diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 7fe0b4fa0c..0000000000 --- a/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -**/.git -/build/ diff --git a/.editorconfig b/.editorconfig index 197a929bc2..f590184de1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -16,6 +16,9 @@ end_of_line = crlf [*.yml] indent_size = 2 +[*.js] +indent_size = 2 + [Makefile] indent_style = tab diff --git a/.exrc b/.exrc deleted file mode 100644 index 1560c9f2a0..0000000000 --- a/.exrc +++ /dev/null @@ -1 +0,0 @@ -set wildignore+=build diff --git a/.github/cspell.json b/.github/cspell.json new file mode 100644 index 0000000000..0c872cb3c3 --- /dev/null +++ b/.github/cspell.json @@ -0,0 +1,109 @@ +{ + "version": "0.2", + "language": "en", + "caseSensitive": false, + "words": [ + "CakePHP", + "VitePress", + "php", + "datetime", + "varchar", + "postgresql", + "mysql", + "sqlite", + "webroot", + "csrf", + "xss", + "cakephp", + "bake", + "phinx", + "hasher", + "middlewares", + "namespaced", + "phpunit", + "composable", + "autowiring", + "stringable", + "arrayable", + "timestampable", + "paginator", + "Enqueue", + "dequeue", + "nullable", + "accessor", + "mutator", + "minphpversion", + "phpversion", + "XAMPP", + "WAMP", + "MAMP", + "controllername", + "actionname", + "Datamapper", + "webservices", + "nginx", + "apache", + "httpd", + "lighttpd", + "mbstring", + "simplexml", + "Laragon", + "composer", + "phar", + "paginate", + "startup", + "endfor", + "endwhile", + "sidebar", + "blocks", + "rewrite", + "before", + "called", + "sites", + "example", + "Classes", + "Redirect", + "Layout" + ], + "ignoreRegExpList": [ + "\\$[a-zA-Z_][a-zA-Z0-9_]*", + "[a-zA-Z]+::[a-zA-Z]+", + "<[^>]+>", + "`[^`]+`", + "```[\\s\\S]*?```", + "\\b[A-Z][a-z]+(?:[A-Z][a-z]+)+\\b", + "\\b[a-z]+_[a-z_]+\\b", + "\\bhttps?:\\/\\/[^\\s]+", + "\\[[a-z]\\][a-z]+", + "\\b[A-Z][a-z]+\\b", + "\\b(true|false|null|while|for|if|else)\\b" + ], + "languageSettings": [ + { + "languageId": "*", + "locale": "en", + "dictionaries": ["en", "softwareTerms", "php"] + }, + { + "languageId": "*", + "locale": "ja", + "allowCompoundWords": true + } + ], + "overrides": [ + { + "filename": "**/docs/ja/**/*.md", + "language": "ja", + "ignoreRegExpList": [ + "[\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Han}]+" + ] + } + ], + "ignorePaths": [ + "node_modules/**", + ".temp/**", + "dist/**", + "**/*.min.js", + "**/*.lock" + ] +} diff --git a/.github/linkchecker-baseline.json b/.github/linkchecker-baseline.json new file mode 100644 index 0000000000..e85fd04bbc --- /dev/null +++ b/.github/linkchecker-baseline.json @@ -0,0 +1,612 @@ +[ + { + "file": "docs/ja/appendices/5-0-migration-guide.md", + "link": "../plugins#loading-a-plugin", + "message": "Anchor '#loading-a-plugin' not found in docs/ja/plugins.md" + }, + { + "file": "docs/ja/appendices/5-1-migration-guide.md", + "link": "../core-libraries/events#registering-event-listeners", + "message": "Anchor '#registering-event-listeners' not found in docs/ja/core-libraries/events.md" + }, + { + "file": "docs/ja/appendices/5-2-migration-guide.md", + "link": "../views/helpers/form#customizing-templates", + "message": "Anchor '#customizing-templates' not found in docs/ja/views/helpers/form.md" + }, + { + "file": "docs/ja/console-commands/shells.md", + "link": "../console-commands/input-output#command-helpers", + "message": "Anchor '#command-helpers' not found in docs/ja/console-commands/input-output.md" + }, + { + "file": "docs/ja/contributing/code.md", + "link": "../orm/database-basics#database-configuration", + "message": "Anchor '#database-configuration' not found in docs/ja/orm/database-basics.md" + }, + { + "file": "docs/ja/contributing/code.md", + "link": "../development/testing#running-tests", + "message": "Anchor '#running-tests' not found in docs/ja/development/testing.md" + }, + { + "file": "docs/ja/controllers/components/request-handling.md", + "link": "../../controllers/middleware#body-parser-middleware", + "message": "Anchor '#body-parser-middleware' not found in docs/ja/controllers/middleware.md" + }, + { + "file": "docs/ja/controllers/middleware.md", + "link": "../development/routing#route-scoped-middleware", + "message": "Anchor '#route-scoped-middleware' not found in docs/ja/development/routing.md" + }, + { + "file": "docs/ja/controllers/middleware.md", + "link": "../core-libraries/caching#cache-configuration", + "message": "Anchor '#cache-configuration' not found in docs/ja/core-libraries/caching.md" + }, + { + "file": "docs/ja/controllers/pagination.md", + "link": "../orm/retrieving-data-and-resultsets#custom-find-methods", + "message": "Anchor '#custom-find-methods' not found in docs/ja/orm/retrieving-data-and-resultsets.md" + }, + { + "file": "docs/ja/controllers/pagination.md", + "link": "../views/helpers/paginator#paginator-helper-multiple", + "message": "Anchor '#paginator-helper-multiple' not found in docs/ja/views/helpers/paginator.md" + }, + { + "file": "docs/ja/controllers/pagination.md", + "link": "../orm/table-objects#table-registry-usage", + "message": "Anchor '#table-registry-usage' not found in docs/ja/orm/table-objects.md" + }, + { + "file": "docs/ja/controllers/request-response.md", + "link": "../development/routing#route-elements", + "message": "Anchor '#route-elements' not found in docs/ja/development/routing.md" + }, + { + "file": "docs/ja/controllers/request-response.md", + "link": "../development/routing#route-elements", + "message": "Anchor '#route-elements' not found in docs/ja/development/routing.md" + }, + { + "file": "docs/ja/controllers/request-response.md", + "link": "../development/routing#passed-arguments", + "message": "Anchor '#passed-arguments' not found in docs/ja/development/routing.md" + }, + { + "file": "docs/ja/controllers/request-response.md", + "link": "../development/routing#prefix-routing", + "message": "Anchor '#prefix-routing' not found in docs/ja/development/routing.md" + }, + { + "file": "docs/ja/controllers/request-response.md", + "link": "../controllers/middleware#body-parser-middleware", + "message": "Anchor '#body-parser-middleware' not found in docs/ja/controllers/middleware.md" + }, + { + "file": "docs/ja/controllers/request-response.md", + "link": "../controllers/middleware#encrypted-cookie-middleware", + "message": "Anchor '#encrypted-cookie-middleware' not found in docs/ja/controllers/middleware.md" + }, + { + "file": "docs/ja/core-libraries/caching.md", + "link": "../orm/query-builder#caching-query-results", + "message": "Anchor '#caching-query-results' not found in docs/ja/orm/query-builder.md" + }, + { + "file": "docs/ja/core-libraries/caching.md", + "link": "../orm/query-builder#caching-query-results", + "message": "Anchor '#caching-query-results' not found in docs/ja/orm/query-builder.md" + }, + { + "file": "docs/ja/core-libraries/email.md", + "link": "../core-libraries/logging#logging-levels", + "message": "Anchor '#logging-levels' not found in docs/ja/core-libraries/logging.md" + }, + { + "file": "docs/ja/core-libraries/email.md", + "link": "../core-libraries/logging#logging-scopes", + "message": "Anchor '#logging-scopes' not found in docs/ja/core-libraries/logging.md" + }, + { + "file": "docs/ja/core-libraries/email.md", + "link": "../core-libraries/events#registering-event-listeners", + "message": "Anchor '#registering-event-listeners' not found in docs/ja/core-libraries/events.md" + }, + { + "file": "docs/ja/core-libraries/events.md", + "link": "../orm/table-objects#table-callbacks", + "message": "Anchor '#table-callbacks' not found in docs/ja/orm/table-objects.md" + }, + { + "file": "docs/ja/core-libraries/events.md", + "link": "../controllers#controller-life-cycle", + "message": "Anchor '#controller-life-cycle' not found in docs/ja/controllers.md" + }, + { + "file": "docs/ja/core-libraries/events.md", + "link": "../views#view-events", + "message": "Anchor '#view-events' not found in docs/ja/views.md" + }, + { + "file": "docs/ja/core-libraries/events.md", + "link": "../development/testing#testing-events", + "message": "Anchor '#testing-events' not found in docs/ja/development/testing.md" + }, + { + "file": "docs/ja/core-libraries/time.md", + "link": "../core-libraries/internationalization-and-localization#parsing-localized-dates", + "message": "Anchor '#parsing-localized-dates' not found in docs/ja/core-libraries/internationalization-and-localization.md" + }, + { + "file": "docs/ja/core-libraries/validation.md", + "link": "../orm/validation#application-rules", + "message": "Anchor '#application-rules' not found in docs/ja/orm/validation.md" + }, + { + "file": "docs/ja/deployment.md", + "link": "installation#without-url-rewriting", + "message": "Anchor '#without-url-rewriting' not found in docs/ja/installation.md" + }, + { + "file": "docs/ja/development/application.md", + "link": "../development/testing#integration-testing", + "message": "Anchor '#integration-testing' not found in docs/ja/development/testing.md" + }, + { + "file": "docs/ja/development/configuration.md", + "link": "../orm/database-basics#database-configuration", + "message": "Anchor '#database-configuration' not found in docs/ja/orm/database-basics.md" + }, + { + "file": "docs/ja/development/configuration.md", + "link": "../core-libraries/caching#cache-configuration", + "message": "Anchor '#cache-configuration' not found in docs/ja/core-libraries/caching.md" + }, + { + "file": "docs/ja/development/configuration.md", + "link": "../development/errors#error-configuration", + "message": "Anchor '#error-configuration' not found in docs/ja/development/errors.md" + }, + { + "file": "docs/ja/development/configuration.md", + "link": "../core-libraries/logging#log-configuration", + "message": "Anchor '#log-configuration' not found in docs/ja/core-libraries/logging.md" + }, + { + "file": "docs/ja/development/configuration.md", + "link": "../core-libraries/email#email-configuration", + "message": "Anchor '#email-configuration' not found in docs/ja/core-libraries/email.md" + }, + { + "file": "docs/ja/development/configuration.md", + "link": "../development/sessions#session-configuration", + "message": "Anchor '#session-configuration' not found in docs/ja/development/sessions.md" + }, + { + "file": "docs/ja/development/configuration.md", + "link": "../core-libraries/inflector#inflection-configuration", + "message": "Anchor '#inflection-configuration' not found in docs/ja/core-libraries/inflector.md" + }, + { + "file": "docs/ja/development/errors.md", + "link": "../development/routing#prefix-routing", + "message": "Anchor '#prefix-routing' not found in docs/ja/development/routing.md" + }, + { + "file": "docs/ja/development/rest.md", + "link": "../development/routing#resource-routes", + "message": "Anchor '#resource-routes' not found in docs/ja/development/routing.md" + }, + { + "file": "docs/ja/development/rest.md", + "link": "../development/routing#resource-routes", + "message": "Anchor '#resource-routes' not found in docs/ja/development/routing.md" + }, + { + "file": "docs/ja/development/routing.md", + "link": "../plugins#plugin-routes", + "message": "Anchor '#plugin-routes' not found in docs/ja/plugins.md" + }, + { + "file": "docs/ja/development/routing.md", + "link": "../controllers/middleware#routing-middleware", + "message": "Anchor '#routing-middleware' not found in docs/ja/controllers/middleware.md" + }, + { + "file": "docs/ja/development/routing.md", + "link": "../controllers/middleware#routing-middleware", + "message": "Anchor '#routing-middleware' not found in docs/ja/controllers/middleware.md" + }, + { + "file": "docs/ja/development/testing.md", + "link": "../controllers/middleware#encrypted-cookie-middleware", + "message": "Anchor '#encrypted-cookie-middleware' not found in docs/ja/controllers/middleware.md" + }, + { + "file": "docs/ja/development/testing.md", + "link": "../controllers/request-response#request-file-uploads", + "message": "Anchor '#request-file-uploads' not found in docs/ja/controllers/request-response.md" + }, + { + "file": "docs/ja/development/testing.md", + "link": "../console-commands/commands#console-integration-testing", + "message": "Anchor '#console-integration-testing' not found in docs/ja/console-commands/commands.md" + }, + { + "file": "docs/ja/development/testing.md", + "link": "../development/dependency-injection#mocking-services-in-tests", + "message": "Anchor '#mocking-services-in-tests' not found in docs/ja/development/dependency-injection.md" + }, + { + "file": "docs/ja/development/testing.md", + "link": "../core-libraries/events#tracking-events", + "message": "Anchor '#tracking-events' not found in docs/ja/core-libraries/events.md" + }, + { + "file": "docs/ja/development/testing.md", + "link": "../core-libraries/email#email-testing", + "message": "Anchor '#email-testing' not found in docs/ja/core-libraries/email.md" + }, + { + "file": "docs/ja/index.md", + "link": "../_downloads/en/CakePHPBook.pdf", + "message": "File not found: docs/_downloads/en/CakePHPBook.pdf" + }, + { + "file": "docs/ja/index.md", + "link": "../_downloads/ja/CakePHP.epub", + "message": "File not found: docs/_downloads/ja/CakePHP.epub" + }, + { + "file": "docs/ja/index.md", + "link": "intro#request-cycle", + "message": "Anchor '#request-cycle' not found in docs/ja/intro.md" + }, + { + "file": "docs/ja/orm.md", + "link": "orm/database-basics#database-configuration", + "message": "Anchor '#database-configuration' not found in docs/ja/orm/database-basics.md" + }, + { + "file": "docs/ja/orm.md", + "link": "intro/conventions#model-and-database-conventions", + "message": "Anchor '#model-and-database-conventions' not found in docs/ja/intro/conventions.md" + }, + { + "file": "docs/ja/orm/associations.md", + "link": "../orm/retrieving-data-and-resultsets#custom-find-methods", + "message": "Anchor '#custom-find-methods' not found in docs/ja/orm/retrieving-data-and-resultsets.md" + }, + { + "file": "docs/ja/orm/associations.md", + "link": "../orm/retrieving-data-and-resultsets#eager-loading-associations", + "message": "Anchor '#eager-loading-associations' not found in docs/ja/orm/retrieving-data-and-resultsets.md" + }, + { + "file": "docs/ja/orm/behaviors.md", + "link": "../orm/table-objects#table-callbacks", + "message": "Anchor '#table-callbacks' not found in docs/ja/orm/table-objects.md" + }, + { + "file": "docs/ja/orm/behaviors.md", + "link": "../orm/retrieving-data-and-resultsets#custom-find-methods", + "message": "Anchor '#custom-find-methods' not found in docs/ja/orm/retrieving-data-and-resultsets.md" + }, + { + "file": "docs/ja/orm/behaviors/counter-cache.md", + "link": "../../orm/associations#using-the-through-option", + "message": "Anchor '#using-the-through-option' not found in docs/ja/orm/associations.md" + }, + { + "file": "docs/ja/orm/database-basics.md", + "link": "../orm/table-objects#configuring-table-connections", + "message": "Anchor '#configuring-table-connections' not found in docs/ja/orm/table-objects.md" + }, + { + "file": "docs/ja/orm/database-basics.md", + "link": "../orm/saving-data#saving-complex-types", + "message": "Anchor '#saving-complex-types' not found in docs/ja/orm/saving-data.md" + }, + { + "file": "docs/ja/orm/database-basics.md", + "link": "../orm/saving-data#saving-complex-types", + "message": "Anchor '#saving-complex-types' not found in docs/ja/orm/saving-data.md" + }, + { + "file": "docs/ja/orm/deleting-data.md", + "link": "../orm/validation#application-rules", + "message": "Anchor '#application-rules' not found in docs/ja/orm/validation.md" + }, + { + "file": "docs/ja/orm/entities.md", + "link": "../orm/saving-data#saving-entities", + "message": "Anchor '#saving-entities' not found in docs/ja/orm/saving-data.md" + }, + { + "file": "docs/ja/orm/entities.md", + "link": "../orm/saving-data#changing-accessible-fields", + "message": "Anchor '#changing-accessible-fields' not found in docs/ja/orm/saving-data.md" + }, + { + "file": "docs/ja/orm/entities.md", + "link": "../orm/saving-data#saving-complex-types", + "message": "Anchor '#saving-complex-types' not found in docs/ja/orm/saving-data.md" + }, + { + "file": "docs/ja/orm/query-builder.md", + "link": "../orm/database-basics#database-queries", + "message": "Anchor '#database-queries' not found in docs/ja/orm/database-basics.md" + }, + { + "file": "docs/ja/orm/query-builder.md", + "link": "../orm/retrieving-data-and-resultsets#table-find-list", + "message": "Anchor '#table-find-list' not found in docs/ja/orm/retrieving-data-and-resultsets.md" + }, + { + "file": "docs/ja/orm/query-builder.md", + "link": "../orm/database-basics#database-query-logging", + "message": "Anchor '#database-query-logging' not found in docs/ja/orm/database-basics.md" + }, + { + "file": "docs/ja/orm/query-builder.md", + "link": "../orm/retrieving-data-and-resultsets#map-reduce", + "message": "Anchor '#map-reduce' not found in docs/ja/orm/retrieving-data-and-resultsets.md" + }, + { + "file": "docs/ja/orm/query-builder.md", + "link": "../orm/database-basics#data-types", + "message": "Anchor '#data-types' not found in docs/ja/orm/database-basics.md" + }, + { + "file": "docs/ja/orm/query-builder.md", + "link": "../orm/database-basics#database-basics-binding-values", + "message": "Anchor '#database-basics-binding-values' not found in docs/ja/orm/database-basics.md" + }, + { + "file": "docs/ja/orm/query-builder.md", + "link": "../orm/database-basics#running-select-statements", + "message": "Anchor '#running-select-statements' not found in docs/ja/orm/database-basics.md" + }, + { + "file": "docs/ja/orm/retrieving-data-and-resultsets.md", + "link": "../orm/query-builder#query-count", + "message": "Anchor '#query-count' not found in docs/ja/orm/query-builder.md" + }, + { + "file": "docs/ja/orm/retrieving-data-and-resultsets.md", + "link": "../orm/query-builder#adding-joins", + "message": "Anchor '#adding-joins' not found in docs/ja/orm/query-builder.md" + }, + { + "file": "docs/ja/orm/retrieving-data-and-resultsets.md", + "link": "../orm/entities#lazy-load-associations", + "message": "Anchor '#lazy-load-associations' not found in docs/ja/orm/entities.md" + }, + { + "file": "docs/ja/orm/retrieving-data-and-resultsets.md", + "link": "../orm/query-builder#format-results", + "message": "Anchor '#format-results' not found in docs/ja/orm/query-builder.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../orm/entities#entities-mass-assignment", + "message": "Anchor '#entities-mass-assignment' not found in docs/ja/orm/entities.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../orm/validation#validating-request-data", + "message": "Anchor '#validating-request-data' not found in docs/ja/orm/validation.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../orm/validation#using-different-validators-per-association", + "message": "Anchor '#using-different-validators-per-association' not found in docs/ja/orm/validation.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../orm/entities#entities-mass-assignment", + "message": "Anchor '#entities-mass-assignment' not found in docs/ja/orm/entities.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../orm/validation#validating-request-data", + "message": "Anchor '#validating-request-data' not found in docs/ja/orm/validation.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../orm/entities#entities-mass-assignment", + "message": "Anchor '#entities-mass-assignment' not found in docs/ja/orm/entities.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../orm/validation#application-rules", + "message": "Anchor '#application-rules' not found in docs/ja/orm/validation.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../orm/validation#application-rules", + "message": "Anchor '#application-rules' not found in docs/ja/orm/validation.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../views/helpers/form#associated-form-inputs", + "message": "Anchor '#associated-form-inputs' not found in docs/ja/views/helpers/form.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../core-libraries/inflector#inflector-methods-summary", + "message": "Anchor '#inflector-methods-summary' not found in docs/ja/core-libraries/inflector.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../core-libraries/inflector#inflector-methods-summary", + "message": "Anchor '#inflector-methods-summary' not found in docs/ja/core-libraries/inflector.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../core-libraries/inflector#inflector-methods-summary", + "message": "Anchor '#inflector-methods-summary' not found in docs/ja/core-libraries/inflector.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../orm/associations#has-many-associations", + "message": "Anchor '#has-many-associations' not found in docs/ja/orm/associations.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../core-libraries/inflector#inflector-methods-summary", + "message": "Anchor '#inflector-methods-summary' not found in docs/ja/core-libraries/inflector.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../orm/associations#belongs-to-many-associations", + "message": "Anchor '#belongs-to-many-associations' not found in docs/ja/orm/associations.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../views/helpers/form#associated-form-inputs", + "message": "Anchor '#associated-form-inputs' not found in docs/ja/views/helpers/form.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../orm/database-basics#adding-custom-database-types", + "message": "Anchor '#adding-custom-database-types' not found in docs/ja/orm/database-basics.md" + }, + { + "file": "docs/ja/orm/saving-data.md", + "link": "../orm/query-builder#query-builder-updating-data", + "message": "Anchor '#query-builder-updating-data' not found in docs/ja/orm/query-builder.md" + }, + { + "file": "docs/ja/orm/schema-system.md", + "link": "../development/testing#test-fixtures", + "message": "Anchor '#test-fixtures' not found in docs/ja/development/testing.md" + }, + { + "file": "docs/ja/orm/table-objects.md", + "link": "../orm/database-basics#database-configuration", + "message": "Anchor '#database-configuration' not found in docs/ja/orm/database-basics.md" + }, + { + "file": "docs/ja/orm/table-objects.md", + "link": "../orm/saving-data#before-marshal", + "message": "Anchor '#before-marshal' not found in docs/ja/orm/saving-data.md" + }, + { + "file": "docs/ja/orm/table-objects.md", + "link": "../orm/retrieving-data-and-resultsets#map-reduce", + "message": "Anchor '#map-reduce' not found in docs/ja/orm/retrieving-data-and-resultsets.md" + }, + { + "file": "docs/ja/orm/validation.md", + "link": "../core-libraries/validation#creating-validators", + "message": "Anchor '#creating-validators' not found in docs/ja/core-libraries/validation.md" + }, + { + "file": "docs/ja/tutorials-and-examples/blog/blog.md", + "link": "../../installation#without-url-rewriting", + "message": "Anchor '#without-url-rewriting' not found in docs/ja/installation.md" + }, + { + "file": "docs/ja/tutorials-and-examples/blog/part-two.md", + "link": "../../development/routing#named-routes", + "message": "Anchor '#named-routes' not found in docs/ja/development/routing.md" + }, + { + "file": "docs/ja/tutorials-and-examples/blog/part-two.md", + "link": "../../views#view-layouts", + "message": "Anchor '#view-layouts' not found in docs/ja/views.md" + }, + { + "file": "docs/ja/tutorials-and-examples/bookmarks/intro.md", + "link": "../../orm/retrieving-data-and-resultsets#custom-find-methods", + "message": "Anchor '#custom-find-methods' not found in docs/ja/orm/retrieving-data-and-resultsets.md" + }, + { + "file": "docs/ja/tutorials-and-examples/cms/articles-controller.md", + "link": "../../development/routing#named-routes", + "message": "Anchor '#named-routes' not found in docs/ja/development/routing.md" + }, + { + "file": "docs/ja/tutorials-and-examples/cms/articles-controller.md", + "link": "../../orm/retrieving-data-and-resultsets#dynamic-finders", + "message": "Anchor '#dynamic-finders' not found in docs/ja/orm/retrieving-data-and-resultsets.md" + }, + { + "file": "docs/ja/tutorials-and-examples/cms/articles-controller.md", + "link": "../../orm/table-objects#table-callbacks", + "message": "Anchor '#table-callbacks' not found in docs/ja/orm/table-objects.md" + }, + { + "file": "docs/ja/tutorials-and-examples/cms/articles-controller.md", + "link": "../../orm/validation#validating-request-data", + "message": "Anchor '#validating-request-data' not found in docs/ja/orm/validation.md" + }, + { + "file": "docs/ja/tutorials-and-examples/cms/database.md", + "link": "../../orm/entities#entities-mass-assignment", + "message": "Anchor '#entities-mass-assignment' not found in docs/ja/orm/entities.md" + }, + { + "file": "docs/ja/tutorials-and-examples/cms/tags-and-users.md", + "link": "../../orm/retrieving-data-and-resultsets#custom-find-methods", + "message": "Anchor '#custom-find-methods' not found in docs/ja/orm/retrieving-data-and-resultsets.md" + }, + { + "file": "docs/ja/views/cells.md", + "link": "../controllers/pagination#paginating-multiple-queries", + "message": "Anchor '#paginating-multiple-queries' not found in docs/ja/controllers/pagination.md" + }, + { + "file": "docs/ja/views/helpers/form.md", + "link": "../../core-libraries/validation#creating-validators", + "message": "Anchor '#creating-validators' not found in docs/ja/core-libraries/validation.md" + }, + { + "file": "docs/ja/views/helpers/form.md", + "link": "../../views#view-blocks", + "message": "Anchor '#view-blocks' not found in docs/ja/views.md" + }, + { + "file": "docs/ja/views/helpers/form.md", + "link": "../../views#view-blocks", + "message": "Anchor '#view-blocks' not found in docs/ja/views.md" + }, + { + "file": "docs/ja/views/helpers/html.md", + "link": "../../views#view-blocks", + "message": "Anchor '#view-blocks' not found in docs/ja/views.md" + }, + { + "file": "docs/ja/views/helpers/paginator.md", + "link": "../../controllers/pagination#control-which-fields-used-for-ordering", + "message": "Anchor '#control-which-fields-used-for-ordering' not found in docs/ja/controllers/pagination.md" + }, + { + "file": "docs/ja/views/helpers/paginator.md", + "link": "../../controllers/pagination#paginating-multiple-queries", + "message": "Anchor '#paginating-multiple-queries' not found in docs/ja/controllers/pagination.md" + }, + { + "file": "docs/ja/views/helpers/url.md", + "link": "../../views/helpers#aliasing-helpers", + "message": "Anchor '#aliasing-helpers' not found in docs/ja/views/helpers.md" + }, + { + "file": "docs/ja/views/json-and-xml-views.md", + "link": "../development/routing#file-extensions", + "message": "Anchor '#file-extensions' not found in docs/ja/development/routing.md" + }, + { + "file": "docs/ja/views/json-and-xml-views.md", + "link": "../development/routing#file-extensions", + "message": "Anchor '#file-extensions' not found in docs/ja/development/routing.md" + }, + { + "file": "docs/ja/views/themes.md", + "link": "../plugins#plugin-create-your-own", + "message": "Anchor '#plugin-create-your-own' not found in docs/ja/plugins.md" + } +] diff --git a/.github/markdownlint.json b/.github/markdownlint.json new file mode 100644 index 0000000000..4327587dbf --- /dev/null +++ b/.github/markdownlint.json @@ -0,0 +1,50 @@ +{ + "default": true, + "heading-increment": false, + "no-hard-tabs": false, + "no-multiple-blanks": false, + "line-length": false, + "commands-show-output": false, + "blanks-around-headings": false, + "no-duplicate-heading": false, + "single-h1": false, + "no-trailing-punctuation": false, + "no-blanks-blockquote": false, + "list-marker-space": false, + "blanks-around-fences": false, + "blanks-around-lists": false, + "no-inline-html": { + "allowed_elements": [ + "Badge", + "div", + "span", + "br", + "style", + "details", + "summary", + "table", + "thead", + "tbody", + "tr", + "th", + "td", + "img", + "a", + "svg", + "path", + "figure", + "p" + ] + }, + "no-bare-urls": false, + "fenced-code-language": false, + "first-line-heading": false, + "code-block-style": false, + "single-trailing-newline": false, + "link-fragments": false, + "table-pipe-style": false, + "table-column-count": false, + "emphasis-style": false, + "table-column-style": false, + "descriptive-link-text": false +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index b3273a6f42..0000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: CI - -on: - push: - branches: - - '4.x' - - '4.next' - - '5.x' - pull_request: - branches: - - '*' - workflow_dispatch: - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-24.04 - strategy: - matrix: - doc-type: ['HTML', 'EPUB', 'PDF'] - - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: 3.11 - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - - uses: awalsh128/cache-apt-pkgs-action@v1 - with: - packages: texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended texlive-lang-all - version: ubuntu-24.04 - - - name: Build Docs - run: | - if [[ ${{ matrix.doc-type }} == 'HTML' ]]; then make html SPHINXOPTS='-W --keep-going'; fi - if [[ ${{ matrix.doc-type }} == 'EPUB' ]]; then make epub; fi - if [[ ${{ matrix.doc-type }} == 'PDF' ]]; then make latex; fi diff --git a/.github/workflows/deploy_5.yml b/.github/workflows/deploy_5.yml index 3c101e2dda..05caa87741 100644 --- a/.github/workflows/deploy_5.yml +++ b/.github/workflows/deploy_5.yml @@ -1,5 +1,5 @@ --- -name: 'deploy_5.x' +name: 'deploy_5' on: push: @@ -9,20 +9,20 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/5.next' }} jobs: deploy: runs-on: ubuntu-latest steps: - name: Cloning repo - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Push to dokku uses: dokku/github-action@master with: - git_remote_url: 'ssh://dokku@apps.cakephp.org:22/book-5' + branch: '5.x' + git_remote_url: 'ssh://dokku@apps.cakephp.org:22/newbook-5' git_push_flags: '-f' ssh_private_key: ${{ secrets.DOKKU_SSH_PRIVATE_KEY }} diff --git a/.github/workflows/deploy_5next.yml b/.github/workflows/deploy_5next.yml deleted file mode 100644 index 7e211505ca..0000000000 --- a/.github/workflows/deploy_5next.yml +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: 'deploy_5.next' - -on: - push: - branches: - - 5.next - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/5.next' }} - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Cloning repo - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Push to dokku - uses: dokku/github-action@master - with: - git_remote_url: 'ssh://dokku@apps.cakephp.org:22/book-5next' - git_push_flags: '-f' - ssh_private_key: ${{ secrets.DOKKU_SSH_PRIVATE_KEY }} diff --git a/.github/workflows/docs-validation.yml b/.github/workflows/docs-validation.yml new file mode 100644 index 0000000000..8bf08474f7 --- /dev/null +++ b/.github/workflows/docs-validation.yml @@ -0,0 +1,114 @@ +name: Documentation Validation + +on: + push: + branches: + - 5.x + - 6.x + - 5.next + paths: + - 'docs/**' + - '.github/**' + - 'toc_*.json' + - 'config.js' + pull_request: + paths: + - 'docs/**' + - '.github/**' + - 'toc_*.json' + - 'config.js' + +jobs: + js-lint: + name: Validate JavaScript Files + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Validate config.js syntax + run: node --check config.js + + json-lint: + name: Validate JSON Files + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Validate JSON syntax + run: | + for file in toc_*.json; do + if ! jq empty "$file" 2>/dev/null; then + echo "Invalid JSON: $file" + exit 1 + fi + echo "✅ Valid: $file" + done + + markdown-lint: + name: Lint Markdown + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Lint markdown files + uses: articulate/actions-markdownlint@v1 + with: + config: .github/markdownlint.json + files: 'docs/**/*.md' + ignore: 'node_modules' + + spell-check: + name: Spell Check + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Check spelling + uses: streetsidesoftware/cspell-action@v5 + with: + files: 'docs/**/*.md' + config: '.github/cspell.json' + incremental_files_only: true + + link-check: + name: Check Internal Links + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Get changed markdown files + id: changed-files + run: | + if [ "${{ github.event_name }}" == "pull_request" ]; then + # Get files changed in PR + FILES=$(git diff --name-only --diff-filter=d origin/${{ github.base_ref }}...HEAD | grep '\.md$' || true) + if [ -z "$FILES" ]; then + echo "No markdown files changed" + echo "files=" >> $GITHUB_OUTPUT + else + # Convert to space-separated list for script + echo "files=$(echo $FILES | tr '\n' ' ')" >> $GITHUB_OUTPUT + echo "Checking files:" + echo "$FILES" + fi + else + # For push events, check all files using glob pattern + echo 'files="docs/**/*.md"' >> $GITHUB_OUTPUT + echo 'Checking all files: docs/**/*.md' + fi + + - name: Check internal links + if: steps.changed-files.outputs.files != '' + run: node bin/check-links.js --baseline .github/linkchecker-baseline.json ${{ steps.changed-files.outputs.files }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 3be2c5aef0..47bbb9dbfb 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/stale@v9 + - uses: actions/stale@v10 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue is stale because it has been open for 120 days with no activity. Remove the `stale` label or comment or this will be closed in 15 days' diff --git a/.gitignore b/.gitignore index 5face6351a..1de888e91c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ # Project specific files # ########################## -*.pyc -/build -*/_build/* +.vitepress/dist +.vitepress/cache +.vitepress/.temp +/node_modules/ +.temp/ # IDE and editor specific files # ################################# @@ -10,6 +12,7 @@ .idea .project .vscode +.zed # OS generated files # ###################### diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 102c8de42c..0000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1 +0,0 @@ -Please find here [CakePHP Contributor Code of Conduct](https://github.com/cakephp/code-of-conduct). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index fe1d65bdf8..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1 +0,0 @@ -Please find here [Contributing](https://github.com/cakephp/docs#contributing). diff --git a/Dockerfile b/Dockerfile index 34f3a7f8f3..80ffb5a73e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,51 @@ -FROM debian:bookworm +# ---------------------- +# 1. Build stage +# ---------------------- +FROM node:22-alpine AS builder -ENV DEBIAN_FRONTEND=noninteractive +# Install git and rsync +RUN apk add --no-cache git rsync -LABEL Description="This image is used to create an environment to contribute to the cakephp/docs" +WORKDIR /app -RUN apt-get update && apt-get install -y \ - build-essential \ - latexmk \ - php \ - python3-full \ - texlive-fonts-recommended \ - texlive-lang-all \ - texlive-latex-extra \ - texlive-latex-recommended \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* +# Bust cache by fetching latest commit info +ADD https://api.github.com/repos/cakephp/docs-skeleton/git/refs/heads/main /tmp/cache-bust.json -RUN python3 -m venv /tmp/venv -ENV PATH="/tmp/venv/bin:$PATH" +# Clone cakephp-docs-skeleton into vitepress directory +RUN git clone --depth 1 https://github.com/cakephp/docs-skeleton.git vitepress -COPY requirements.txt /tmp/ -RUN pip install -r /tmp/requirements.txt +# Copy documentation and config files into the skeleton +# Use rsync to merge docs instead of replacing to preserve shared public assets +RUN --mount=type=bind,source=docs,target=/tmp/docs \ + rsync -av /tmp/docs/ vitepress/docs/ -WORKDIR /data -VOLUME "/data" +COPY config.js vitepress/config.js +COPY toc_en.json vitepress/toc_en.json +COPY toc_ja.json vitepress/toc_ja.json -CMD ["/bin/bash"] +# Install vitepress deps +WORKDIR /app/vitepress +RUN npm install + +# Increase max-old-space-size to avoid memory issues during build +ENV NODE_OPTIONS="--max-old-space-size=8192" + +# Build VitePress site +RUN npm run docs:build + +# ---------------------- +# 2. Runtime stage (nginx) +# ---------------------- +FROM nginx:1.27-alpine AS runner + +# Copy built files +COPY --from=builder /app/vitepress/.vitepress/dist /usr/share/nginx/html + +# Expose port +EXPOSE 80 + +# Health check (optional) +HEALTHCHECK CMD wget --quiet --tries=1 --spider http://localhost:80/ || exit 1 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index 349b4b9dca..0000000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,13 +0,0 @@ - - -**Issue Description** - - - - - ---- - -**Do you want to address this issue?** - - diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index dea7365f70..0000000000 --- a/LICENSE.txt +++ /dev/null @@ -1,440 +0,0 @@ -CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org) -Copyright (c) 2005-present, Cake Software Foundation, Inc. (https://cakefoundation.org) - -Attribution-NonCommercial-ShareAlike 4.0 International - -======================================================================= - -Creative Commons Corporation ("Creative Commons") is not a law firm and -does not provide legal services or legal advice. Distribution of -Creative Commons public licenses does not create a lawyer-client or -other relationship. Creative Commons makes its licenses and related -information available on an "as-is" basis. Creative Commons gives no -warranties regarding its licenses, any material licensed under their -terms and conditions, or any related information. Creative Commons -disclaims all liability for damages resulting from their use to the -fullest extent possible. - -Using Creative Commons Public Licenses - -Creative Commons public licenses provide a standard set of terms and -conditions that creators and other rights holders may use to share -original works of authorship and other material subject to copyright -and certain other rights specified in the public license below. The -following considerations are for informational purposes only, are not -exhaustive, and do not form part of our licenses. - - Considerations for licensors: Our public licenses are - intended for use by those authorized to give the public - permission to use material in ways otherwise restricted by - copyright and certain other rights. Our licenses are - irrevocable. Licensors should read and understand the terms - and conditions of the license they choose before applying it. - Licensors should also secure all rights necessary before - applying our licenses so that the public can reuse the - material as expected. Licensors should clearly mark any - material not subject to the license. This includes other CC- - licensed material, or material used under an exception or - limitation to copyright. More considerations for licensors: - wiki.creativecommons.org/Considerations_for_licensors - - Considerations for the public: By using one of our public - licenses, a licensor grants the public permission to use the - licensed material under specified terms and conditions. If - the licensor's permission is not necessary for any reason--for - example, because of any applicable exception or limitation to - copyright--then that use is not regulated by the license. Our - licenses grant only permissions under copyright and certain - other rights that a licensor has authority to grant. Use of - the licensed material may still be restricted for other - reasons, including because others have copyright or other - rights in the material. A licensor may make special requests, - such as asking that all changes be marked or described. - Although not required by our licenses, you are encouraged to - respect those requests where reasonable. More considerations - for the public: - wiki.creativecommons.org/Considerations_for_licensees - -======================================================================= - -Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International -Public License - -By exercising the Licensed Rights (defined below), You accept and agree -to be bound by the terms and conditions of this Creative Commons -Attribution-NonCommercial-ShareAlike 4.0 International Public License -("Public License"). To the extent this Public License may be -interpreted as a contract, You are granted the Licensed Rights in -consideration of Your acceptance of these terms and conditions, and the -Licensor grants You such rights in consideration of benefits the -Licensor receives from making the Licensed Material available under -these terms and conditions. - - -Section 1 -- Definitions. - - a. Adapted Material means material subject to Copyright and Similar - Rights that is derived from or based upon the Licensed Material - and in which the Licensed Material is translated, altered, - arranged, transformed, or otherwise modified in a manner requiring - permission under the Copyright and Similar Rights held by the - Licensor. For purposes of this Public License, where the Licensed - Material is a musical work, performance, or sound recording, - Adapted Material is always produced where the Licensed Material is - synched in timed relation with a moving image. - - b. Adapter's License means the license You apply to Your Copyright - and Similar Rights in Your contributions to Adapted Material in - accordance with the terms and conditions of this Public License. - - c. BY-NC-SA Compatible License means a license listed at - creativecommons.org/compatiblelicenses, approved by Creative - Commons as essentially the equivalent of this Public License. - - d. Copyright and Similar Rights means copyright and/or similar rights - closely related to copyright including, without limitation, - performance, broadcast, sound recording, and Sui Generis Database - Rights, without regard to how the rights are labeled or - categorized. For purposes of this Public License, the rights - specified in Section 2(b)(1)-(2) are not Copyright and Similar - Rights. - - e. Effective Technological Measures means those measures that, in the - absence of proper authority, may not be circumvented under laws - fulfilling obligations under Article 11 of the WIPO Copyright - Treaty adopted on December 20, 1996, and/or similar international - agreements. - - f. Exceptions and Limitations means fair use, fair dealing, and/or - any other exception or limitation to Copyright and Similar Rights - that applies to Your use of the Licensed Material. - - g. License Elements means the license attributes listed in the name - of a Creative Commons Public License. The License Elements of this - Public License are Attribution, NonCommercial, and ShareAlike. - - h. Licensed Material means the artistic or literary work, database, - or other material to which the Licensor applied this Public - License. - - i. Licensed Rights means the rights granted to You subject to the - terms and conditions of this Public License, which are limited to - all Copyright and Similar Rights that apply to Your use of the - Licensed Material and that the Licensor has authority to license. - - j. Licensor means the individual(s) or entity(ies) granting rights - under this Public License. - - k. NonCommercial means not primarily intended for or directed towards - commercial advantage or monetary compensation. For purposes of - this Public License, the exchange of the Licensed Material for - other material subject to Copyright and Similar Rights by digital - file-sharing or similar means is NonCommercial provided there is - no payment of monetary compensation in connection with the - exchange. - - l. Share means to provide material to the public by any means or - process that requires permission under the Licensed Rights, such - as reproduction, public display, public performance, distribution, - dissemination, communication, or importation, and to make material - available to the public including in ways that members of the - public may access the material from a place and at a time - individually chosen by them. - - m. Sui Generis Database Rights means rights other than copyright - resulting from Directive 96/9/EC of the European Parliament and of - the Council of 11 March 1996 on the legal protection of databases, - as amended and/or succeeded, as well as other essentially - equivalent rights anywhere in the world. - - n. You means the individual or entity exercising the Licensed Rights - under this Public License. Your has a corresponding meaning. - - -Section 2 -- Scope. - - a. License grant. - - 1. Subject to the terms and conditions of this Public License, - the Licensor hereby grants You a worldwide, royalty-free, - non-sublicensable, non-exclusive, irrevocable license to - exercise the Licensed Rights in the Licensed Material to: - - a. reproduce and Share the Licensed Material, in whole or - in part, for NonCommercial purposes only; and - - b. produce, reproduce, and Share Adapted Material for - NonCommercial purposes only. - - 2. Exceptions and Limitations. For the avoidance of doubt, where - Exceptions and Limitations apply to Your use, this Public - License does not apply, and You do not need to comply with - its terms and conditions. - - 3. Term. The term of this Public License is specified in Section - 6(a). - - 4. Media and formats; technical modifications allowed. The - Licensor authorizes You to exercise the Licensed Rights in - all media and formats whether now known or hereafter created, - and to make technical modifications necessary to do so. The - Licensor waives and/or agrees not to assert any right or - authority to forbid You from making technical modifications - necessary to exercise the Licensed Rights, including - technical modifications necessary to circumvent Effective - Technological Measures. For purposes of this Public License, - simply making modifications authorized by this Section 2(a) - (4) never produces Adapted Material. - - 5. Downstream recipients. - - a. Offer from the Licensor -- Licensed Material. Every - recipient of the Licensed Material automatically - receives an offer from the Licensor to exercise the - Licensed Rights under the terms and conditions of this - Public License. - - b. Additional offer from the Licensor -- Adapted Material. - Every recipient of Adapted Material from You - automatically receives an offer from the Licensor to - exercise the Licensed Rights in the Adapted Material - under the conditions of the Adapter's License You apply. - - c. No downstream restrictions. You may not offer or impose - any additional or different terms or conditions on, or - apply any Effective Technological Measures to, the - Licensed Material if doing so restricts exercise of the - Licensed Rights by any recipient of the Licensed - Material. - - 6. No endorsement. Nothing in this Public License constitutes or - may be construed as permission to assert or imply that You - are, or that Your use of the Licensed Material is, connected - with, or sponsored, endorsed, or granted official status by, - the Licensor or others designated to receive attribution as - provided in Section 3(a)(1)(A)(i). - - b. Other rights. - - 1. Moral rights, such as the right of integrity, are not - licensed under this Public License, nor are publicity, - privacy, and/or other similar personality rights; however, to - the extent possible, the Licensor waives and/or agrees not to - assert any such rights held by the Licensor to the limited - extent necessary to allow You to exercise the Licensed - Rights, but not otherwise. - - 2. Patent and trademark rights are not licensed under this - Public License. - - 3. To the extent possible, the Licensor waives any right to - collect royalties from You for the exercise of the Licensed - Rights, whether directly or through a collecting society - under any voluntary or waivable statutory or compulsory - licensing scheme. In all other cases the Licensor expressly - reserves any right to collect such royalties, including when - the Licensed Material is used other than for NonCommercial - purposes. - - -Section 3 -- License Conditions. - -Your exercise of the Licensed Rights is expressly made subject to the -following conditions. - - a. Attribution. - - 1. If You Share the Licensed Material (including in modified - form), You must: - - a. retain the following if it is supplied by the Licensor - with the Licensed Material: - - i. identification of the creator(s) of the Licensed - Material and any others designated to receive - attribution, in any reasonable manner requested by - the Licensor (including by pseudonym if - designated); - - ii. a copyright notice; - - iii. a notice that refers to this Public License; - - iv. a notice that refers to the disclaimer of - warranties; - - v. a URI or hyperlink to the Licensed Material to the - extent reasonably practicable; - - b. indicate if You modified the Licensed Material and - retain an indication of any previous modifications; and - - c. indicate the Licensed Material is licensed under this - Public License, and include the text of, or the URI or - hyperlink to, this Public License. - - 2. You may satisfy the conditions in Section 3(a)(1) in any - reasonable manner based on the medium, means, and context in - which You Share the Licensed Material. For example, it may be - reasonable to satisfy the conditions by providing a URI or - hyperlink to a resource that includes the required - information. - 3. If requested by the Licensor, You must remove any of the - information required by Section 3(a)(1)(A) to the extent - reasonably practicable. - - b. ShareAlike. - - In addition to the conditions in Section 3(a), if You Share - Adapted Material You produce, the following conditions also apply. - - 1. The Adapter's License You apply must be a Creative Commons - license with the same License Elements, this version or - later, or a BY-NC-SA Compatible License. - - 2. You must include the text of, or the URI or hyperlink to, the - Adapter's License You apply. You may satisfy this condition - in any reasonable manner based on the medium, means, and - context in which You Share Adapted Material. - - 3. You may not offer or impose any additional or different terms - or conditions on, or apply any Effective Technological - Measures to, Adapted Material that restrict exercise of the - rights granted under the Adapter's License You apply. - - -Section 4 -- Sui Generis Database Rights. - -Where the Licensed Rights include Sui Generis Database Rights that -apply to Your use of the Licensed Material: - - a. for the avoidance of doubt, Section 2(a)(1) grants You the right - to extract, reuse, reproduce, and Share all or a substantial - portion of the contents of the database for NonCommercial purposes - only; - - b. if You include all or a substantial portion of the database - contents in a database in which You have Sui Generis Database - Rights, then the database in which You have Sui Generis Database - Rights (but not its individual contents) is Adapted Material, - including for purposes of Section 3(b); and - - c. You must comply with the conditions in Section 3(a) if You Share - all or a substantial portion of the contents of the database. - -For the avoidance of doubt, this Section 4 supplements and does not -replace Your obligations under this Public License where the Licensed -Rights include other Copyright and Similar Rights. - - -Section 5 -- Disclaimer of Warranties and Limitation of Liability. - - a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE - EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS - AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF - ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, - IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, - WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR - PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, - ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT - KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT - ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. - - b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE - TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, - NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, - INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, - COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR - USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN - ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR - DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR - IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. - - c. The disclaimer of warranties and limitation of liability provided - above shall be interpreted in a manner that, to the extent - possible, most closely approximates an absolute disclaimer and - waiver of all liability. - - -Section 6 -- Term and Termination. - - a. This Public License applies for the term of the Copyright and - Similar Rights licensed here. However, if You fail to comply with - this Public License, then Your rights under this Public License - terminate automatically. - - b. Where Your right to use the Licensed Material has terminated under - Section 6(a), it reinstates: - - 1. automatically as of the date the violation is cured, provided - it is cured within 30 days of Your discovery of the - violation; or - - 2. upon express reinstatement by the Licensor. - - For the avoidance of doubt, this Section 6(b) does not affect any - right the Licensor may have to seek remedies for Your violations - of this Public License. - - c. For the avoidance of doubt, the Licensor may also offer the - Licensed Material under separate terms or conditions or stop - distributing the Licensed Material at any time; however, doing so - will not terminate this Public License. - - d. Sections 1, 5, 6, 7, and 8 survive termination of this Public - License. - - -Section 7 -- Other Terms and Conditions. - - a. The Licensor shall not be bound by any additional or different - terms or conditions communicated by You unless expressly agreed. - - b. Any arrangements, understandings, or agreements regarding the - Licensed Material not stated herein are separate from and - independent of the terms and conditions of this Public License. - - -Section 8 -- Interpretation. - - a. For the avoidance of doubt, this Public License does not, and - shall not be interpreted to, reduce, limit, restrict, or impose - conditions on any use of the Licensed Material that could lawfully - be made without permission under this Public License. - - b. To the extent possible, if any provision of this Public License is - deemed unenforceable, it shall be automatically reformed to the - minimum extent necessary to make it enforceable. If the provision - cannot be reformed, it shall be severed from this Public License - without affecting the enforceability of the remaining terms and - conditions. - - c. No term or condition of this Public License will be waived and no - failure to comply consented to unless expressly agreed to by the - Licensor. - - d. Nothing in this Public License constitutes or may be interpreted - as a limitation upon, or waiver of, any privileges and immunities - that apply to the Licensor or You, including from the legal - processes of any jurisdiction or authority. - -======================================================================= - -Creative Commons is not a party to its public -licenses. Notwithstanding, Creative Commons may elect to apply one of -its public licenses to material it publishes and in those instances -will be considered the “Licensor.” The text of the Creative Commons -public licenses is dedicated to the public domain under the CC0 Public -Domain Dedication. Except for the limited purpose of indicating that -material is shared under a Creative Commons public license or as -otherwise permitted by the Creative Commons policies published at -creativecommons.org/policies, Creative Commons does not authorize the -use of the trademark "Creative Commons" or any other trademark or logo -of Creative Commons without its prior written consent including, -without limitation, in connection with any unauthorized modifications -to any of its public licenses or any other arrangements, -understandings, or agreements concerning use of licensed material. For -the avoidance of doubt, this paragraph does not form part of the -public licenses. - -Creative Commons may be contacted at creativecommons.org. diff --git a/Makefile b/Makefile deleted file mode 100644 index 456fc8a435..0000000000 --- a/Makefile +++ /dev/null @@ -1,125 +0,0 @@ -# MakeFile for building all the docs at once. -# Inspired by the Makefile used by bazaar. -# https://bazaar.launchpad.net/~bzr-pqm/bzr/2.3/ - -PYTHON = python3 -ES_HOST = -ES_HOST_V2 = - -.PHONY: all clean html latexpdf epub htmlhelp website website-dirs rebuild-index - -# Languages that can be built. -LANGS = en es fr ja pt - -# pdflatex does not like ja, zh & tr for some reason. -PDF_LANGS = en es fr pt - -DEST = website - -EPUB_ARGS = -SPHINXOPTS = - -# Get path to theme directory to build static assets. -THEME_DIR = $(shell python3 -c 'import os, cakephpsphinx; print(os.path.abspath(os.path.dirname(cakephpsphinx.__file__)))') - -# -# The various formats the documentation can be created in. -# -# Loop over the possible languages and call other build targets. -# -html: $(foreach lang, $(LANGS), html-$(lang)) -htmlhelp: $(foreach lang, $(LANGS), htmlhelp-$(lang)) -epub: $(foreach lang, $(LANGS), epub-$(lang)) -latex: $(foreach lang, $(PDF_LANGS), latex-$(lang)) -pdf: $(foreach lang, $(PDF_LANGS), pdf-$(lang)) -htmlhelp: $(foreach lang, $(LANGS), htmlhelp-$(lang)) -server: $(foreach lang, $(LANGS), server-$(lang)) -rebuild-index: $(foreach lang, $(LANGS), rebuild-index-$(lang)) - - -# Make the HTML version of the documentation with correctly nested language folders. -html-%: - cd $* && make html SPHINXOPTS="$(SPHINXOPTS)" - make build/html/$*/_static/css/dist.css - make build/html/$*/_static/js/dist.js - -htmlhelp-%: - cd $* && make htmlhelp - -epub-%: - cd $* && make epub - -latex-%: - cd $* && make latex - -pdf-%: - cd $* && make latexpdf - -server-%: - cd build/html/$* && python3 -m SimpleHTTPServer - -epub-check-%: build/epub/$* - java -jar /epubcheck/epubcheck.jar build/epub/$*/CakePHP.epub $(EPUB_ARGS) - -website-dirs: - # Make the directory if its not there already. - [ ! -d $(DEST) ] && mkdir $(DEST) || true - - # Make the downloads directory - [ ! -d $(DEST)/_downloads ] && mkdir $(DEST)/_downloads || true - - # Make downloads for each language - $(foreach lang, $(LANGS), [ ! -d $(DEST)/_downloads/$(lang) ] && mkdir $(DEST)/_downloads/$(lang) || true;) - -website: website-dirs html epub pdf - # Move HTML - $(foreach lang, $(LANGS), cp -r build/html/$(lang) $(DEST)/$(lang);) - - # Move EPUB files - $(foreach lang, $(LANGS), cp -r build/epub/$(lang)/*.epub $(DEST)/_downloads/$(lang) || true;) - - # Move PDF files - $(foreach lang, $(PDF_LANGS), [ -f build/latex/$(lang)/*.pdf ] && cp -r build/latex/$(lang)/*.pdf $(DEST)/_downloads/$(lang) || true;) - -clean: - rm -rf build/* - -clean-website: - rm -rf $(DEST)/* - -build/html/%/_static: - mkdir -p build/html/$*/_static - -build/html/%/_static/css: build/html/%/_static - mkdir -p build/html/$*/_static/css - -build/html/%/_static/js: build/html/%/_static - mkdir -p build/html/$*/_static/js - -CSS_FILES = $(THEME_DIR)/themes/cakephp/static/css/fonts.css \ - $(THEME_DIR)/themes/cakephp/static/css/bootstrap.min.css \ - $(THEME_DIR)/themes/cakephp/static/css/font-awesome.min.css \ - $(THEME_DIR)/themes/cakephp/static/css/style.css \ - $(THEME_DIR)/themes/cakephp/static/css/default.css \ - $(THEME_DIR)/themes/cakephp/static/css/pygments.css \ - $(THEME_DIR)/themes/cakephp/static/css/responsive.css - -build/html/%/_static/css/dist.css: build/html/%/_static/css $(CSS_FILES) - # build css dependencies for distribution into '$@' - cat $(CSS_FILES) > $@ - -JS_FILES = $(THEME_DIR)/themes/cakephp/static/js/vendor.js \ - $(THEME_DIR)/themes/cakephp/static/js/app.js \ - $(THEME_DIR)/themes/cakephp/static/js/messages.js \ - $(THEME_DIR)/themes/cakephp/static/js/common.js \ - $(THEME_DIR)/themes/cakephp/static/js/responsive-menus.js \ - $(THEME_DIR)/themes/cakephp/static/js/mega-menu.js \ - $(THEME_DIR)/themes/cakephp/static/js/header.js \ - $(THEME_DIR)/themes/cakephp/static/js/search.js \ - $(THEME_DIR)/themes/cakephp/static/js/search.messages.*.js \ - $(THEME_DIR)/themes/cakephp/static/js/inline-search.js \ - $(THEME_DIR)/themes/cakephp/static/js/standalone-search.js - -build/html/%/_static/js/dist.js: build/html/%/_static/js $(JS_FILES) - # build js dependencies for distribution into '$@' - cat $(JS_FILES) > $@ diff --git a/README.md b/README.md index 312c67b6c3..affdffdede 100644 --- a/README.md +++ b/README.md @@ -2,148 +2,97 @@ CakePHP Documentation ===================== [![License: CC BY-NC-SA 4.0](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgreen.svg)](https://creativecommons.org/licenses/by-nc-sa/4.0/) -[![Build Status](https://github.com/cakephp/docs/actions/workflows/ci.yml/badge.svg?branch=4.x)](https://github.com/cakephp/docs/actions/workflows/ci.yml) +[![Documentation Validation](https://github.com/cakephp/docs/actions/workflows/docs-validation.yml/badge.svg)](https://github.com/cakephp/docs/actions/workflows/docs-validation.yml) +[![Build Status](https://github.com/cakephp/docs/actions/workflows/deploy_5.yml/badge.svg?branch=5.x)](https://github.com/cakephp/docs/actions/workflows/deploy_5.yml) This is the official documentation for the CakePHP project. It is available -online in HTML, PDF and EPUB formats at https://book.cakephp.org. +online at https://book.cakephp.org. Contributing to the documentation is pretty simple. Please read the documentation on contributing to the documentation over on [the cookbook](https://book.cakephp.org/5/en/contributing/documentation.html) for help. You can read all the documentation within as it is just in plain text -files, marked up with ReST text formatting. +files, marked up with Markdown formatting. -There are two ways for building the documentation: with Docker, or by installing -the packages directly on your OS. +## Local Development -Build the Documentation with Docker ------------------------------------ +For working with the documentation markdown files locally, use the provided development server script: -Docker will let you create a container with all packages needed to build the -docs. You need to have docker installed, see the [official docs of -docker](https://docs.docker.com/desktop/) for more information. +```bash +./bin/dev-server.sh +``` -### Build the image locally ### +This script will: +- Set up a clean `.temp` working directory +- Clone the VitePress skeleton repository +- Sync your documentation files +- Install dependencies +- Start a local development server with hot-reload -Starting in the top-level directory, you can build the provided `Dockerfile` -and tag it with the name `cakephp/docs` by running: +The documentation will be available at `http://localhost:5173` + +### Development Server Options ```bash -docker build -t cakephp/docs . +# Start on a custom port +./bin/dev-server.sh --port 3000 + +# Adjust docs sync interval (default: 1 second) +./bin/dev-server.sh --sync-interval 2 ``` -This can take a little while, because all packages needs to be downloaded, but -you'll only need to do this once. +### Prerequisites -Now that the image is built, you can run all the commands to build the docs: +The development server requires: +- `git` - Version control +- `node` - JavaScript runtime +- `npm` - Package manager +- `rsync` - File synchronization -##### To build the html: ##### -```bash -docker run -it --rm -v $(pwd):/data cakephp/docs make html -``` -##### To build the epub: ##### -```bash -docker run -it --rm -v $(pwd):/data cakephp/docs make epub -``` -##### To build the latex: ##### -```bash -docker run -it --rm -v $(pwd):/data cakephp/docs make latex -``` -##### To build the pdf: ##### -```bash -docker run -it --rm -v $(pwd):/data cakephp/docs make pdf -``` +Press `Ctrl+C` to stop the development server. -All the documentation will output to the `build` directory. +## Build the Documentation with Docker -Build the Documentation Manually --------------------------------- +Docker will let you create a container with all packages needed to build the +docs. You need to have docker installed, see the [official docs of +docker](https://docs.docker.com/desktop/) for more information. -### Installing the needed Packages ### +### Build the image locally -To build the documentation you'll need to install dependencies using: +Starting in the top-level directory, you can build the provided `Dockerfile` +and tag it with the name `cakephp/docs` by running: ```bash -pip install -r requirements.txt +docker build -f Dockerfile -t cakephp/docs . ``` -*To run the pip command, python-pip package must be previously installed.* - -### Building the Documentation ### +This can take a little while, because all packages needs to be downloaded, but +you'll only need to do this once. -After installing the required packages, you can build the documentation using -`make`. +Now that the image is built, you can run the commands to build the docs: -##### Create all the HTML docs. Including all the languages: ##### +##### To build the static site: ```bash -make html -``` - ##### Create just the English HTML docs: ##### -```bash -make html-en +docker build --progress=plain --no-cache -f Dockerfile -t cake-docs . ``` -##### Create all the EPUB (e-book) docs: ##### -```bash -make epub -``` -##### Create just the English EPUB docs: ##### +##### To run the development server: ```bash -make epub-en +docker run -d -p 8080:80 --name cakedocs cake-vitepress ``` -After making changes to the documentation, you can build the HTML version of the -docs by using `make html` again. This will build only the HTML files that have -had changes made to them. - -### Building the PDF ### - -Building the PDF is a non-trivial task. - -1. Install LaTeX - This varies by distribution/OS so refer to your package - manager. You should install the full LaTeX package. The basic one requires - any additional packages to be installed with `tlmgr` -2. Run `make latex-en`. -3. Run `make pdf-en`. - -At this point the completed PDF should be in `build/latex/en/CakePHPBook.pdf`. +The built documentation will output to the `.vitepress/dist` directory. -Contributing ------------- +## Contributing -There are currently a number of outstanding issues that need to be addressed. -We've tried to flag these with `.. todo::` where possible. To see all the -outstanding todo's add the following to your `config/all.py` - -```python -todo_include_todos = True -``` -After rebuilding the HTML content, you should see a list of existing todo items -at the bottom of the table of contents. - -You are also welcome to make and suggestions for new content as commits in a +You are welcome to make suggestions for new content as commits in a GitHub fork. Please make any totally new sections in a separate branch. This makes changes far easier to integrate later on. -Translations ------------- - -Contributing translations requires that you make a new directory using the two -letter name for your language. As content is translated, directories mirroring -the English content should be created with localized content. For more info, -please, -[click here](https://book.cakephp.org/3/en/contributing/documentation.html#new-translation-language). - -Making Search Work Locally --------------------------- +The documentation is written in Markdown and uses VitePress for static site generation. +All documentation files are located in the `docs/` directory, organized by version. -* Install elasticsearch. This varies based on your platform, but most - package managers have a package for it. -* Clone the [docs_search](https://github.com/cakephp/docs_search) into a - web accessible directory. -* Modify `searchUrl` in `themes/cakephp/static/app.js` to point at the - baseurl for your docs_search clone. -* Start elasticsearch with the default configuration. -* Populate the search using tooling found in the [cakephp docs builder](https://github.com/cakephp/docs-builder) project. -* You should now be able to search the docs using elasticsearch. +## Search Functionality +The documentation includes built-in search functionality powered by VitePress's local search feature. +Search works automatically in both development and production builds without requiring any additional setup. diff --git a/bin/check-links.js b/bin/check-links.js new file mode 100755 index 0000000000..f30abad3d4 --- /dev/null +++ b/bin/check-links.js @@ -0,0 +1,459 @@ +#!/usr/bin/env node + +/** + * Internal Markdown Link Checker + * + * Validates internal markdown links in documentation files. + * Only checks relative links - ignores external URLs. + * + * Usage: + * node bin/check-links.js ... + * node bin/check-links.js --baseline "docs/subfolder/*.md" + * node bin/check-links.js --generate-baseline "docs/subfolder/*.md" + */ + +const fs = require('fs'); +const path = require('path'); + +const BASELINE_FILE = '.github/linkchecker-baseline.json'; + +/** + * Simple glob implementation using Node.js built-ins + */ +function globSync(pattern, options = {}) { + const results = []; + + // Handle simple patterns like "docs/en/**/*.md" + if (pattern.includes('**')) { + const [basePath, ...rest] = pattern.split('**'); + const suffix = rest.join('**').replace(/^\//, ''); // Remove leading slash + const baseDir = basePath.replace(/\/$/, '') || '.'; + + function traverse(dir) { + try { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + traverse(fullPath); + } else if (entry.isFile() && matchPattern(entry.name, suffix)) { + results.push(options.absolute ? path.resolve(fullPath) : fullPath); + } + } + } catch (err) { + // Ignore permission errors + } + } + + traverse(baseDir); + } else { + // Simple wildcard pattern like "docs/en/*.md" + const dir = path.dirname(pattern); + const filePattern = path.basename(pattern); + + if (fs.existsSync(dir)) { + const entries = fs.readdirSync(dir); + for (const entry of entries) { + const fullPath = path.join(dir, entry); + if (fs.statSync(fullPath).isFile() && matchPattern(entry, filePattern)) { + results.push(options.absolute ? path.resolve(fullPath) : fullPath); + } + } + } + } + + return results; +} + +function matchPattern(filename, pattern) { + const regex = pattern + .replace(/\./g, '\\.') + .replace(/\*/g, '.*') + .replace(/\?/g, '.'); + return new RegExp(`^${regex}$`).test(filename) || + new RegExp(regex).test(filename); +} + +class LinkChecker { + constructor(options = {}) { + this.errors = []; + this.checked = new Set(); + this.headingCache = new Map(); + this.generateBaseline = options.generateBaseline || false; + this.baselinePath = options.baselinePath || null; + this.baseline = this.baselinePath ? this.loadBaseline() : []; + } + + /** + * Load baseline configuration + */ + loadBaseline() { + if (!fs.existsSync(this.baselinePath)) { + console.warn(`Warning: Baseline file not found: ${this.baselinePath}`); + return []; + } + + try { + const content = fs.readFileSync(this.baselinePath, 'utf8'); + return JSON.parse(content); + } catch (err) { + console.warn(`Warning: Could not load baseline file: ${err.message}`); + return []; + } + } + + /** + * Save baseline configuration + */ + saveBaseline() { + const baselineData = this.errors.map(error => ({ + file: error.file, + link: error.link, + message: error.message + })); + + const outputPath = this.baselinePath || BASELINE_FILE; + const dir = path.dirname(outputPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + fs.writeFileSync( + outputPath, + JSON.stringify(baselineData, null, 2) + '\n', + 'utf8' + ); + } + + /** + * Check if an error matches baseline + */ + isInBaseline(error) { + return this.baseline.some(baselineError => + baselineError.file === error.file && + baselineError.link === error.link + ); + } + + /** + * Main entry point - check files matching patterns + */ + async checkFiles(patterns) { + const files = await this.resolveFiles(patterns); + + if (files.length === 0) { + console.error('No files found matching patterns'); + return false; + } + + console.log(`Checking ${files.length} file(s)...\n`); + + for (const file of files) { + this.checkFile(file); + } + + return this.reportResults(); + } + + /** + * Resolve glob patterns to actual file paths + */ + async resolveFiles(patterns) { + const fileSet = new Set(); + + for (const pattern of patterns) { + // Check if it's a direct file path + if (fs.existsSync(pattern) && fs.statSync(pattern).isFile()) { + fileSet.add(path.resolve(pattern)); + } else { + // Treat as glob pattern + const matches = globSync(pattern, { + absolute: true + }); + matches.forEach(f => fileSet.add(f)); + } + } + + return Array.from(fileSet).sort(); + } + + /** + * Check all links in a single file + */ + checkFile(filePath) { + if (this.checked.has(filePath)) { + return; + } + this.checked.add(filePath); + + const content = fs.readFileSync(filePath, 'utf8'); + const links = this.extractLinks(content); + const sourceDir = path.dirname(filePath); + + for (const link of links) { + if (this.isExternalLink(link.url)) { + continue; + } + + const { targetPath, anchor } = this.parseLink(link.url, sourceDir); + + // Check file exists + if (!fs.existsSync(targetPath)) { + this.addError(filePath, link, `File not found: ${path.relative(process.cwd(), targetPath)}`); + continue; + } + + // Check anchor if present + if (anchor) { + const headings = this.getHeadings(targetPath); + const anchorId = this.slugify(anchor); + + if (!headings.includes(anchorId)) { + this.addError( + filePath, + link, + `Anchor '#${anchor}' not found in ${path.relative(process.cwd(), targetPath)}` + ); + } + } + } + } + + /** + * Extract all markdown links from content + */ + extractLinks(content) { + const links = []; + + // Remove code blocks first (fenced with ```), then inline code (single backticks) + let contentWithoutCodeBlocks = content.replace(/```[\s\S]*?```/g, ''); + // For inline code, only match within a single line (no newlines) + contentWithoutCodeBlocks = contentWithoutCodeBlocks.replace(/`[^`\n]+`/g, ''); + + // Match [text](url) or [text](url "title") + const linkRegex = /\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g; + let match; + + while ((match = linkRegex.exec(contentWithoutCodeBlocks)) !== null) { + links.push({ + text: match[1], + url: match[2], + raw: match[0] + }); + } + + return links; + } + + /** + * Check if a URL is external + */ + isExternalLink(url) { + // Skip HTTP(S), mailto, IRC, anchor-only, protocol-relative, and absolute paths (VitePress public dir) + return /^(https?:\/\/|mailto:|irc:\/\/|#|\/\/|\/[^.])/i.test(url); + } + + /** + * Parse link URL into target path and anchor + */ + parseLink(url, sourceDir) { + const [pathPart, anchor] = url.split('#'); + + // Handle anchor-only links (same file) + if (!pathPart) { + return { + targetPath: path.join(sourceDir, path.basename(sourceDir) + '.md'), + anchor + }; + } + + let targetPath = path.resolve(sourceDir, pathPart); + + // VitePress automatically adds .md extension if not present + // Match this behavior by always trying .md first for extensionless links + if (!path.extname(targetPath)) { + const mdPath = targetPath + '.md'; + if (fs.existsSync(mdPath)) { + targetPath = mdPath; + } else if (fs.existsSync(targetPath) && fs.statSync(targetPath).isDirectory()) { + // If it's a directory, look for index.md + targetPath = path.join(targetPath, 'index.md'); + } else { + // Default to .md extension (VitePress behavior) + targetPath = mdPath; + } + } else if (fs.existsSync(targetPath) && fs.statSync(targetPath).isDirectory()) { + // If path is directory, look for index.md + targetPath = path.join(targetPath, 'index.md'); + } + + return { targetPath, anchor }; + } + + /** + * Extract headings from a markdown file + */ + getHeadings(filePath) { + if (this.headingCache.has(filePath)) { + return this.headingCache.get(filePath); + } + + const content = fs.readFileSync(filePath, 'utf8'); + const headings = []; + + // Match ATX headings: # Heading + const headingRegex = /^#{1,6}\s+(.+)$/gm; + let match; + + while ((match = headingRegex.exec(content)) !== null) { + const heading = match[1] + .replace(/\{#([^}]+)\}$/g, '') // Remove {#custom-id} but keep the ID + .replace(/\[[^\]]+\]\([^)]+\)/g, '') // Remove links + .replace(/`([^`]+)`/g, '$1') // Remove code formatting + .trim(); + + // Check if there's a custom ID in the original match + const customIdMatch = match[1].match(/\{#([^}]+)\}$/); + if (customIdMatch) { + headings.push(customIdMatch[1]); // Add the custom ID + } + + headings.push(this.slugify(heading)); + } + + // Also extract HTML anchor IDs: + const htmlAnchorRegex = / !this.isInBaseline(error)); + + if (newErrors.length === 0 && this.errors.length === 0) { + console.log('✓ All internal links are valid!\n'); + return true; + } + + if (newErrors.length === 0 && this.errors.length > 0) { + console.log(`✓ No new broken links (${this.errors.length} known issue(s) in baseline)\n`); + return true; + } + + console.error(`✗ Found ${newErrors.length} new broken link(s):\n`); + + for (const error of newErrors) { + console.error(` ${error.file}`); + console.error(` Link: [${error.text}](${error.link})`); + console.error(` Error: ${error.message}`); + console.error(''); + } + + if (this.errors.length > newErrors.length) { + const baselineCount = this.errors.length - newErrors.length; + console.error(` (${baselineCount} known issue(s) ignored from baseline)\n`); + } + + return false; + } +} + +// Main execution +async function main() { + const args = process.argv.slice(2); + + // Parse arguments + let generateBaseline = false; + let baselinePath = null; + const patterns = []; + + for (let i = 0; i < args.length; i++) { + if (args[i] === '--generate-baseline') { + generateBaseline = true; + } else if (args[i] === '--baseline') { + if (i + 1 < args.length) { + baselinePath = args[++i]; + } else { + console.error('Error: --baseline requires a path argument'); + process.exit(1); + } + } else { + patterns.push(args[i]); + } + } + + if (patterns.length === 0) { + console.error('Usage: node bin/check-links.js [--baseline ] [--generate-baseline] ...'); + console.error(''); + console.error('Examples:'); + console.error(' node bin/check-links.js docs/en/installation.md'); + console.error(' node bin/check-links.js "docs/**/*.md"'); + console.error(' node bin/check-links.js docs/en/*.md docs/ja/*.md'); + console.error(''); + console.error('With baseline:'); + console.error(' node bin/check-links.js --baseline .github/linkchecker-baseline.json "docs/**/*.md"'); + console.error(''); + console.error('Generate baseline:'); + console.error(' node bin/check-links.js --generate-baseline "docs/**/*.md"'); + console.error(' node bin/check-links.js --generate-baseline --baseline custom-baseline.json "docs/**/*.md"'); + process.exit(1); + } + + const checker = new LinkChecker({ generateBaseline, baselinePath }); + const success = await checker.checkFiles(patterns); + + process.exit(success ? 0 : 1); +} + +// Run if executed directly +if (require.main === module) { + main().catch(error => { + console.error('Error:', error.message); + process.exit(1); + }); +} + +module.exports = LinkChecker; diff --git a/bin/dev-server.sh b/bin/dev-server.sh new file mode 100755 index 0000000000..9423fa1a1f --- /dev/null +++ b/bin/dev-server.sh @@ -0,0 +1,319 @@ +#!/bin/bash + +# ============================================================================== +# CakePHP vitepress docs dev server orchestration script +# ------------------------------------------------------------------------------ +# This script orchestrates the VitePress development environment by: +# 1. Cleaning and setting up .temp directory +# 2. Cloning the cakephp-docs-skeleton repository +# 3. Syncing docs folder and symlinking config files +# 4. Installing dependencies +# 5. Starting the VitePress dev server with live sync +# ------------------------------------------------------------------------------ +# Usage: ./dev-server.sh [OPTIONS] +# --port PORT Specify the port (default: 5173) +# --sync-interval N Rsync interval in seconds (default: 1) +# --skeleton-path PATH Use local skeleton repo instead of cloning +# --help Show this help message +# ============================================================================== + +set -e # Exit on error + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TEMP_DIR="${PROJECT_ROOT}/.temp" +SKELETON_REPO="https://github.com/cakephp/docs-skeleton.git" +SKELETON_PATH="" +DEV_PORT=5173 +SYNC_INTERVAL=1 +RSYNC_PID="" + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --port) + DEV_PORT="$2" + shift 2 + ;; + --sync-interval) + SYNC_INTERVAL="$2" + shift 2 + ;; + --skeleton-path) + SKELETON_PATH="$2" + shift 2 + ;; + -h|--help) + head -n 17 "$0" | tail -n 14 | sed 's/^# //' + exit 0 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +# Helper functions +log_info() { + echo -e "${BLUE}ℹ ${1}${NC}" +} + +log_success() { + echo -e "${GREEN}✓ ${1}${NC}" +} + +log_warn() { + echo -e "${YELLOW}⚠ ${1}${NC}" +} + +log_error() { + echo -e "${RED}✗ ${1}${NC}" +} + +# Check prerequisites +check_prerequisites() { + log_info "Checking prerequisites..." + + local missing_tools=() + + if ! command -v git &> /dev/null; then + missing_tools+=("git") + fi + + if ! command -v node &> /dev/null; then + missing_tools+=("node") + fi + + if ! command -v npm &> /dev/null; then + missing_tools+=("npm") + fi + + if ! command -v rsync &> /dev/null; then + missing_tools+=("rsync") + fi + + if [ ${#missing_tools[@]} -gt 0 ]; then + log_error "Missing required tools: ${missing_tools[*]}" + exit 1 + fi + + log_success "All prerequisites installed" + echo " - node: $(node --version)" + echo " - npm: $(npm --version)" + echo " - rsync: $(rsync --version | head -1 | awk '{print $3}')" +} + +# Setup .temp directory +setup_temp_dir() { + if [ -d "$TEMP_DIR" ]; then + log_info "Removing existing .temp directory..." + rm -rf "$TEMP_DIR" + fi + + log_info "Creating .temp directory..." + mkdir -p "$TEMP_DIR" + log_success ".temp directory ready" +} + +# Clone or copy skeleton repository +clone_skeleton() { + if [ -n "$SKELETON_PATH" ]; then + # Convert to absolute path + local abs_skeleton_path + abs_skeleton_path="$(cd "$SKELETON_PATH" && pwd)" + + log_info "Copying local skeleton from: $abs_skeleton_path" + + if [ ! -d "$abs_skeleton_path" ]; then + log_error "Skeleton path not found: $abs_skeleton_path" + exit 1 + fi + + # Copy all files and directories from skeleton to .temp (excluding docs, node_modules, .git) + rsync -a \ + --exclude='docs/' \ + --exclude='node_modules/' \ + --exclude='.git/' \ + --exclude='package-lock.json' \ + "$abs_skeleton_path/" "$TEMP_DIR/" + + log_success "Local skeleton copied" + else + log_info "Cloning docs-skeleton repository..." + git clone --depth 1 "$SKELETON_REPO" "$TEMP_DIR" 2>&1 | grep -E "(Cloning|Resolving|Receiving)" || true + log_success "Skeleton repository cloned" + fi +} + +# Initial sync of docs folder +sync_docs_initial() { + log_info "Syncing docs folder..." + + local src="${PROJECT_ROOT}/docs/" + local dest="${TEMP_DIR}/docs/" + + if [ ! -d "$src" ]; then + log_error "Source docs directory not found: $src" + exit 1 + fi + + # Ensure destination directory exists + mkdir -p "$dest" + + # Initial sync + rsync -az "$src" "$dest" + log_success "Docs folder synced" +} + +# Start background rsync watcher +start_rsync_watcher() { + log_info "Starting docs sync watcher..." + + local src="${PROJECT_ROOT}/docs/" + local dest="${TEMP_DIR}/docs/" + + # Start rsync in background loop + ( + while true; do + rsync -az "$src" "$dest" 2>/dev/null + sleep "$SYNC_INTERVAL" + done + ) & + + RSYNC_PID=$! + log_success "Docs sync watcher started (PID: $RSYNC_PID)" +} + +# Create symlinks for config files +create_symlinks() { + log_info "Creating symlinks for config files..." + + # List of files to symlink (not docs, which is synced) + local items=( + "config.js" + "toc_en.json" + "toc_ja.json" + ) + + for item in "${items[@]}"; do + local src="${PROJECT_ROOT}/${item}" + local dest="${TEMP_DIR}/${item}" + + if [ ! -e "$src" ]; then + log_warn "Source not found: $src (skipping)" + continue + fi + + # Remove existing file/symlink if it exists + if [ -e "$dest" ] || [ -L "$dest" ]; then + rm -rf "$dest" + fi + + ln -s "$src" "$dest" + log_success "Symlinked: $item" + done +} + +# Install dependencies +install_dependencies() { + log_info "Installing VitePress dependencies..." + + cd "$TEMP_DIR" + npm install --quiet + log_success "Dependencies installed" +} + +# Display start information +show_startup_info() { + echo "" + echo -e "${GREEN}════════════════════════════════════════════════════════${NC}" + echo -e "${GREEN} VitePress Dev Server Ready${NC}" + echo -e "${GREEN}════════════════════════════════════════════════════════${NC}" + echo "" + echo " 📂 Project Root: ${PROJECT_ROOT}" + echo " 📂 Temp Directory: ${TEMP_DIR}" + if [ -n "$SKELETON_PATH" ]; then + echo " 📦 Skeleton Source: ${SKELETON_PATH} (local)" + else + echo " 📦 Skeleton Source: ${SKELETON_REPO} (remote)" + fi + echo " 🔗 Port: ${DEV_PORT}" + echo " 🔄 Docs Sync: Active (rsync every ${SYNC_INTERVAL}s)" + echo "" + echo " 📚 Documentation: http://localhost:${DEV_PORT}" + echo "" + echo " ℹ️ Docs folder syncs every ${SYNC_INTERVAL}s via rsync" + echo " ℹ️ Config files (config.js, toc_*.json) are symlinked" + echo "" + echo " To stop the server: Press Ctrl+C" + echo "" + echo -e "${GREEN}════════════════════════════════════════════════════════${NC}" + echo "" +} + +# Start dev server +start_dev_server() { + log_info "Starting VitePress dev server on port ${DEV_PORT}..." + log_warn "Please wait, VitePress server may take a moment to start up..." + cd "$TEMP_DIR" + npm run docs:dev -- --port "$DEV_PORT" +} + +# Cleanup on exit +cleanup_on_exit() { + echo "" + log_info "Shutting down..." + + # Kill rsync watcher if running + if [ -n "$RSYNC_PID" ] && kill -0 "$RSYNC_PID" 2>/dev/null; then + log_info "Stopping docs sync watcher (PID: $RSYNC_PID)..." + kill "$RSYNC_PID" 2>/dev/null || true + fi + + log_success "Dev server stopped" +} + +trap cleanup_on_exit EXIT INT TERM + +# Main execution +main() { + echo "" + log_info "VitePress Dev Server Setup" + log_info "================================" + echo "" + + check_prerequisites + echo "" + + setup_temp_dir + echo "" + + clone_skeleton + echo "" + + sync_docs_initial + echo "" + + create_symlinks + echo "" + + install_dependencies + echo "" + + start_rsync_watcher + echo "" + + show_startup_info + + start_dev_server +} + +main "$@" diff --git a/config.js b/config.js new file mode 100644 index 0000000000..6170bf1495 --- /dev/null +++ b/config.js @@ -0,0 +1,69 @@ +import { createRequire } from "module"; +const require = createRequire(import.meta.url); +const toc_en = require("./toc_en.json"); +const toc_ja = require("./toc_ja.json"); + +const versions = { + text: "5.x", + items: [ + { text: "5.x (latest)", link: "https://book.cakephp.org/5.x/", target: '_self' }, + { text: "4.x", link: "https://book.cakephp.org/4.x/", target: '_self' }, + { text: "3.x", link: "https://book.cakephp.org/3.x/", target: '_self' }, + { text: "2.x", link: "https://book.cakephp.org/2.x/", target: '_self' }, + { text: "Next releases", items: [ + { text: "5.next", link: "https://book.cakephp.org/5.next/", target: '_self' }, + { text: "6.x", link: "https://book.cakephp.org/6.x/", target: '_self' }, + ]}, + ], +}; + +// This file contains overrides for .vitepress/config.js +export default { + base: "/5.x/", + rewrites: { + "en/:slug*": ":slug*", + }, + sitemap: { + hostname: "https://book.cakephp.org/5.x/", + }, + themeConfig: { + socialLinks: [ + { icon: "github", link: "https://github.com/cakephp/cakephp" }, + ], + editLink: { + pattern: "https://github.com/cakephp/docs/edit/5.x/docs/:path", + text: "Edit this page on GitHub", + }, + sidebar: toc_en, + nav: [ + { text: "Guide", link: "/intro" }, + { text: "API", link: "https://api.cakephp.org/" }, + { text: "Documentation", link: "/" }, + { ...versions }, + ], + }, + substitutions: { + '|phpversion|': { value: '8.5', format: 'bold' }, + '|minphpversion|': { value: '8.2', format: 'italic' }, + '|cakeversion|': '5.3', + }, + locales: { + root: { + label: "English", + lang: "en", + }, + ja: { + label: "Japanese", + lang: "ja", + themeConfig: { + nav: [ + { text: "ガイド", link: "/ja/intro" }, + { text: "API", link: "https://api.cakephp.org/" }, + { text: "ドキュメント", link: "/ja/" }, + { ...versions }, + ], + sidebar: toc_ja, + }, + }, + }, +}; diff --git a/config/__init__.py b/config/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/config/conf.py b/config/conf.py deleted file mode 100644 index eea5246b1b..0000000000 --- a/config/conf.py +++ /dev/null @@ -1,123 +0,0 @@ -import os -# Global configuration information used across all the -# translations of documentation. -# -# Import the base theme configuration -from cakephpsphinx.config.all import * - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '5.x' - -# The full version, including alpha/beta/rc tags. -release = '5.x' - -# The search index version. -search_version = '5' - -# The marketing diplay name for the book. -version_name = 'Chiffon' - -# Other versions that display in the version picker menu. -version_list = [ - {'name': '5.x', 'number': '5', 'current': True, 'title': '5.x Book'}, - {'name': '4.x', 'number': '4', 'title': '4.x Book'}, - {'name': '3.x', 'number': '3', 'title': '3.x Book'}, - {'name': '2.x', 'number': '2', 'title': '2.x Book'}, - {'name': '1.3', 'number': '1.3', 'title': '1.3 Book'}, - {'name': '1.2', 'number': '1.2', 'title': '1.2 Book'}, - {'name': '1.1', 'number': '1.1', 'title': '1.1 Book'}, -] -# Enables the 'development version banner' -is_prerelease = False - -# Languages available. -languages = ['en', 'pt_BR', 'es', 'ja', 'fr'] - -# The GitHub branch name for this version of the docs -# for edit links to point at. -branch = '5.x' - -# Add any paths that contain custom themes here, relative to this directory. -html_theme_path = [] -html_theme = 'cakephp' - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -html_last_updated_fmt = '%b %d, %Y' - -# Custom sidebar templates, maps document names to template names. -html_sidebars = { - '**': ['globaltoc.html'] -} - -language = os.getenv('LANG') or 'en' -html_use_opensearch = 'https://book.cakephp.org/' + version + '/' + language - -# -- Options for LaTeX output ------------------------------------------------ - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, -# documentclass [howto/manual]). -latex_documents = [ - ('pdf-contents', 'CakePHPBook.tex', u'CakePHP Book', - u'Cake Software Foundation', 'manual'), -] - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'cakephpbook', u'CakePHP Book', - [u'CakePHP'], 1) -] - - -# -- Options for Epub output ------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = u'CakePHP Book' -epub_author = u'Cake Software Foundation, Inc.' -epub_publisher = u'Cake Software Foundation, Inc.' -epub_copyright = u'%d, Cake Software Foundation, Inc.' % datetime.datetime.now().year - -epub_theme = 'cakephp-epub' - -# The cover page information. -epub_cover = ('_static/epub-logo.png', 'epub-cover.html') - -# The scheme of the identifier. Typical schemes are ISBN or URL. -epub_scheme = 'URL' - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -epub_identifier = 'https://cakephp.org' - -# A unique identification for the text. -epub_uid = 'cakephpbook1393624653' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = [ - 'index.html', - 'pdf-contents.html', - 'search.html', - 'contents.html' -] - -# The depth of the table of contents in toc.ncx. -epub_tocdepth = 2 - -rst_epilog = """ -.. |phpversion| replace:: **8.4** -.. |minphpversion| replace:: 8.1 -""" - -# todo_include_todos = True - -# turn off contents entries for classes/functions -# generally we add titles for methods and classes instead. -toc_object_entries = False diff --git a/deploy.Dockerfile b/deploy.Dockerfile deleted file mode 100644 index 6ae8fb66a4..0000000000 --- a/deploy.Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM ghcr.io/cakephp/docs-builder as builder - -COPY . /data/docs -COPY requirements.txt /tmp/ -RUN pip install -r /tmp/requirements.txt && rm /tmp/requirements.txt - -WORKDIR /data/docs - -RUN make website DEST="/data/website" - -# Create a slim nginx image. -# Final image doesn't need python or latex -FROM ghcr.io/cakephp/docs-builder:runtime as runtime - -ENV LANGS="en es fr ja pt" -ENV SEARCH_SOURCE="/data/docs/build/html" -ENV SEARCH_URL_PREFIX="/5" - -COPY --from=builder /data/docs /data/docs -COPY --from=builder /data/website /data/website -COPY --from=builder /data/docs/nginx.conf /etc/nginx/conf.d/default.conf - -# Move built site into place -RUN cp -R /data/website/* /usr/share/nginx/html \ - && rm -rf /data/website/ diff --git a/diagrams/request-cycle.graffle b/diagrams/request-cycle.graffle deleted file mode 100644 index fb1b1b014b..0000000000 --- a/diagrams/request-cycle.graffle +++ /dev/null @@ -1,1677 +0,0 @@ - - - - - ActiveLayerIndex - 0 - ApplicationVersion - - com.omnigroup.OmniGraffle - 139.18.0.187838 - - AutoAdjust - - BackgroundGraphic - - Bounds - {{0, 0}, {576, 733}} - Class - SolidGraphic - ID - 2 - Style - - shadow - - Draws - NO - - stroke - - Draws - NO - - - - BaseZoom - 0 - CanvasOrigin - {0, 0} - ColumnAlign - 1 - ColumnSpacing - 36 - CreationDate - 2015-07-16 02:19:33 +0000 - Creator - Mark Story - DisplayScale - 1 0/72 in = 1.0000 in - GraphDocumentVersion - 8 - GraphicsList - - - Class - LineGraphic - Head - - ID - 26 - - ID - 39 - Points - - {383.11111450195312, 263.62499618530273} - {383.11111450195312, 221} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 37 - - - - Class - LineGraphic - Head - - ID - 37 - - ID - 38 - Points - - {383.11111450195312, 345.75} - {383.11111450195312, 300.62499618530273} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 12 - Info - 2 - - - - Bounds - {{309.30557250976562, 264.12499618530273}, {147.61111450195312, 36}} - Class - ShapedGraphic - ID - 37 - Shape - Rectangle - Style - - fill - - Color - - b - 0.866325 - g - 1 - r - 0.78566 - - - shadow - - Draws - NO - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Middleware} - - - - Bounds - {{138.91667175292969, 264.12499618530273}, {136.38888549804688, 36}} - Class - ShapedGraphic - ID - 35 - Shape - Rectangle - Style - - fill - - Color - - b - 0.866325 - g - 1 - r - 0.78566 - - - shadow - - Draws - NO - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Middleware} - - - - Class - LineGraphic - Head - - ID - 35 - - ID - 34 - Points - - {207.11111450195312, 220.9999932050705} - {207.11111450195312, 263.62499619497771} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 33 - Info - 1 - - - - Bounds - {{150.61111450195312, 166.9999932050705}, {113, 54}} - Class - ShapedGraphic - ID - 33 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.866325 - g - 1 - r - 0.78566 - - - shadow - - Draws - NO - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Load Application & bind to HttpServer} - - - - Bounds - {{326.61111450195312, 167}, {113, 54}} - Class - ShapedGraphic - ID - 26 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.737255 - g - 0.921569 - r - 0.976471 - - - shadow - - Draws - NO - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Response} - - - - Class - LineGraphic - Head - - ID - 15 - - ID - 25 - Points - - {442.61111450195312, 376.25} - {463.16665649414062, 403} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - - - Class - LineGraphic - Head - - ID - 14 - Info - 4 - - ID - 24 - Points - - {439.61111450195312, 372.75} - {463.16665649414062, 343.75} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 12 - Info - 3 - - - - Class - LineGraphic - Head - - ID - 9 - - ID - 23 - Points - - {307.88888549804688, 461} - {263.61111450195312, 370.25} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 11 - - - - Class - LineGraphic - Head - - ID - 12 - - ID - 22 - Points - - {265.61111450195312, 373.25} - {326.61111450195312, 372.75} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - - - Class - LineGraphic - Head - - ID - 13 - - ID - 21 - Points - - {251.38888549804688, 488} - {227.61111450195312, 488} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 11 - Info - 4 - - - - Class - LineGraphic - Head - - ID - 10 - - ID - 19 - Points - - {150.61111450195312, 370.25} - {123.61111450195312, 370.25} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 9 - Info - 4 - - - - Class - LineGraphic - Head - - ID - 9 - Info - 2 - - ID - 18 - Points - - {207.11111450195312, 300.62499618530273} - {207.11111450195312, 343.25} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 35 - - - - Class - LineGraphic - Head - - ID - 33 - Info - 2 - - ID - 17 - Points - - {207.11110687255859, 143} - {207.11111450195312, 166.9999932050705} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 5 - Info - 1 - - - - Bounds - {{463.16665649414062, 383.5}, {81.611114501953125, 39}} - Class - ShapedGraphic - ID - 15 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.945098 - g - 0.788235 - r - 0.470588 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.482353 - g - 0.466667 - r - 0.329412 - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Cell} - - - - Bounds - {{463.16665649414062, 324.25}, {81.611114501953125, 39}} - Class - ShapedGraphic - ID - 14 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.945098 - g - 0.788235 - r - 0.470588 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.482353 - g - 0.466667 - r - 0.329412 - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Helper} - - - - Bounds - {{326.61111450195312, 345.75}, {113, 54}} - Class - ShapedGraphic - ID - 12 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.945098 - g - 0.788235 - r - 0.470588 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.482353 - g - 0.466667 - r - 0.329412 - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 View} - - - - Bounds - {{251.38888549804688, 461}, {113, 54}} - Class - ShapedGraphic - ID - 11 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.258824 - g - 0.27451 - r - 0.85098 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.215686 - g - 0.145098 - r - 0.329412 - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Model} - - - - Bounds - {{42, 350.75}, {81.611114501953125, 39}} - Class - ShapedGraphic - ID - 10 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.172549 - g - 0.741176 - r - 0.945098 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.482353 - g - 0.466667 - r - 0.329412 - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Component} - - - - Bounds - {{150.61111450195312, 343.25}, {113, 54}} - Class - ShapedGraphic - ID - 9 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.172549 - g - 0.741176 - r - 0.945098 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.482353 - g - 0.466667 - r - 0.329412 - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Controller} - - - - Class - LineGraphic - Head - - ID - 5 - - ID - 6 - Points - - {126.61111450195312, 116} - {150.61110687255859, 116} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 4 - Info - 3 - - - - Bounds - {{150.61110687255859, 89}, {113, 54}} - Class - ShapedGraphic - ID - 5 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.866325 - g - 1 - r - 0.78566 - - - shadow - - Draws - NO - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 autoloader} - - - - Bounds - {{13.611114501953125, 89}, {113, 54}} - Class - ShapedGraphic - ID - 4 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.737255 - g - 0.921569 - r - 0.976471 - - - shadow - - Draws - NO - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 index.php} - - - - Bounds - {{37, 345.75}, {81.611114501953125, 39}} - Class - ShapedGraphic - ID - 28 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.172549 - g - 0.741176 - r - 0.945098 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.482353 - g - 0.466667 - r - 0.329412 - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Component} - - - - Bounds - {{468.16665649414062, 318.25}, {81.611114501953125, 39}} - Class - ShapedGraphic - ID - 29 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.945098 - g - 0.788235 - r - 0.470588 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.482353 - g - 0.466667 - r - 0.329412 - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Helper} - - - - Bounds - {{468.16665649414062, 380.5}, {81.611114501953125, 39}} - Class - ShapedGraphic - ID - 30 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.945098 - g - 0.788235 - r - 0.470588 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.482353 - g - 0.466667 - r - 0.329412 - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Cell} - - - - Bounds - {{146, 468.5}, {81.611114501953125, 39}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 12 - - ID - 13 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.258824 - g - 0.27451 - r - 0.85098 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.215686 - g - 0.145098 - r - 0.329412 - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Behavior} - - - - Bounds - {{142, 463.5}, {81.611114501953125, 39}} - Class - ShapedGraphic - ID - 31 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.258824 - g - 0.27451 - r - 0.85098 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.215686 - g - 0.145098 - r - 0.329412 - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Behavior} - - - - Bounds - {{255.77777099609375, 456}, {113, 54}} - Class - ShapedGraphic - ID - 32 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.258824 - g - 0.27451 - r - 0.85098 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.215686 - g - 0.145098 - r - 0.329412 - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Model} - - - - GridInfo - - GuidesLocked - NO - GuidesVisible - YES - HPages - 1 - ImageCounter - 1 - KeepToScale - - Layers - - - Lock - NO - Name - Layer 1 - Print - YES - View - YES - - - LayoutInfo - - Animate - NO - circoMinDist - 18 - circoSeparation - 0.0 - layoutEngine - dot - neatoSeparation - 0.0 - twopiSeparation - 0.0 - - LinksVisible - NO - MagnetsVisible - NO - MasterSheets - - ModificationDate - 2016-07-20 02:29:00 +0000 - Modifier - Mark Story - NotesVisible - NO - Orientation - 2 - OriginVisible - NO - PageBreaks - YES - PrintInfo - - NSBottomMargin - - float - 41 - - NSHorizonalPagination - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSLeftMargin - - float - 18 - - NSPaperSize - - size - {612, 792} - - NSPrintReverseOrientation - - int - 0 - - NSRightMargin - - float - 18 - - NSTopMargin - - float - 18 - - - PrintOnePage - - ReadOnly - NO - RowAlign - 1 - RowSpacing - 36 - SheetTitle - Canvas 1 - SmartAlignmentGuidesActive - YES - SmartDistanceGuidesActive - YES - UniqueID - 1 - UseEntirePage - - VPages - 1 - WindowInfo - - CurrentSheet - 0 - ExpandedCanvases - - - name - Canvas 1 - - - Frame - {{61, 35}, {951, 742}} - ListView - - OutlineWidth - 142 - RightSidebar - - ShowRuler - - Sidebar - - SidebarWidth - 120 - VisibleRegion - {{-120, 0}, {816, 600}} - Zoom - 1 - ZoomValues - - - Canvas 1 - 1 - 1 - - - - - diff --git a/diagrams/save-cycle.graffle b/diagrams/save-cycle.graffle deleted file mode 100644 index 7196eb83be..0000000000 --- a/diagrams/save-cycle.graffle +++ /dev/null @@ -1,1625 +0,0 @@ - - - - - ActiveLayerIndex - 0 - ApplicationVersion - - com.omnigroup.OmniGraffle - 139.18.0.187838 - - AutoAdjust - - BackgroundGraphic - - Bounds - {{0, 0}, {575.99998474121094, 1466}} - Class - SolidGraphic - ID - 2 - Style - - shadow - - Draws - NO - - stroke - - Draws - NO - - - - BaseZoom - 0 - CanvasOrigin - {0, 0} - ColumnAlign - 1 - ColumnSpacing - 36 - CreationDate - 2015-11-19 17:06:46 +0000 - Creator - Mark Story - DisplayScale - 1 0/72 in = 1.0000 in - GraphDocumentVersion - 8 - GraphicsList - - - Class - LineGraphic - Head - - ID - 62 - - ID - 79 - Points - - {238.5, 453.87501004692155} - {238.5, 486.33510325838961} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 77 - - - - Bounds - {{152.12500493102294, 417.37501006602866}, {172.75, 36}} - Class - ShapedGraphic - ID - 77 - Shape - Rectangle - Style - - fill - - Color - - b - 0.290196 - g - 0.658824 - r - 0.631373 - - - stroke - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Model.beforeSave Event} - - - - Class - LineGraphic - Head - - ID - 37 - Info - 2 - - ID - 76 - Points - - {238.5, 787.52260325838961} - {238.5, 846.27260325838961} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 53 - - - - Class - LineGraphic - Head - - ID - 53 - - ID - 75 - Points - - {238.50001500090627, 719.96010325838961} - {238.50001500090627, 750.52260325838961} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 73 - - - - Class - LineGraphic - Head - - ID - 73 - - ID - 74 - Points - - {238.49999294640077, 652.39760758583475} - {238.49999294640077, 682.96010326664305} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 68 - Info - 1 - - - - Bounds - {{152.125, 683.46010325838961}, {172.75, 36}} - Class - ShapedGraphic - ID - 73 - Shape - Rectangle - Style - - fill - - Color - - b - 0.290196 - g - 0.658824 - r - 0.631373 - - - stroke - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Model.afterSave Event} - - - - Bounds - {{365.4375, 601.38031700381691}, {16, 14}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 72 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 No} - VerticalPad - 0 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 70 - - ID - 71 - Points - - {293, 618.39760325838961} - {428, 618.39760325838961} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 68 - Info - 3 - - - - Bounds - {{428, 584.89760325838961}, {83, 67}} - Class - ShapedGraphic - ID - 70 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.203922 - g - 0.141176 - r - 0.788235 - - - shadow - - Draws - NO - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 false} - - - - Class - LineGraphic - Head - - ID - 68 - Info - 2 - - ID - 69 - Points - - {238.5, 553.33510325838961} - {238.5, 584.89760325838961} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 62 - Info - 1 - - - - Bounds - {{184, 584.89760325838961}, {109, 67}} - Class - ShapedGraphic - ID - 68 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Diamond - Style - - fill - - Color - - b - 0.945098 - g - 0.788235 - r - 0.470588 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Save Child Entities?} - - - - Bounds - {{365.93750133639605, 498.8191535002295}, {16, 14}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 67 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 No} - VerticalPad - 0 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 65 - Info - 4 - - ID - 66 - Points - - {293, 519.83510325838961} - {428, 519.83510325838961} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 62 - Info - 3 - - - - Bounds - {{428, 486.33510325838967}, {83, 67}} - Class - ShapedGraphic - ID - 65 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.203922 - g - 0.141176 - r - 0.788235 - - - shadow - - Draws - NO - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 false} - - - - Class - LineGraphic - Head - - ID - 77 - - ID - 64 - Points - - {238.50000735035636, 389.50000000932869} - {238.50000735035636, 416.87501005681827} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 51 - - - - Bounds - {{184, 486.33510325838967}, {109, 67}} - Class - ShapedGraphic - ID - 62 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Diamond - Style - - fill - - Color - - b - 0.945098 - g - 0.788235 - r - 0.470588 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Entity Save Success?} - - - - Bounds - {{357.43749544943222, 353.00000227528386}, {52, 14}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 61 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Stopped?} - VerticalPad - 0 - - Wrap - NO - - - Bounds - {{428, 337.5}, {83, 67}} - Class - ShapedGraphic - ID - 59 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.203922 - g - 0.141176 - r - 0.788235 - - - shadow - - Draws - NO - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Event result} - - - - Class - LineGraphic - Head - - ID - 59 - - ID - 58 - Points - - {325.375, 371} - {428, 371} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 51 - - - - Bounds - {{357.4375, 265}, {16, 14}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 57 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 No} - VerticalPad - 0 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 51 - - ID - 56 - Points - - {238.5, 316.1875} - {238.5, 352.5} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 33 - Info - 1 - - - - Class - LineGraphic - Head - - ID - 33 - Info - 2 - - ID - 55 - Points - - {238.5, 221.25} - {238.5, 249.1875} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 52 - - - - Class - LineGraphic - Head - - ID - 39 - Info - 4 - - ID - 54 - Points - - {293, 282.6875} - {428, 282.6875} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 33 - Info - 3 - - - - Bounds - {{152.125, 751.02260325838961}, {172.75, 36}} - Class - ShapedGraphic - ID - 53 - Shape - Rectangle - Style - - fill - - Color - - b - 0.290196 - g - 0.658824 - r - 0.631373 - - - stroke - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Model.afterSaveCommit Event} - - - - Bounds - {{146.25, 184.75}, {184.5, 36}} - Class - ShapedGraphic - ID - 52 - Shape - Rectangle - Style - - fill - - Color - - b - 0.290196 - g - 0.658824 - r - 0.631373 - - - stroke - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Model.buildRules Event} - - - - Bounds - {{152.125, 353}, {172.75, 36}} - Class - ShapedGraphic - ID - 51 - Shape - Rectangle - Style - - fill - - Color - - b - 0.290196 - g - 0.658824 - r - 0.631373 - - - stroke - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Model.afterRules Event} - - - - Bounds - {{428, 249.1875}, {83, 67}} - Class - ShapedGraphic - ID - 39 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.203922 - g - 0.141176 - r - 0.788235 - - - shadow - - Draws - NO - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Entity with Errors} - - - - Bounds - {{197, 846.27260325838961}, {83, 83}} - Class - ShapedGraphic - ID - 37 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.258824 - g - 0.27451 - r - 0.85098 - - - shadow - - Draws - NO - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Persisted Entity} - - - - Bounds - {{184, 249.1875}, {109, 67}} - Class - ShapedGraphic - ID - 33 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Diamond - Style - - fill - - Color - - b - 0.945098 - g - 0.788235 - r - 0.470588 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Application Rules Pass?} - - - - Bounds - {{184, 158}, {127, 17}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - FontInfo - - Font - Helvetica - Size - 12 - - ID - 31 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs28 \cf0 ArticlesTable::save()} - VerticalPad - 0 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 27 - - ID - 30 - Points - - {236.5, 102} - {236.5, 148} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 29 - - - - Bounds - {{195, 19}, {83, 83}} - Class - ShapedGraphic - ID - 29 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.258824 - g - 0.27451 - r - 0.85098 - - - shadow - - Draws - NO - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Entity Object} - - - - Bounds - {{130, 148}, {213, 657.31915283203125}} - Class - ShapedGraphic - ID - 27 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.741176 - g - 0.921569 - r - 0.972549 - - - shadow - - Draws - NO - - stroke - - Color - - a - 0 - b - 0 - g - 0 - r - 0 - - CornerRadius - 5 - - - - - GridInfo - - GuidesLocked - NO - GuidesVisible - YES - HPages - 1 - ImageCounter - 1 - KeepToScale - - Layers - - - Lock - NO - Name - Layer 1 - Print - YES - View - YES - - - LayoutInfo - - Animate - NO - circoMinDist - 18 - circoSeparation - 0.0 - layoutEngine - dot - neatoSeparation - 0.0 - twopiSeparation - 0.0 - - LinksVisible - NO - MagnetsVisible - NO - MasterSheets - - ModificationDate - 2015-12-10 03:00:52 +0000 - Modifier - Mark Story - NotesVisible - NO - Orientation - 2 - OriginVisible - NO - PageBreaks - YES - PrintInfo - - NSBottomMargin - - float - 41 - - NSHorizonalPagination - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSLeftMargin - - float - 18 - - NSPaperSize - - size - {611.99998474121094, 792} - - NSPrintReverseOrientation - - int - 0 - - NSRightMargin - - float - 18 - - NSTopMargin - - float - 18 - - - PrintOnePage - - ReadOnly - NO - RowAlign - 1 - RowSpacing - 36 - SheetTitle - Canvas 1 - SmartAlignmentGuidesActive - YES - SmartDistanceGuidesActive - YES - UniqueID - 1 - UseEntirePage - - VPages - 2 - WindowInfo - - CurrentSheet - 0 - ExpandedCanvases - - - name - Canvas 1 - - - Frame - {{146, 4}, {799, 773}} - ListView - - OutlineWidth - 142 - RightSidebar - - ShowRuler - - Sidebar - - SidebarWidth - 120 - VisibleRegion - {{-73, 244.56521295475835}, {721.73911734204228, 685.86955277534457}} - Zoom - 0.92000001668930054 - ZoomValues - - - Canvas 1 - 0.92000001668930054 - 0.93999999761581421 - - - - - diff --git a/diagrams/validation-cycle.graffle b/diagrams/validation-cycle.graffle deleted file mode 100644 index 1a4f732911..0000000000 --- a/diagrams/validation-cycle.graffle +++ /dev/null @@ -1,948 +0,0 @@ - - - - - ActiveLayerIndex - 0 - ApplicationVersion - - com.omnigroup.OmniGraffle - 139.18.0.187838 - - AutoAdjust - - BackgroundGraphic - - Bounds - {{0, 0}, {575.99998474121094, 949}} - Class - SolidGraphic - ID - 2 - Style - - shadow - - Draws - NO - - stroke - - Draws - NO - - - - BaseZoom - 0 - CanvasOrigin - {0, 0} - ColumnAlign - 1 - ColumnSpacing - 36 - CreationDate - 2015-11-18 03:26:13 +0000 - Creator - Mark Story - DisplayScale - 1 0/72 in = 1.0000 in - GraphDocumentVersion - 8 - GraphicsList - - - Class - LineGraphic - Head - - ID - 43 - - ID - 54 - Points - - {384.68555234279057, 239.74951613369532} - {383.77198374280601, 260.50048386630465} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 52 - - - - Class - LineGraphic - Head - - ID - 52 - - ID - 53 - Points - - {385.67075804692541, 181.99997869474356} - {385.86228526628054, 202.75009584196511} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 51 - - - - Bounds - {{265.5, 203.25}, {240, 36}} - Class - ShapedGraphic - ID - 52 - Shape - Rectangle - Style - - fill - - Color - - b - 0.290196 - g - 0.658824 - r - 0.631373 - - - stroke - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Model.buildValidator Event} - - - - Bounds - {{265.5, 145.5}, {240, 36}} - Class - ShapedGraphic - ID - 51 - Shape - Rectangle - Style - - fill - - Color - - b - 0.290196 - g - 0.658824 - r - 0.631373 - - - stroke - - CornerRadius - 9 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Model.beforeMarshal Event} - - - - Class - LineGraphic - Head - - ID - 49 - - ID - 50 - Points - - {210, 294.5} - {247.5, 294.5} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 4 - - - - Bounds - {{309.24999237060547, 261}, {149, 54}} - Class - ShapedGraphic - ID - 43 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.172549 - g - 0.741176 - r - 0.945098 - - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Validation rules applied} - - - - Bounds - {{448.25000762939453, 419}, {16, 14}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 26 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 No} - VerticalPad - 0 - - Wrap - NO - - - Bounds - {{299.24999237060547, 419}, {20, 14}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 25 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Yes} - VerticalPad - 0 - - Wrap - NO - - - Bounds - {{315.75, 109.75}, {135, 14}} - Class - ShapedGraphic - FitText - YES - Flow - Resize - ID - 23 - Shape - Rectangle - Style - - fill - - Draws - NO - - shadow - - Draws - NO - - stroke - - Draws - NO - - - Text - - Pad - 0 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 ArticlesTable::newEntity()} - VerticalPad - 0 - - Wrap - NO - - - Class - LineGraphic - Head - - ID - 14 - Info - 2 - - ID - 21 - Points - - {383.74999237060547, 315} - {383.75, 345} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 43 - Info - 1 - - - - Class - LineGraphic - Head - - ID - 17 - Info - 2 - - ID - 20 - Points - - {334.25, 394.5} - {332.75, 488} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 14 - Info - 4 - - - - Class - LineGraphic - Head - - ID - 18 - - ID - 19 - Points - - {433.25, 394.5} - {433.75, 488} - - Style - - stroke - - HeadArrow - FilledArrow - Legacy - - LineType - 1 - TailArrow - 0 - - - Tail - - ID - 14 - Info - 3 - - - - Bounds - {{392.25, 488}, {83, 83}} - Class - ShapedGraphic - ID - 18 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.258824 - g - 0.27451 - r - 0.85098 - - - shadow - - Draws - NO - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Article Entity with errors} - - - - Bounds - {{291.25, 488}, {83, 83}} - Class - ShapedGraphic - ID - 17 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.258824 - g - 0.27451 - r - 0.85098 - - - shadow - - Draws - NO - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Article Entity} - - - - Bounds - {{111, 262}, {99, 65}} - Class - ShapedGraphic - ID - 4 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.258824 - g - 0.27451 - r - 0.85098 - - - shadow - - Draws - NO - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Request Data} - - - - Bounds - {{334.25, 345}, {99, 99}} - Class - ShapedGraphic - ID - 14 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Diamond - Style - - fill - - Color - - b - 0.945098 - g - 0.788235 - r - 0.470588 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 Valid?} - - - - Bounds - {{248, 131}, {270.5, 327}} - Class - ShapedGraphic - ID - 49 - Shape - Rectangle - Style - - fill - - Color - - b - 0.741176 - g - 0.921569 - r - 0.972549 - - - shadow - - Draws - NO - - stroke - - Color - - b - 0.741176 - g - 0.921569 - r - 0.972549 - - CornerRadius - 9 - - - - - GridInfo - - GuidesLocked - NO - GuidesVisible - YES - HPages - 1 - ImageCounter - 1 - KeepToScale - - Layers - - - Lock - NO - Name - Layer 1 - Print - YES - View - YES - - - LayoutInfo - - Animate - NO - circoMinDist - 18 - circoSeparation - 0.0 - layoutEngine - dot - neatoSeparation - 0.0 - twopiSeparation - 0.0 - - LinksVisible - NO - MagnetsVisible - NO - MasterSheets - - ModificationDate - 2015-12-10 02:47:14 +0000 - Modifier - Mark Story - NotesVisible - NO - Orientation - 2 - OriginVisible - NO - PageBreaks - YES - PrintInfo - - NSBottomMargin - - float - 41 - - NSHorizonalPagination - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSLeftMargin - - float - 18 - - NSPaperName - - string - na-legal - - NSPaperSize - - size - {611.99998474121094, 1008} - - NSPrintReverseOrientation - - int - 0 - - NSRightMargin - - float - 18 - - NSTopMargin - - float - 18 - - - PrintOnePage - - ReadOnly - NO - RowAlign - 1 - RowSpacing - 36 - SheetTitle - Canvas 1 - SmartAlignmentGuidesActive - YES - SmartDistanceGuidesActive - YES - UniqueID - 1 - UseEntirePage - - VPages - 1 - WindowInfo - - CurrentSheet - 0 - ExpandedCanvases - - - name - Canvas 1 - - - Frame - {{44, 40}, {933, 731}} - ListView - - OutlineWidth - 142 - RightSidebar - - ShowRuler - - Sidebar - - SidebarWidth - 120 - VisibleRegion - {{-111, 39}, {798, 589}} - Zoom - 1 - ZoomValues - - - Canvas 1 - 1 - 1 - - - - - diff --git a/docs/en/404.md b/docs/en/404.md new file mode 100644 index 0000000000..d57641b92d --- /dev/null +++ b/docs/en/404.md @@ -0,0 +1,7 @@ +orphan +True + +# Not Found + +The page you're looking for cannot be found. Try using the search bar to find +what you're looking for. diff --git a/docs/en/appendices.md b/docs/en/appendices.md new file mode 100644 index 0000000000..6d3c462562 --- /dev/null +++ b/docs/en/appendices.md @@ -0,0 +1,28 @@ +# Appendices + +Appendices contain information regarding the new features +introduced in each version and the migration path between versions. + +## Migration Guides + +[Migration Guides](appendices/migration-guides) + +## Backwards Compatibility Shimming + +If you need/want to shim 4.x behavior, or partially migrate in steps, check out +the [Shim plugin](https://github.com/dereuromark/cakephp-shim) that can help mitigate some BC breaking changes. + +## Forwards Compatibility Shimming + +Forwards compatibility shimming can prepare your 4.x app for the next major +release (5.x). + +If you already want to shim 5.x behavior into 4.x, check out the [Shim plugin](https://github.com/dereuromark/cakephp-shim). This plugin aims to mitigate +some backwards compatibility breakage and help backport features from 5.x to +4.x. The closer your 3.x app is to 4.x, the smaller will be the diff of +changes, and the smoother will be the final upgrade. + +## General Information + +- [CakePHP Development Process](appendices/cakephp-development-process) +- [Glossary](appendices/glossary) diff --git a/docs/en/appendices/5-0-migration-guide.md b/docs/en/appendices/5-0-migration-guide.md new file mode 100644 index 0000000000..37c64868c4 --- /dev/null +++ b/docs/en/appendices/5-0-migration-guide.md @@ -0,0 +1,429 @@ +# 5.0 Migration Guide + +CakePHP 5.0 contains breaking changes, and is not backwards compatible with 4.x +releases. Before attempting to upgrade to 5.0, first upgrade to 4.5 and resolve +all deprecation warnings. + +Refer to the [5.0 Upgrade Guide](../appendices/5-0-upgrade-guide) for step by step instructions +on how to upgrade to 5.0. + +## Deprecated Features Removed + +All methods, properties and functionality that were emitting deprecation warnings +as of 4.5 have been removed. + +## Breaking Changes + +In addition to the removal of deprecated features there have been breaking +changes made: + +### Global + +- Type declarations were added to all function parameter and returns where possible. These are intended + to match the docblock annotations, but include fixes for incorrect annotations. + +- Type declarations were added to all class properties where possible. These also include some fixes for + incorrect annotations. + +- The `SECOND`, `MINUTE`, `HOUR`, `DAY`, `WEEK`, `MONTH`, `YEAR` constants were removed. + +- Use of `#[\AllowDynamicProperties]` removed everywhere. It was used for the following classes: + - `Command/Command` + - `Console/Shell` + - `Controller/Component` + - `Controller/Controller` + - `Mailer/Mailer` + - `View/Cell` + - `View/Helper` + - `View/View` + +- The supported database engine versions were updated: + - MySQL (5.7 or higher) + - MariaDB (10.1 or higher) + - PostgreSQL (9.6 or higher) + - Microsoft SQL Server (2012 or higher) + - SQLite 3 (3.16 or higher) + +### Auth + +- Auth has been removed. Use the [cakephp/authentication](https://book.cakephp.org/authentication/3/en/index.html) and + [cakephp/authorization](https://book.cakephp.org/authorization/3/en/index.html) plugins instead. + +### Cache + +- The `Wincache` engine was removed. The wincache extension is not supported + on PHP 8. + +### Collection + +- `combine()` now throws an exception if the key path or group path doesn't exist or contains a null value. + This matches the behavior of `indexBy()` and `groupBy()`. + +### Console + +- `BaseCommand::__construct()` was removed. +- `ConsoleIntegrationTestTrait::useCommandRunner()` was removed since it's no longer needed. +- `Shell` has been removed and should be replaced with [Command](https://book.cakephp.org/5/en/console-commands/commands.html) +- `ConsoleOptionParser::addSubcommand()` was removed alongside the removal of + `Shell`. Subcommands should be replaced with `Command` classes that + implement `Command::defaultName()` to define the necessary command name. +- `BaseCommand` now emits `Command.beforeExecute` and + `Command.afterExecute` events around the command's `execute()` method + being invoked by the framework. + +### Connection + +- `Connection::prepare()` has been removed. You can use `Connection::execute()` + instead to execute a SQL query by specifing the SQL string, params and types in a single call. +- `Connection::enableQueryLogging()` has been removed. If you haven't enabled logging + through the connection config then you can later set the logger instance for the + driver to enable query logging `$connection->getDriver()->setLogger()`. + +### Controller + +- The method signature for `Controller::__construct()` has changed. + So you need to adjust your code accordingly if you are overriding the constructor. +- After loading components are no longer set as dynamic properties. Instead + `Controller` uses `__get()` to provide property access to components. This + change can impact applications that use `property_exists()` on components. +- The components' `Controller.shutdown` event callback has been renamed from + `shutdown` to `afterFilter` to match the controller one. This makes the callbacks more consistent. +- `PaginatorComponent` has been removed and should be replaced by calling `$this->paginate()` in your controller or + using `Cake\Datasource\Paging\NumericPaginator` directly +- `RequestHandlerComponent` has been removed. See the [4.4 migration](https://book.cakephp.org/4/en/appendices/4-4-migration-guide.html#requesthandlercomponent) guide for how to upgrade +- `SecurityComponent` has been removed. Use `FormProtectionComponent` for form tampering protection + or `HttpsEnforcerMiddleware` to enforce use of HTTPS for requests instead. +- `Controller::paginate()` no longer accepts query options like `contain` for + its `$settings` argument. You should instead use the `finder` option + `$this->paginate($this->Articles, ['finder' => 'published'])`. Or you can + create required select query before hand and then pass it to `paginate()` + `$query = $this->Articles->find()->where(['is_published' => true]); $this->paginate($query);`. + +### Core + +- The function `getTypeName()` has been dropped. Use PHP's `get_debug_type()` instead. +- The dependency on `league/container` was updated to `4.x`. This will + require the addition of typehints to your `ServiceProvider` implementations. +- `deprecationWarning()` now has a `$version` parameter. +- The `App.uploadedFilesAsObjects` configuration option has been removed + alongside of support for PHP file upload shaped arrays throughout the + framework. +- `ClassLoader` has been removed. Use composer to generate autoload files instead. + +### Database + +- The `DateTimeType` and `DateType` now always return immutable objects. + Additionally the interface for `Date` objects reflects the `ChronosDate` + interface which lacks all of the time related methods that were present in + CakePHP 4.x. +- `DateType::setLocaleFormat()` no longer accepts an array. +- `Query` now accepts only `\Closure` parameters instead of `callable`. Callables can be converted + to closures using the new first-class array syntax in PHP 8.1. +- `Query::execute()` no longer runs results decorator callbacks. You must use `Query::all()` instead. +- `TableSchemaAwareInterface` was removed. +- `Driver::quote()` was removed. Use prepared statements instead. +- `Query::orderBy()` was added to replace `Query::order()`. +- `Query::groupBy()` was added to replace `Query::group()`. +- `SqlDialectTrait` has been removed and all its functionality has been moved + into the `Driver` class itself. +- `CaseExpression` has been removed and should be replaced with + `QueryExpression::case()` or `CaseStatementExpression` +- `Connection::connect()` has been removed. Use + `$connection->getDriver()->connect()` instead. +- `Connection::disconnect()` has been removed. Use + `$connection->getDriver()->disconnect()` instead. +- `cake.database.queries` has been added as an alternative to the `queriesLog` scope +- The ability to enable/disable ResultSet buffering has been removed. Results are always buffered. + +### Datasource + +- The `getAccessible()` method was added to `EntityInterface`. Non-ORM + implementations need to implement this method now. +- The `aliasField()` method was added to `RepositoryInterface`. Non-ORM + implementations need to implement this method now. + +### Event + +- Event payloads must be an array. Other object such as `ArrayAccess` are no longer cast to array and will raise a `TypeError` now. +- It is recommended to adjust event handlers to be void methods and use `$event->setResult()` instead of returning the result + +### Error + +- `ErrorHandler` and `ConsoleErrorHandler` have been removed. See the [4.4 migration](https://book.cakephp.org/4/en/appendices/4-4-migration-guide.html#errorhandler-consoleerrorhandler) guide for how to upgrade +- `ExceptionRenderer` has been removed and should be replaced with `WebExceptionRenderer` +- `ErrorLoggerInterface::log()` has been removed and should be replaced with `ErrorLoggerInterface::logException()` +- `ErrorLoggerInterface::logMessage()` has been removed and should be replaced with `ErrorLoggerInterface::logError()` + +### Filesystem + +- The Filesystem package was removed, and `Filesystem` class was moved to the Utility package. + +### Http + +- `ServerRequest` is no longer compatible with `files` as arrays. This + behavior has been disabled by default since 4.1.0. The `files` data will now + always contain `UploadedFileInterfaces` objects. + +### I18n + +- `FrozenDate` was renamed to Date and `FrozenTime` was renamed to DateTime. +- `Time` now extends `Cake\Chronos\ChronosTime` and is therefore immutable. +- `Date` objects do not extend `DateTimeInterface` anymore - therefore you can't compare them with `DateTime` objects. + See the [cakephp/chronos release documentation](https://github.com/cakephp/chronos/releases/tag/3.0.2) for more information. +- `Date::parseDateTime()` was removed. +- `Date::parseTime()` was removed. +- `Date::setToStringFormat()` and `Date::setJsonEncodeFormat()` no longer accept an array. +- `Date::i18nFormat()` and `Date::nice()` no longer accept a timezone parameter. +- Translation files for plugins with vendor prefixed names (`FooBar/Awesome`) will now have that + prefix in the file name, e.g. `foo_bar_awesome.po` to avoid collision with a `awesome.po` file + from a corresponding plugin (`Awesome`). + +### Log + +- Log engine config now uses `null` instead of `false` to disable scopes. + So instead of `'scopes' => false` you need to use `'scopes' => null` in your log config. + +### Mailer + +- `Email` has been removed. Use [Mailer](https://book.cakephp.org/5/en/core-libraries/email.html) instead. +- `cake.mailer` has been added as an alternative to the `email` scope + +### ORM + +- `EntityTrait::has()` now returns `true` when an attribute exists and is + set to `null`. In previous versions of CakePHP this would return `false`. + See the release notes for 4.5.0 for how to adopt this behavior in 4.x. +- `EntityTrait::extractOriginal()` now returns only existing fields, similar to `extractOriginalChanged()`. +- Finder arguments are now required to be associative arrays as they were always expected to be. +- `TranslateBehavior` now defaults to the `ShadowTable` strategy. If you are + using the `Eav` strategy you will need to update your behavior configuration + to retain the previous behavior. +- `allowMultipleNulls` option for `isUnique` rule now default to true matching + the original 3.x behavior. +- `Table::query()` has been removed in favor of query-type specific functions. +- `Table::updateQuery()`, `Table::selectQuery()`, `Table::insertQuery()`, and + `Table::deleteQuery()`) were added and return the new type-specific query objects below. +- `SelectQuery`, `InsertQuery`, `UpdateQuery` and `DeleteQuery` were added + which represent only a single type of query and do not allow switching between query types nor + calling functions unrelated to the specific query type. +- `Table::_initializeSchema()` has been removed and should be replaced by calling + `$this->getSchema()` inside the `initialize()` method. +- `SaveOptionsBuilder` has been removed. Use a normal array for options instead. + +### Known Issues + +**Memory usage with large result sets** + +Compared to CakePHP 4.x, when working with large data sets (especially when +calling collection methods like `extract()` on the result set), you may encounter +high memory usage due to the entire result set being buffered in memory. + +You can work around this issue by disabling results buffering for the query: + +``` php +$results = $articles->find() + ->disableBufferedResults() + ->all(); +``` + +Depending on your use case, you may also consider using disabling hydration: + +``` php +$results = $articles->find() + ->disableHydration() + ->all(); +``` + +The above will disable creation of entity objects and return rows as arrays instead. + +### Routing + +- Static methods `connect()`, `prefix()`, `scope()` and `plugin()` of the `Router` have been removed and + should be replaced by calling their non-static method variants via the `RouteBuilder` instance. +- `RedirectException` has been removed. Use `\Cake\Http\Exception\RedirectException` instead. + +### TestSuite + +- `TestSuite` was removed. Users should use environment variables to customize + unit test settings instead. +- `TestListenerTrait` was removed. PHPUnit dropped support for these listeners. + See [PHPUnit 10 Upgrade](../appendices/phpunit10) +- `IntegrationTestTrait::configRequest()` now merges config when called multiple times + instead of replacing the currently present config. + +### Validation + +- `Validation::isEmpty()` is no longer compatible with file upload shaped + arrays. Support for PHP file upload arrays has been removed from + `ServerRequest` as well so you should not see this as a problem outside of + tests. +- Previously, most data validation error messages were simply `The provided value is invalid`. + Now, the data validation error messages are worded more precisely. + For example, `` The provided value must be greater than or equal to \`5\ ``\`. + +### View + +- `ViewBuilder` options are now truly associative (string keys). +- `NumberHelper` and `TextHelper` no longer accept an `engine` config. +- `ViewBuilder::setHelpers()` parameter `$merge` was removed. Use `ViewBuilder::addHelpers()` instead. +- Inside `View::initialize()`, prefer using `addHelper()` instead of `loadHelper()`. + All configured helpers will be loaded afterwards, anyway. +- `View\Widget\FileWidget` is no longer compatible with PHP file upload shaped + arrays. This is aligned with `ServerRequest` and `Validation` changes. +- `FormHelper` no longer sets `autocomplete=off` on CSRF token fields. This + was a workaround for a Safari bug that is no longer relevant. + +## Deprecations + +The following is a list of deprecated methods, properties and behaviors. These +features will continue to function in 5.x and will be removed in 6.0. + +### Database + +- `Query::order()` was deprecated. Use `Query::orderBy()` instead now that + `Connection` methods are no longer proxied. This aligns the function name + with the SQL statement. +- `Query::group()` was deprecated. Use `Query::groupBy()` instead now that + `Connection` methods are no longer proxied. This aligns the function name + with the SQL statement. + +### ORM + +- Calling `Table::find()` with options array is deprecated. Use [named arguments](https://www.php.net/manual/en/functions.arguments.php#functions.named-arguments) + instead. For e.g. instead of `find('all', ['conditions' => $array])` use + `find('all', conditions: $array)`. Similarly for custom finder options, instead + of `find('list', ['valueField' => 'name'])` use `find('list', valueField: 'name')` + or multiple named arguments like `find(type: 'list', valueField: 'name', conditions: $array)`. + +## New Features + +### Improved type checking + +CakePHP 5 leverages the expanded type system feature available in PHP 8.1+. +CakePHP also uses `assert()` to provide improved error messages and additional +type soundness. In production mode, you can configure PHP to not generate +code for `assert()` yielding improved application performance. See the +[Symlink Assets](../deployment#symlink-assets) for how to do this. + +### Collection + +- Added `unique()` which filters out duplicate value specified by provided callback. +- `reject()` now supports a default callback which filters out truthy values which is + the inverse of the default behavior of `filter()` + +### Core + +- The `services()` method was added to `PluginInterface`. +- `PluginCollection::addFromConfig()` has been added to [simplify plugin loading](../plugins#loading-a-plugin). + +### Database + +- `ConnectionManager` now supports read and write connection roles. Roles can be configured + with `read` and `write` keys in the connection config that override the shared config. +- `Query::all()` was added which runs result decorator callbacks and returns a result set for select queries. +- `Query::comment()` was added to add a SQL comment to the executed query. This makes it easier to debug queries. +- `EnumType` was added to allow mapping between PHP backed enums and a string or integer column. +- `getMaxAliasLength()` and `getConnectionRetries()` were added + to `DriverInterface`. +- Supported drivers now automatically add auto-increment only to integer primary keys named "id" instead + of all integer primary keys. Setting 'autoIncrement' to false always disables on all supported drivers. + +### Http + +- Added support for [PSR-17](https://www.php-fig.org/psr/psr-17/) factories + interface. This allows `cakephp/http` to provide a client implementation to + libraries that allow automatic interface resolution like php-http. +- Added `CookieCollection::__get()` and `CookieCollection::__isset()` to add + ergonomic ways to access cookies without exceptions. + +### ORM + +### Required Entity Fields + +Entities have a new opt-in functionality that allows making entities handle +properties more strictly. The new behavior is called 'required fields'. When +enabled, accessing properties that are not defined in the entity will raise +exceptions. This impacts the following usage: + +``` php +$entity->get(); +$entity->has(); +$entity->getOriginal(); +isset($entity->attribute); +$entity->attribute; +``` + +Fields are considered defined if they pass `array_key_exists`. This includes +null values. Because this can be a tedious to enable feature, it was deferred to +5.0. We'd like any feedback you have on this feature as we're considering making +this the default behavior in the future. + +### Typed Finder Parameters + +Table finders can now have typed arguments as required instead of an options array. +For e.g. a finder for fetching posts by category or user: + +``` php +public function findByCategoryOrUser(SelectQuery $query, array $options) +{ + if (isset($options['categoryId'])) { + $query->where(['category_id' => $options['categoryId']]); + } + if (isset($options['userId'])) { + $query->where(['user_id' => $options['userId']]); + } + + return $query; +} +``` + +can now be written as: + +``` php +public function findByCategoryOrUser(SelectQuery $query, ?int $categoryId = null, ?int $userId = null) +{ + if ($categoryId) { + $query->where(['category_id' => $categoryId]); + } + if ($userId) { + $query->where(['user_id' => $userId]); + } + + return $query; +} +``` + +The finder can then be called as `find('byCategoryOrUser', userId: $somevar)`. +You can even include the special named arguments for setting query clauses. +`find('byCategoryOrUser', userId: $somevar, conditions: ['enabled' => true])`. + +A similar change has been applied to the `RepositoryInterface::get()` method: + +``` php +public function view(int $id) +{ + $author = $this->Authors->get($id, [ + 'contain' => ['Books'], + 'finder' => 'latest', + ]); +} +``` + +can now be written as: + +``` php +public function view(int $id) +{ + $author = $this->Authors->get($id, contain: ['Books'], finder: 'latest'); +} +``` + +### TestSuite + +- `IntegrationTestTrait::requestAsJson()` has been added to set JSON headers for the next request. + +### Plugin Installer + +- The plugin installer has been updated to automatically handle class autoloading + for your app plugins. So you can remove the namespace to path mappings for your + plugins from your `composer.json` and just run `composer dumpautoload`. diff --git a/docs/en/appendices/5-0-upgrade-guide.md b/docs/en/appendices/5-0-upgrade-guide.md new file mode 100644 index 0000000000..cf78d9759d --- /dev/null +++ b/docs/en/appendices/5-0-upgrade-guide.md @@ -0,0 +1,70 @@ +# 5.0 Upgrade Guide + +First, check that your application is running on latest CakePHP 4.x version. + +## Fix Deprecation Warnings + +Once your application is running on latest CakePHP 4.x, enable deprecation warnings in **config/app.php**: + +``` text +'Error' => [ + 'errorLevel' => E_ALL, +] +``` + +Now that you can see all the warnings, make sure these are fixed before proceeding with the upgrade. + +Some potentially impactful deprecations you should make sure you have addressed +are: + +- `Table::query()` was deprecated in 4.5.0. Use `selectQuery()`, + `updateQuery()`, `insertQuery()` and `deleteQuery()` instead. + +## Upgrade to PHP 8.1 + +If you are not running on **PHP 8.1 or higher**, you will need to upgrade PHP before updating CakePHP. + +> [!NOTE] +> CakePHP 5.0 requires **a minimum of PHP 8.1**. + + + +## Use the Upgrade Tool + +> [!NOTE] +> The upgrade tool only works on applications running on latest CakePHP 4.x. You cannot run the upgrade tool after updating to CakePHP 5.0. + +Because CakePHP 5 leverages union types and `mixed`, there are many +backwards incompatible changes concerning method signatures and file renames. +To help expedite fixing these tedious changes there is an upgrade CLI tool: + +``` bash +# Install the upgrade tool +git clone https://github.com/cakephp/upgrade +cd upgrade +git checkout 5.x +composer install --no-dev +``` + +With the upgrade tool installed you can now run it on your application or +plugin: + +``` text +bin/cake upgrade rector --rules cakephp50 +bin/cake upgrade rector --rules chronos3 +``` + +## Update CakePHP Dependency + +After applying rector refactorings you need to upgrade CakePHP, its plugins, PHPUnit +and maybe other dependencies in your `composer.json`. +This process heavily depends on your application so we recommend you compare your +`composer.json` with what is present in [cakephp/app](https://github.com/cakephp/app/blob/5.x/composer.json). + +After the version strings are adjusted in your `composer.json` execute +`composer update -W` and check its output. + +## Update app files based upon latest app template + +Next, ensure the rest of your application has been updated to be based upon the +latest version of [cakephp/app](https://github.com/cakephp/app/blob/5.x/). diff --git a/docs/en/appendices/5-1-migration-guide.md b/docs/en/appendices/5-1-migration-guide.md new file mode 100644 index 0000000000..ef59425f86 --- /dev/null +++ b/docs/en/appendices/5-1-migration-guide.md @@ -0,0 +1,179 @@ +# 5.1 Migration Guide + +The 5.1.0 release is a backwards compatible with 5.0. It adds new functionality +and introduces new deprecations. Any functionality deprecated in 5.x will be +removed in 6.0.0. + +## Upgrade Tool + +The [upgrade tool](../appendices/migration-guides) provides rector rules for +automating some of the migration work. Run rector before updating your +`composer.json` dependencies: + +``` text +bin/cake upgrade rector --rules cakephp51 +``` + +## Behavior Changes + +- Connection now creates unique read and write drivers if the keys `read` or + `write` are present in the config regardless of values. +- FormHelper no longer generates `aria-required` attributes on input elements + that also have the `required` attribute set. The `aria-required` attribute + is redundant on these elements and generates HTML validation warnings. If you + are using `aria-required` attribute in styling or scripting you'll need to + update your application. +- Adding associations with duplicate names will now raise exceptions. You can + use `$table->associations()->has()` to conditionally define associations if + required. +- Text Utility and TextHelper methods around truncation and maximum length are using + a UTF-8 character for `ellipsis` instead of `...` legacy characters. +- `TableSchema::setColumnType()` now throws an exception if the specified column + does not exist. +- `PluginCollection::addPlugin()` now throws an exception if a plugin of the same + name is already added. +- `TestCase::loadPlugins()` will now clear out any previously loaded plugins. So + you must specify all plugins required for any subsequent tests. +- The hashing algorithm for `Cache` configurations that use `groups`. Any + keys will have new group prefix hashes generated which will cause cache + misses. Consider an incremental deploy to avoid operating on an entirely cold + cache. +- `FormHelper::getFormProtector()` now returns `null` in addition to its + previous types. This allows dynamic view code to run with fewer errors and + shouldn't impact most applications. +- The default value for `valueSeparator` in `Table::findList()` is now + a single space instead of `;`. +- `ErrorLogger` uses `Psr\Log\LogTrait` now. +- `Database\QueryCompiler::$_orderedUnion` was removed. + +## Deprecations + +### I18n + +- The `_cake_core_` cache config key has been renamed to `_cake_translations_`. + +### Mailer + +- `Mailer::setMessage()` is deprecated. It has unintuitive behavior and very + low usage. + +## New Features + +### Cache + +- `RedisEngine` now supports a `tls` option that enables connecting to redis + over a TLS connection. You can use the `ssl_ca`, `ssl_cert` and + `ssl_key` options to define the TLS context for redis. + +### Command + +- `bin/cake plugin list` has been added to list all available plugins, + their load configuration and version. +- Optional `Command` arguments can now have a `default` value. +- `BannerHelper` was added. This command helper can format text as a banner + with a coloured background and padding. +- Additional default styles for `info.bg`, `warning.bg`, `error.bg` and + `success.bg` were added to `ConsoleOutput`. + +### Console + +- `Arguments::getBooleanOption()` and `Arguments::getMultipleOption()` were added. +- `Arguments::getArgument()` will now raise an exception if an unknown + argument name is provided. This helps prevent mixing up option/argument names. + +### Controller + +- Components can now use the DI container to have dependencies resolved and + provided as constructor parameters just like Controllers and Commands do. + +### Core + +- `PluginConfig` was added. Use this class to get all available plugins, their load config and versions. +- The `toString`, `toInt`, `toBool` functions were added. They give you + a typesafe way to cast request data or other input and return `null` when conversion fails. +- `pathCombine()` was added to help build paths without worrying about duplicate and trailing slashes. +- A new `events` hook was added to the `BaseApplication` as well as the `BasePlugin` class. This hook + is the recommended way to register global event listeners for you application. See [Registering Listeners](../core-libraries/events#registering-event-listeners) + +### Database + +- Support for `point`, `linestring`, `polygon` and `geometry` types were + added. These types are useful when working with geospatial or cartesian + co-ordinates. Sqlite support uses text columns under the hood and lacks + functions to manipulate data as geospatial values. +- `SelectQuery::__debugInfo()` now includes which connection role the query + is for. +- `SelectQuery::intersect()` and `SelectQuery::intersectAll()` were added. + These methods enable queries using `INTERSECT` and `INTERSECT ALL` + conjunctions to be expressed. +- New supports features were added for `intersect`, `intersect-all` and + `set-operations-order-by` features. +- The ability to fetch records without buffering which existed in 4.x has been restored. + Methods `SelectQuery::enableBufferedResults()`, `SelectQuery::disableBufferedResults()` + and `SelectQuery::isBufferedResultsEnabled()` have been re-added. + +### Datasource + +- `RulesChecker::remove()`, `removeCreate()`, `removeUpdate()`, and + `removeDelete()` methods were added. These methods allow you to remove rules + by name. + +### Http + +- `SecurityHeadersMiddleware::setPermissionsPolicy()` was added. This method + adds the ability to define `permissions-policy` header values. +- `Client` now emits `HttpClient.beforeSend` and `HttpClient.afterSend` + events when requests are sent. You can use these events to perform logging, + caching or collect telemetry. +- `Http\Server::terminate()` was added. This method triggers the + `Server.terminate` event which can be used to run logic after the response + has been sent in fastcgi environments. In other environments the + `Server.terminate` event runs *before* the response has been sent. + +### I18n + +- `Number::formatter()` and `currency()` now accept a `roundingMode` + option to override how rounding is done. +- The `toDate`, and `toDateTime` functions were added. They give you + a typesafe way to cast request data or other input and return `null` when + conversion fails. + +### ORM + +- Setting the `preserveKeys` option on association finder queries. This can be + used with `formatResults()` to replace association finder results with an + associative array. +- SQLite columns with names containing `json` can now be mapped to `JsonType`. + This is currently an opt-in feature which is enabled by setting the `ORM.mapJsonTypeForSqlite` + configure value to `true` in your app. + +### TestSuite + +- CakePHP as well as the app template have been updated to use PHPUnit `^10.5.5 || ^11.1.3"`. - `ConnectionHelper` methods are now all static. This class has no state and its methods were updated to be static. - `LogTestTrait` was added. This new trait makes it easy to capture logs in your tests and make assertions on the presence or absence of log messages. - `IntegrationTestTrait::replaceRequest()` was added. + +### Utility + +- `Hash::insert()` and `Hash::remove()` now accept `ArrayAccess` objects along with `array` data. + +### Validation + +- `Validation::enum()` and `Validator::enum()` were added. These validation + methods simplify validating backed enum values. +- `Validation::enumOnly()` and `Validation::enumExcept()` were added to check for specific cases + and further simplify validating backed enum values. + +### View + +- View cells now emit events around their actions `Cell.beforeAction` and + `Cell.afterAction`. +- `NumberHelper::format()` now accepts a `roundingMode` option to override how + rounding is done. + +### Helpers + +- `TextHelper::autoLinkUrls()` has options added for better link label printing: + - `stripProtocol`: Strips `http://` and `https://` from the beginning of the link. Default off. + - `maxLength`: The maximum length of the link label. Default off. + - `ellipsis`: The string to append to the end of the link label. Defaults to UTF8 version. +- `HtmlHelper::meta()` can now create a meta tag containing the current CSRF + token using `meta('csrfToken')`. diff --git a/docs/en/appendices/5-2-migration-guide.md b/docs/en/appendices/5-2-migration-guide.md new file mode 100644 index 0000000000..c787a714d3 --- /dev/null +++ b/docs/en/appendices/5-2-migration-guide.md @@ -0,0 +1,129 @@ +# 5.2 Migration Guide + +The 5.2.0 release is a backwards compatible with 5.0. It adds new functionality +and introduces new deprecations. Any functionality deprecated in 5.x will be +removed in 6.0.0. + +## Upgrade Tool + +The [upgrade tool](../appendices/migration-guides) provides rector rules for +automating some of the migration work. Run rector before updating your +`composer.json` dependencies: + +``` text +bin/cake upgrade rector --rules cakephp52 +``` + +## Behavior Changes + +- `ValidationSet::add()` will now raise errors when a rule is added with + a name that is already defined. This change aims to prevent rules from being + overwritten by accident. +- `Http\Session` will now raise an exception when an invalid session preset is + used. +- `FormProtectionComponent` now raises `Cake\Controller\Exception\FormProtectionException`. This + class is a subclass of `BadRequestException`, and offers the benefit of + being filterable from logging. +- `NumericPaginator::paginate()` now uses the `finder` option even when a `SelectQuery` instance is passed to it. + +## Deprecations + +### Console + +- `Arguments::getMultipleOption()` is deprecated. Use `getArrayOption()` + instead. + +### Datasource + +- The ability to cast an `EntityInterface` instance to string has been deprecated. + You should `json_encode()` the entity instead. +- Mass assigning multiple entity fields using `EntityInterface::set()` is deprecated. + Use `EntityInterface::patch()` instead. For e.g. change usage like + `$entity->set(['field1' => 'value1', 'field2' => 'value2'])` to + `$entity->patch(['field1' => 'value1', 'field2' => 'value2'])`. + +### Event + +- Returning values from event listeners / callbacks is deprecated. Use `$event->setResult()` + instead or `$event->stopPropogation()` to just stop the event propogation. + +### View + +- The `errorClass` option of `FormHelper` has been deprecated in favour of + using a template string. To upgrade move your `errorClass` definition to + a template set. See [Customizing Templates](../views/helpers/form#customizing-templates). + +## New Features + +### Console + +- The `cake counter_cache` command was added. This command can be used to + regenerate counters for models that use `CounterCacheBehavior`. +- `ConsoleIntegrationTestTrait::debugOutput()` makes it easier to debug + integration tests for console commands. +- `ConsoleInputArgument` now supports a `separator` option. This option + allows positional arguments to be delimited with a character sequence like + `,`. CakePHP will split the positional argument into an array when arguments + are parsed. +- `Arguments::getArrayArgumentAt()`, and `Arguments::getArrayArgument()` + were added. These methods allow you to read `separator` delimitered + positional arguments as arrays. +- `ConsoleInputOption` now supports a `separator` option. This option + allows option values to be delimited with a character sequence like + `,`. CakePHP will split the option value into an array when arguments + are parsed. +- `Arguments::getArrayArgumentAt()`, `Arguments::getArrayArgument()`, and + `Arguments::getArrayOption()` + were added. These methods allow you to read `separator` delimitered + positional arguments as arrays. + +### Database + +- The `nativeuuid` type was added. This type enables `uuid` columns to be + used in Mysql connections with MariaDB. In all other drivers, `nativeuuid` + is an alias for `uuid`. +- `Cake\Database\Type\JsonType::setDecodingOptions()` was added. This method + lets you define the value for the `$flags` argument of `json_decode()`. +- `CounterCacheBehavior::updateCounterCache()` was added. This method allows + you to update the counter cache values for all records of the configured + associations. `CounterCacheCommand` was also added to do the same through the + console. +- `Cake\Database\Driver::quote()` was added. This method provides a way to + quote values to be used in SQL queries where prepared statements cannot be + used. + +### Datasource + +- Application rules can now use `Closure` to define the validation message. + This allows you to create dynamic validation messages based on the entity + state and validation rule options. + +### Error + +- Custom exceptions can have specific error handling logic defined in + `ErrorController`. + +### ORM + +- `CounterCacheBehavior::updateCounterCache()` has been added. This method + allows you to update the counter cache values for all records of the configured + associations. +- `BelongsToMany::setJunctionProperty()` and `getJunctionProperty()` were + added. These methods allow you to customize the `_joinData` property that is + used to hydrate junction table records. +- `Table::findOrCreate()` now accepts an array as second argument to directly pass data in. + +### TestSuite + +- `TestFixture::$strictFields` was added. Enabling this property will make + fixtures raise an error if a fixture's record list contains fields that do not + exist in the schema. + +### View + +- `FormHelper::deleteLink()` has been added as convenience wrapper for delete links in + templates using `DELETE` method. +- `HtmlHelper::importmap()` was added. This method allows you to define + import maps for your JavaScript files. +- `FormHelper` now uses the `containerClass` template to apply a class to + the form control div. The default value is `input`. diff --git a/docs/en/appendices/5-3-migration-guide.md b/docs/en/appendices/5-3-migration-guide.md new file mode 100644 index 0000000000..eaa541dc79 --- /dev/null +++ b/docs/en/appendices/5-3-migration-guide.md @@ -0,0 +1,261 @@ +# 5.3 Migration Guide + +The 5.3.0 release is a backwards compatible with 5.0. It adds new functionality +and introduces new deprecations. Any functionality deprecated in 5.x will be +removed in 6.0.0. + +## Upgrade Tool + +The [upgrade tool](../appendices/migration-guides) provides rector rules for +automating some of the migration work. Run rector before updating your +`composer.json` dependencies: + +``` text +bin/cake upgrade rector --rules cakephp53 +``` + +## Upgrade to PHP 8.2 + +If you are not running on **PHP 8.2 or higher**, you will need to upgrade PHP before updating CakePHP. + +> [!NOTE] +> CakePHP 5.3 requires **a minimum of PHP 8.2**. + +## Behavior Changes + +### Core + +- `InstanceConfigTrait::deleteConfig()` was added. For classes using this trait, + you can now use `$this->deleteConfig('key')` instead of `$this->setConfig('key', null)` + +### Database + +- `Query::with()` now accepts an array of expressions to align with other query clauses. + This also allows clearing the expressions with an empty array. + +### ORM + +- `joinWith()` now asserts when the association conflicts with an existing join and will overwrite it. + Because existing code might harmlessly ignore the join or accidentally rely on that behavior, this change is not breaking + in production for CakePHP 5. + +### Validation + +- The signature of `Validator::validate(array $data, bool $newRecord = true, array $context = [])` has now a additional third parameter `$context`. + It can be used to pass necessary context into the validation when marshalling. + +### View + +- The `format()` and `currency()` methods of `NumberHelper` now accept also null as input and can return any default string here. + This allows for easier templates, in particular baked ones. Make sure to adjust any extending helper (plugin or app level) by adding that type. + +## Deprecations + +### Database + +- `Query::newExpr()` is deprecated. Use `Query::expr()` instead. + +### Form + +- `Form::_execute()` is deprecated. You should rename your `_execute` + methods to `process()` which accepts the same parameters and has the same + return type. + +### Http + +- Using `$request->getParam('?')` to get the query params is deprecated. + Use `$request->getQueryParams()` instead. + +### ORM + +- Calling behavior methods on table instances is now deprecated. To call + a method of an attached behavior you need to use + `$table->getBehavior('Sluggable')->slugify()` instead of `$table->slugify()`. +- `EntityTrait::isEmpty()` is deprecated. Use `hasValue()` instead. + +### Plugin + +- Loading of plugins without a plugin class is deprecated. For your existing plugins + which don't have one, you can use the `bin/cake bake plugin MyPlugin --class-only` + command, which will create the file `plugins/MyPlugin/src/MyPlugin.php`. + +### View + +- Passing an array as the first argument to `BreadcrumbsHelper::add()` and + `BreadcrumbsHelper::prepend()` is deprecated. Use `addMany()` and + `prependMany()` instead. + +## New Features + +### Cache + +- Added Redis Cluster support to `RedisEngine`. Configure the `cluster` option + with an array of server addresses to enable cluster mode. +- Several [Cache Events](../core-libraries/caching#cache-events) were added to allow monitoring the caching behavior. + +### Collection + +- `Collection::any()` was added to replace `Collection::some()` with a more familiar name. + +### Command + +- `cake plugin assets symlink` command now supports a `--relative` option to + create relative path symlinks. This is useful when creating symlinks within + containers that use volume mounts. +- `cake server` now supports a `--frankenphp` option that will start the + development server with [FrankenPHP](https://frankenphp.dev/). + +### Console + +- Added `TreeHelper` which outputs an array as a tree such as an array of filesystem + directories as array keys and files as lists under each directory. +- Commands can now implement `getGroup()` to customize how commands are + grouped in `bin/cake -h` output. +- `CommandCollection::replace()` was added. This method allows you to replace + an existing command in the collection without needing to remove and re-add it. + This is particularly useful when using `autoDiscover` and you want to replace + a command with a customized version. + +### Core + +- Added `Configure` attribute to support injecting `Configure` values into + constructor arguments. See ref:\`configure-dependency-injection\`. + +### Database + +- Added support for Entra authentication to SqlServer driver. +- Added `Query::optimizerHint()` which accepts engine-specific optimizer hints. +- Added `Query::getDriver()` helper which returns the `Driver` for the current connection + role by default. +- Added support for `year` column types in MySQL. +- Added support for `inet`, `cidr` and `macaddr` network column types to + postgres driver. +- Added `TypeFactory::getMapped()` to retrieve the mapped class name for a specific type. + This provides a cleaner API compared to using `TypeFactory::getMap()` with a type argument. + +### Error + +- `Debugger` now replaces `ROOT` with the + `Debugger.editorBasePath` Configure value if defined. This improves + debugging workflows within containerized environments. + +### Http + +- The new `RateLimitMiddleware` provides configurable rate limiting for your + application to protect against abuse and ensure fair usage of resources. It + supports multiple identification strategies (IP, user, route, API key), + different rate limiting algorithms (sliding window, fixed window, token bucket), + and advanced features like custom identifiers, request costs, and dynamic limits. +- `UnprocessableContentException` was added. + +### I18n + +- Added `DateTimePeriod` which wraps a php `DatePeriod` and returns `DateTime` + instances when iterating. +- Added `DatePeriod` which wraps a php `DatePeriod` and returns `Date` instances + when iterating. +- Added `toQuarterRange()` method to `DateTime` and `FrozenTime` classes which returns + an array containing the start and end dates of the quarter for the given date. +- Added `Date::getTimestamp()`. This method returns an int of the date's + timestamp. + +### Mailer + +- Added `Message::addAttachment()` for adding attachments to a message. Like + other message methods, it can be accessed via the `Mailer` instance as `$mailer->addAttachment()`. + +### ORM + +- `Table::patchEntity()`, `Table::newEntity()`, `Marshaller::one()` and + `Marshaller::many()` now accept a `strictFields` option that only applies + validation to the fields listed in the `fields` option. +- Added `TableContainer` that you can register in your `Application::services()` to + add dependency injection for your Tables. +- Added `SelectQuery::projectAs()` for projecting query results into Data + Transfer Objects (DTOs) instead of Entity objects. DTOs provide a + memory-efficient alternative (approximately 3x less memory than entities) for + read-only data access. See [Dto Projection](../orm/query-builder#dto-projection). +- Added the `#[CollectionOf]` attribute for declaring the element type of + array properties in DTOs. This enables proper hydration of nested + associations into DTOs. + +### Pagination + +- Added `SortableFieldsBuilder` class enabling fluent configuration of + sortable fields with advanced features. The `sortableFields` option now + accepts a callable that receives a `SortableFieldsBuilder` instance, + allowing you to map friendly sort keys to database fields with multi-column + sorting and direction control. +- Added `SortField` class for defining sort field configurations with + customizable default directions and locked directions (e.g., + `SortField::asc('price')` or `SortField::desc('created', locked: true)`). +- Added support for combined sorting keys in URLs (e.g., `?sort=title-asc` or + `?sort=price-desc`) in addition to the traditional `?sort=field&direction=asc` + format. + +### Routing + +- Added `RouteBuilder::setOptions()` method to set default route options at + the scope level. This allows you to apply options like `_host`, `_https`, + and `_port` to all routes within a scope without repeating them on each + route. Options set at the scope level are inherited by nested scopes and can + be overridden on individual routes. +- `EntityRoute` now handles enum value conversions. This enables you to use + enum backed properties as route parameters. When an enum backed property is + used in routing, the enum's `value` or `name` will be used. +- Added `RedirectTrait`. This trait can be used to create custom redirect route + classes. + +### TestSuite + +- `assertRedirectBack()` added to assert a successful redirect has been made to the same previous URL. +- `assertRedirectBackToReferer()` added to assert a successful redirect has been made to the referer URL. +- `assertFlashMessageContains()` and `assertFlashMessageContainsAt()` were added. These methods enable + substring matching of flash message content. +- `TestFixture::$tableAlias` was added. This property lets you define the + table alias that will be used to load an `ORM\Table` instance for a fixture. + This improves compatibility for schemas that do not closely follow naming conventions. + +### Utility + +- `Text::uuid()` now supports configurable UUID generation. You can set a custom + UUID generator using `Configure::write('Text.uuidGenerator', $closure)` to + integrate your own UUID generation strategy or third-party libraries. + +### Validation + +- `ipOrRange()` validation has has been added to check for an IP or a range (subnet). +- When validating within CakePHP marshalling context, the entity will be passed + into the `context` argument for use inside custom validation rules. This can + be useful when patching partially and then needing to get that data from the + entity instead of the passed data. +- `existsInNullable()` rule has been added. This rule allows `null` values + in nullable composite foreign keys, which is semantically correct for optional + relationships. Use `$rules->existsInNullable(['author_id', 'site_id'], 'SiteAuthors')` instead of `existsIn()` when you want to permit null values + in foreign keys. + +### View + +- `HtmlHelper::scriptStart()` and `scriptEnd()` now allow simple + wrapping script tags (``) around inline JavaScript. This + enables syntax highlighting in many editors to work. The wrapping script tag + will be removed and replaced with a script tag generated by the helper. +- `FormHelper` now supports a new option `nestedCheckboxAndRadio`. + By default, the helper generates inputs of type checkbox and radio nested + inside their label. Setting the `nestedCheckboxAndRadio` option to `false` + will turn off the nesting. +- `ViewBuilder::setConfigMergeStrategy()` was added to control how view options + are merged with the View class's default configuration. Available strategies are + `ViewBuilder::MERGE_DEEP` (recursive merge, default) and `ViewBuilder::MERGE_SHALLOW` + (simple array merge). This is useful when you want to replace array values in view + options rather than deep merging them. +- `ViewBuilder::getConfigMergeStrategy()` was added to retrieve the current merge + strategy setting. +- `PaginatorHelper::limitControl()` now automatically respects the + `maxLimit` configuration from the paginator, filtering out any limit options + that exceed it. A new `steps` option was added to automatically generate limit + options in multiples of a specific value (e.g., `['steps' => 10]` generates + 10, 20, 30... up to maxLimit). +- `BreadcrumbsHelper::addMany()` and `BreadcrumbsHelper::prependMany()` + were added. These methods allow adding multiple breadcrumbs at once with support + for shared options that apply to all crumbs. diff --git a/docs/en/appendices/cakephp-development-process.md b/docs/en/appendices/cakephp-development-process.md new file mode 100644 index 0000000000..9853bf98ca --- /dev/null +++ b/docs/en/appendices/cakephp-development-process.md @@ -0,0 +1,58 @@ +# CakePHP Development Process + +CakePHP projects broadly follow [semver](https://semver.org/). This means that: + +- Releases are numbered in the form of **A.B.C** +- **A** releases are *major releases*. They contain breaking changes and will + require non-trivial amounts of work to upgrade to from a lower **A** release. +- **A.B** releases are *feature releases*. Each version will be backwards + compatible but may introduce new deprecations. If a breaking change is + absolutely required it will be noted in the migration guide for that release. +- **A.B.C** releases are *patch* releases. They should be backwards compatible + with the previous patch release. The exception to this rule is if a security + issue is discovered and the only solution is to break an existing API. + +See the [Backwards Compatibility Guide](../contributing/backwards-compatibility) for what we consider to be +backwards compatible and a breaking changes. + +## Major Releases + +Major releases introduce new features and can remove functionality deprecated in +an earlier release. These releases live in `next` branches that match their +version number such as `5.next`. Once released they are promoted into `master` +and then `5.next` branch is used for future feature releases. + +## Feature Releases + +Feature releases are where new features or extensions to existing features are +shipped. Each release series receiving updates will have a `next` branch. For +example `4.next`. If you would like to contribute a new feature please target +these branches. + +## Patch Releases + +Patch releases fix bugs in existing code/documentation and should always be +compatible with earlier patch releases from the same feature release. These +releases are created from the stable branches. Stable branches are often named +after the release series such as `3.x`. + +## Release Cadence + +- *Major Releases* are delivered approximately every two to three years. This timeframe + forces us to be deliberate and considerate with our breaking changes and gives + time for the community to keep up without feeling like they are being left + behind. +- *Feature Releases* are delivered every five to eight months. +- *Patch Releases* Are initially delivered every two weeks. As a feature release + matures this cadence relaxes to a monthly schedule. + +## Deprecation Policy + +Before a feature can be removed in a major release it needs to be deprecated. +When a behavior is deprecated in release **A.x** it will continue to work for +remainder of all **A.x** releases. Deprecations are generally indicated via PHP +warnings. You can enable deprecation warnings by adding `E_USER_DEPRECATED` to +your application's `Error.level` value. + +Once deprecated behavior is not removed until the next major release. For +example behavior deprecated in `4.1` will be removed in `5.0`. diff --git a/docs/en/appendices/glossary.md b/docs/en/appendices/glossary.md new file mode 100644 index 0000000000..fcbec91f45 --- /dev/null +++ b/docs/en/appendices/glossary.md @@ -0,0 +1,112 @@ +# Glossary + +
+ +CDN +Content Delivery Network. A 3rd party vendor you can pay to help +distribute your content to data centers around the world. This helps +put your static assets closer to geographically distributed users. + +columns +Used in the ORM when referring to the table columns in an database +table. + +CSRF +Cross Site Request Forgery. Prevents replay attacks, double +submissions and forged requests from other domains. + +DI Container +In `Application::services()` you can configure application services +and their dependencies. Application services are automatically injected +into Controller actions, and Command Constructors. See +[Dependency Injection](../development/dependency-injection). + +DSN +Data Source Name. A connection string format that is formed like a URI. +CakePHP supports DSNs for Cache, Database, Log and Email connections. + +dot notation +Dot notation defines an array path, by separating nested levels with `.` +For example: + + Cache.default.engine + +Would point to the following value: + +``` php +[ + 'Cache' => [ + 'default' => [ + 'engine' => 'File' + ] + ] +] +``` + +DRY +Don't repeat yourself. Is a principle of software development aimed at +reducing repetition of information of all kinds. In CakePHP DRY is used +to allow you to code things once and re-use them across your +application. + +fields +A generic term used to describe both entity properties, or database +columns. Often used in conjunction with the FormHelper. + +HTML attributes +An array of key =\> values that are composed into HTML attributes. For example: + +``` text +// Given +['class' => 'my-class', 'target' => '_blank'] + +// Would generate +class="my-class" target="_blank" +``` + +If an option can be minimized or accepts its name as the value, then `true` +can be used: + +``` text +// Given +['checked' => true] + +// Would generate +checked="checked" +``` + +PaaS +Platform as a Service. Platform as a Service providers will provide +cloud based hosting, database and caching resources. Some popular +providers include Heroku, EngineYard and PagodaBox + +properties +Used when referencing columns mapped onto an ORM entity. + +plugin syntax +Plugin syntax refers to the dot separated class name indicating classes +are part of a plugin: + +``` text +// The plugin is "DebugKit", and the class name is "Toolbar". +'DebugKit.Toolbar' + +// The plugin is "AcmeCorp/Tools", and the class name is "Toolbar". +'AcmeCorp/Tools.Toolbar' +``` + +routes.php +A file in the `config/` directory that contains routing configuration. +This file is included before each request is processed. +It should connect all the routes your application needs so +requests can be routed to the correct controller + action. + +routing array +An array of attributes that are passed to `Router::url()`. +They typically look like: + +``` php +['controller' => 'Posts', 'action' => 'view', 5] +``` + +
diff --git a/docs/en/appendices/migration-guides.md b/docs/en/appendices/migration-guides.md new file mode 100644 index 0000000000..cf2cc6b4f6 --- /dev/null +++ b/docs/en/appendices/migration-guides.md @@ -0,0 +1,34 @@ +# Migration Guides + +Migration guides contain information regarding the new features introduced in +each version and the migration path between 5.x minor releases. + +## Upgrade Tool + +CakePHP provides an [upgrade tool](https://github.com/cakephp/upgrade) that +automates many code changes using [Rector](https://getrector.com/). The tool +has rulesets for each minor version to help automate tedious code changes like +method renames and signature updates. + +To use the upgrade tool: + +``` text +# Install the upgrade tool +git clone https://github.com/cakephp/upgrade +cd upgrade +git checkout 5.x +composer install --no-dev + +# Run rector with the desired ruleset +bin/cake upgrade rector --rules cakephp51 +``` + +Run rector before updating your `composer.json` dependencies +to ensure the tool can resolve class names correctly. + +- [5 0 Upgrade Guide](5-0-upgrade-guide) +- [5 0 Migration Guide](5-0-migration-guide) +- [5 1 Migration Guide](5-1-migration-guide) +- [5 2 Migration Guide](5-2-migration-guide) +- [5 3 Migration Guide](5-3-migration-guide) +- [Phpunit10](phpunit10) diff --git a/docs/en/appendices/phpunit10.md b/docs/en/appendices/phpunit10.md new file mode 100644 index 0000000000..a6606f000e --- /dev/null +++ b/docs/en/appendices/phpunit10.md @@ -0,0 +1,68 @@ +# PHPUnit 10 Upgrade + +With CakePHP 5 the minimum PHPUnit version has changed from `^8.5 || ^9.3` to `^10.1`. This introduces a few breaking changes from PHPUnit as well as from CakePHP's side. + +## phpunit.xml adjustments + +It is recommended to let PHPUnit update its configuration file via the following command: + + vendor/bin/phpunit --migrate-configuration + +> [!NOTE] +> Make sure you are already on PHPUnit 10 via `vendor/bin/phpunit --version` before executing this command! + +With this command out of the way your `phpunit.xml` already has most of the recommended changes present. + +### New event system + +PHPUnit 10 removed the old hook system and introduced a new [Event system](https://docs.phpunit.de/en/10.5/extending-phpunit.html#extending-the-test-runner) +which requires the following code in your `phpunit.xml` to be adjusted from: + +``` php + + + +``` + +to: + +``` php + + + +``` + +## `->withConsecutive()` has been removed + +You can convert the removed `->withConsecutive()` method to a +working interim solution like you can see here: + +``` php +->withConsecutive(['firstCallArg'], ['secondCallArg']) +``` + +should be converted to: + +``` php +->with( + ...self::withConsecutive(['firstCallArg'], ['secondCallArg']) +) +``` + +the static `self::withConsecutive()` method has been added via the `Cake\TestSuite\PHPUnitConsecutiveTrait` +to the base `Cake\TestSuite\TestCase` class so you don't have to manually add that trait to your Testcase classes. + +## data providers have to be static + +If your testcases leverage the data provider feature of PHPUnit then +you have to adjust your data providers to be static: + +``` php +public function myProvider(): array +``` + +should be converted to: + +``` text +public static function myProvider(): array +``` diff --git a/docs/en/bake.md b/docs/en/bake.md new file mode 100644 index 0000000000..1104d57fbe --- /dev/null +++ b/docs/en/bake.md @@ -0,0 +1,3 @@ +# Bake Console + +This page has [moved](https://book.cakephp.org/bake/2.x/en/index.html). diff --git a/docs/en/bake/development.md b/docs/en/bake/development.md new file mode 100644 index 0000000000..ee1fa8222a --- /dev/null +++ b/docs/en/bake/development.md @@ -0,0 +1,3 @@ +# Extending Bake + +This page has [moved](https://book.cakephp.org/bake/2.x/en/development.html). diff --git a/docs/en/bake/usage.md b/docs/en/bake/usage.md new file mode 100644 index 0000000000..7be48ac6c0 --- /dev/null +++ b/docs/en/bake/usage.md @@ -0,0 +1,3 @@ +# Code Generation with Bake + +This page has [moved](https://book.cakephp.org/bake/2.x/en/usage.html). diff --git a/docs/en/chronos.md b/docs/en/chronos.md new file mode 100644 index 0000000000..f83a14f3d7 --- /dev/null +++ b/docs/en/chronos.md @@ -0,0 +1,3 @@ +# Chronos + +This page has [moved](https://book.cakephp.org/chronos/2.x/en/). diff --git a/docs/en/console-commands.md b/docs/en/console-commands.md new file mode 100644 index 0000000000..18bc6f30ab --- /dev/null +++ b/docs/en/console-commands.md @@ -0,0 +1,164 @@ +# Console Commands + +In addition to a web framework, CakePHP also provides a console framework for +creating command line tools & applications. Console applications are ideal for +handling a variety of background & maintenance tasks that leverage your existing +application configuration, models, plugins and domain logic. + +CakePHP provides several console tools for interacting with CakePHP features +like i18n and routing that enable you to introspect your application and +generate related files. + +## The CakePHP Console + +The CakePHP Console uses a dispatcher-type system to load commands, parse +their arguments and invoke the correct command. While the examples below use +bash the CakePHP console is compatible with any \*nix shell and windows. + +A CakePHP application contains **src/Command** directory that contain its commands. +It also comes with an executable in the **bin** directory: + +``` bash +$ cd /path/to/app +$ bin/cake +``` + +> [!NOTE] +> For Windows, the command needs to be `bin\cake` (note the backslash). + +Running the Console with no arguments will list out available commands. You +could then run the any of the listed commands by using its name: + +``` bash +# run server command +bin/cake server + +# run migrations command +bin/cake migrations -h + +# run bake (with plugin prefix) +bin/cake bake.bake -h +``` + +Plugin commands can be invoked without a plugin prefix if the commands's name +does not overlap with an application or framework command. In the case that two +plugins provide a command with the same name, the first loaded plugin will get +the short alias. You can always use the `plugin.command` format to +unambiguously reference a command. + +## Console Applications + +By default CakePHP will automatically discover all the commands in your +application and its plugins. You may want to reduce the number of exposed +commands, when building standalone console applications. You can use your +`Application`'s `console()` hook to limit which commands are exposed and +rename commands that are exposed: + +``` php +// in src/Application.php +namespace App; + +use App\Command\UserCommand; +use App\Command\VersionCommand; +use Cake\Console\CommandCollection; +use Cake\Http\BaseApplication; + +class Application extends BaseApplication +{ + public function console(CommandCollection $commands): CommandCollection + { + // Add by classname + $commands->add('user', UserCommand::class); + + // Add instance + $commands->add('version', new VersionCommand()); + + return $commands; + } +} +``` + +In the above example, the only commands available would be `help`, `version` +and `user`. See the [Plugin Commands](plugins#plugin-commands) section for how to add commands in +your plugins. + +> [!NOTE] +> When adding multiple commands that use the same Command class, the `help` +> command will display the shortest option. + +## Renaming Commands + +There are cases where you will want to rename commands, to create nested +commands or subcommands. While the default auto-discovery of commands will not +do this, you can register your commands to create any desired naming. + +You can customize the command names by defining each command in your plugin: + +``` php +public function console(CommandCollection $commands): CommandCollection +{ + // Add commands with nested naming + $commands->add('user dump', UserDumpCommand::class); + $commands->add('user:show', UserShowCommand::class); + + // Rename a command entirely + $commands->add('lazer', UserDeleteCommand::class); + + return $commands; +} +``` + +When overriding the `console()` hook in your application, remember to +call `$commands->autoDiscover()` to add commands from CakePHP, your +application, and plugins. + +If you need to rename/remove any attached commands, you can use the +`Console.buildCommands` event on your application event manager to modify the +available commands. + +## Commands + +See the [Command Objects](console-commands/commands) chapter on how to create your first +command. Then learn more about commands: + +- [Command Objects](console-commands/commands) +- [Command Input/Output](console-commands/input-output) +- [Option Parsers](console-commands/option-parsers) +- [Running Shells as Cron Jobs](console-commands/cron-jobs) + +## CakePHP Provided Commands + +- [Cache Tool](console-commands/cache) +- [Completion Tool](console-commands/completion) +- [CounterCache Tool](console-commands/counter-cache) +- [I18N Tool](console-commands/i18n) +- [Plugin Tool](console-commands/plugin) +- [Schema Cache Tool](console-commands/schema-cache) +- [Routes Tool](console-commands/routes) +- [Server Tool](console-commands/server) +- [Interactive Console (REPL)](console-commands/repl) + +## Routing in the Console Environment + +In command-line interface (CLI), specifically your console commands, +`env('HTTP_HOST')` and other webbrowser specific environment variables are not +set. + +If you generate reports or send emails that make use of `Router::url()` those +will contain the default host `http://localhost/` and thus resulting in +invalid URLs. In this case you need to specify the domain manually. +You can do that using the Configure value `App.fullBaseUrl` from your +bootstrap or config, for example. + +For sending emails, you should provide Email class with the host you want to +send the email with: + +``` php +use Cake\Mailer\Email; + +$email = new Email(); +$email->setDomain('www.example.org'); +``` + +This asserts that the generated message IDs are valid and fit to the domain the +emails are sent from. diff --git a/docs/en/console-commands/cache.md b/docs/en/console-commands/cache.md new file mode 100644 index 0000000000..3367a8cc2f --- /dev/null +++ b/docs/en/console-commands/cache.md @@ -0,0 +1,15 @@ +# Cache Tool + +To help you better manage cached data from a CLI environment, a console command +is available for clearing cached data your application has: + +``` text +// Clear one cache config +bin/cake cache clear + +// Clear all cache configs +bin/cake cache clear_all + +// Clear one cache group +bin/cake cache clear_group +``` diff --git a/docs/en/console-commands/commands.md b/docs/en/console-commands/commands.md new file mode 100644 index 0000000000..b8aa442d6f --- /dev/null +++ b/docs/en/console-commands/commands.md @@ -0,0 +1,660 @@ +# Command Objects + +`class` Cake\\Console\\**Command** + +CakePHP comes with a number of built-in commands for speeding up your +development, and automating routine tasks. You can use these same libraries to +create commands for your application and plugins. + +## Creating a Command + +Let's create our first Command. For this example, we'll create a +simple Hello world command. In your application's **src/Command** directory create +**HelloCommand.php**. Put the following code inside it: + +``` php +out('Hello world.'); + + return static::CODE_SUCCESS; + } +} +``` + +Command classes must implement an `execute()` method that does the bulk of +their work. This method is called when a command is invoked. Lets call our first +command application directory, run: + +``` bash +bin/cake hello +``` + +You should see the following output: + + Hello world. + +Our `execute()` method isn't very interesting let's read some input from the +command line: + +``` php +addArgument('name', [ + 'help' => 'What is your name', + ]); + + return $parser; + } + + public function execute(Arguments $args, ConsoleIo $io): int + { + $name = $args->getArgument('name'); + $io->out("Hello {$name}."); + + return static::CODE_SUCCESS; + } +} +``` + +After saving this file, you should be able to run the following command: + +``` bash +bin/cake hello jillian + +# Outputs +Hello jillian +``` + +## Changing the Default Command Name + +CakePHP will use conventions to generate the name your commands use on the +command line. If you want to overwrite the generated name implement the +`defaultName()` method in your command: + +``` text +public static function defaultName(): string +{ + return 'oh_hi'; +} +``` + +The above would make our `HelloCommand` accessible by `cake oh_hi` instead +of `cake hello`. + +## Defining Arguments and Options + +As we saw in the last example, we can use the `buildOptionParser()` hook +method to define arguments. We can also define options. For example, we could +add a `yell` option to our `HelloCommand`: + +``` php +// ... +protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser +{ + $parser + ->addArgument('name', [ + 'help' => 'What is your name', + ]) + ->addOption('yell', [ + 'help' => 'Shout the name', + 'boolean' => true, + ]); + + return $parser; +} + +public function execute(Arguments $args, ConsoleIo $io): int +{ + $name = $args->getArgument('name'); + if ($args->getOption('yell')) { + $name = mb_strtoupper($name); + } + $io->out("Hello {$name}."); + + return static::CODE_SUCCESS; +} +``` + +See the [Option Parsers](../console-commands/option-parsers) section for more information. + +## Creating Output + +Commands are provided a `ConsoleIo` instance when executed. This object allows +you to interact with `Cake\Console\ConsoleIo::out()` and +`Cake\Console\ConsoleIo::err()` to emit on `stdout`, and +`stderr`. Files can be created with overwrite confirmation with +`Cake\Console\ConsoleIo::createFile()`. :ref:\`command-helpers +provide 'macros' for output generation. See the +[Command Input/Output](../console-commands/input-output) section for more information. + +## Using Models in Commands + +You'll often need access to your application's business logic in console +commands. You can load models in commands, just as you would in a controller +using `$this->fetchTable()` since command use the `LocatorAwareTrait`: + +``` php +addArgument('name', [ + 'help' => 'What is your name' + ]); + + return $parser; + } + + public function execute(Arguments $args, ConsoleIo $io): int + { + $name = $args->getArgument('name'); + $user = $this->fetchTable()->findByUsername($name)->first(); + + $io->out(print_r($user, true)); + + return static::CODE_SUCCESS; + } +} +``` + +The above command, will fetch a user by username and display the information +stored in the database. + +## Exit Codes and Stopping Execution + +When your commands hit an unrecoverable error you can use the `abort()` method +to terminate execution: + +``` php +// ... +public function execute(Arguments $args, ConsoleIo $io): int +{ + $name = $args->getArgument('name'); + if (strlen($name) < 5) { + // Halt execution, output to stderr, and set exit code to 1 + $io->error('Name must be at least 4 characters long.'); + $this->abort(); + } + + return static::CODE_SUCCESS; +} +``` + +You can also use `abort()` on the `$io` object to emit a message and code: + +``` php +public function execute(Arguments $args, ConsoleIo $io): int +{ + $name = $args->getArgument('name'); + if (strlen($name) < 5) { + // Halt execution, output to stderr, and set exit code to 99 + $io->abort('Name must be at least 4 characters long.', 99); + } + + return static::CODE_SUCCESS; +} +``` + +You can pass any desired exit code into `abort()`. + +> [!TIP] +> Avoid exit codes 64 - 78, as they have specific meanings described by +> `sysexits.h`. Avoid exit codes above 127, as these are used to indicate +> process exit by signal, such as SIGKILL or SIGSEGV. +> +> You can read more about conventional exit codes in the sysexit manual page +> on most Unix systems (`man sysexits`), or the `System Error Codes` help +> page in Windows. + +## Calling other Commands + +You may need to call other commands from your command. You can use +`executeCommand` to do that: + +``` php +// You can pass an array of CLI options and arguments. +$this->executeCommand(OtherCommand::class, ['--verbose', 'deploy']); + +// Can pass an instance of the command if it has constructor args +$command = new OtherCommand($otherArgs); +$this->executeCommand($command, ['--verbose', 'deploy']); +``` + +> [!NOTE] +> When calling `executeCommand()` in a loop, it is recommended to pass in the +> parent command's `ConsoleIo` instance as the optional 3rd argument to +> avoid a potential "open files" limit that could occur in some environments. + +## Setting Command Description + +You may want to set a command description via: + +``` php +class UserCommand extends Command +{ + public static function getDescription(): string + { + return 'My custom description'; + } +} +``` + +This will show your description in the Cake CLI: + +``` bash +bin/cake + +App: + - user + └─── My custom description +``` + +As well as in the help section of your command: + +``` bash +cake user --help +My custom description + +Usage: +cake user [-h] [-q] [-v] +``` + +## Grouping Commands + +By default in the help output CakePHP will group commands into core, app, and +plugin groups. You can customize the grouping of commands by implementing +`getGroup()`: + +``` php +class CleanupCommand extends Command +{ + public static function getGroup(): string + { + return 'maintenance'; + } +} +``` + +::: info Added in version 5.3.0 +Custom grouping support was added. +::: + + + +## Testing Commands + +To make testing console applications easier, CakePHP comes with a +`ConsoleIntegrationTestTrait` trait that can be used to test console applications +and assert against their results. + +To get started testing your console application, create a test case that uses the +`Cake\TestSuite\ConsoleIntegrationTestTrait` trait. This trait contains a method +`exec()` that is used to execute your command. You can pass the same string +you would use in the CLI to this method. + +Let's start with a very simple command, located in +**src/Command/UpdateTableCommand.php**: + +``` php +namespace App\Command; + +use Cake\Command\Command; +use Cake\Console\Arguments; +use Cake\Console\ConsoleIo; +use Cake\Console\ConsoleOptionParser; + +class UpdateTableCommand extends Command +{ + protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser + { + $parser->setDescription('My cool console app'); + + return $parser; + } +} +``` + +To write an integration test for this command, we would create a test case in +**tests/TestCase/Command/UpdateTableTest.php** that uses the +`Cake\TestSuite\ConsoleIntegrationTestTrait` trait. This command doesn't do much at the +moment, but let's just test that our command's description is displayed in `stdout`: + +``` php +namespace App\Test\TestCase\Command; + +use Cake\TestSuite\ConsoleIntegrationTestTrait; +use Cake\TestSuite\TestCase; + +class UpdateTableCommandTest extends TestCase +{ + use ConsoleIntegrationTestTrait; + + public function testDescriptionOutput() + { + $this->exec('update_table --help'); + $this->assertOutputContains('My cool console app'); + } +} +``` + +Our test passes! While this is very trivial example, it shows that creating an +integration test case for console applications can follow command line +conventions. Let's continue by adding more logic to our command: + +``` php +namespace App\Command; + +use Cake\Command\Command; +use Cake\Console\Arguments; +use Cake\Console\ConsoleIo; +use Cake\Console\ConsoleOptionParser; +use Cake\I18n\DateTime; + +class UpdateTableCommand extends Command +{ + protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser + { + $parser + ->setDescription('My cool console app') + ->addArgument('table', [ + 'help' => 'Table to update', + 'required' => true + ]); + + return $parser; + } + + public function execute(Arguments $args, ConsoleIo $io): int + { + $table = $args->getArgument('table'); + $this->fetchTable($table)->updateQuery() + ->set([ + 'modified' => new DateTime() + ]) + ->execute(); + + return static::CODE_SUCCESS; + } +} +``` + +This is a more complete command that has required options and relevant logic. +Modify your test case to the following snippet of code: + +``` php +namespace Cake\Test\TestCase\Command; + +use Cake\Command\Command; +use Cake\I18n\DateTime; +use Cake\TestSuite\ConsoleIntegrationTestTrait; +use Cake\TestSuite\TestCase; + +class UpdateTableCommandTest extends TestCase +{ + use ConsoleIntegrationTestTrait; + + protected $fixtures = [ + // assumes you have a UsersFixture + 'app.Users', + ]; + + public function testDescriptionOutput() + { + $this->exec('update_table --help'); + $this->assertOutputContains('My cool console app'); + } + + public function testUpdateModified() + { + $now = new DateTime('2017-01-01 00:00:00'); + DateTime::setTestNow($now); + + $this->loadFixtures('Users'); + + $this->exec('update_table Users'); + $this->assertExitCode(Command::CODE_SUCCESS); + + $user = $this->getTableLocator()->get('Users')->get(1); + $this->assertSame($user->modified->timestamp, $now->timestamp); + + DateTime::setTestNow(null); + } +} +``` + +As you can see from the `testUpdateModified` method, we are testing that our +command updates the table that we are passing as the first argument. First, we +assert that the command exited with the proper status code, `0`. Then we check +that our command did its work, that is, updated the table we provided and set +the `modified` column to the current time. + +Remember, `exec()` will take the same string you type into your CLI, so you +can include options and arguments in your command string. + +### Testing Interactive Commands + +Consoles are often interactive. Testing interactive commands with the +`Cake\TestSuite\ConsoleIntegrationTestTrait` trait only requires passing the +inputs you expect as the second parameter of `exec()`. They should be +included as an array in the order that you expect them. + +Continuing with our example command, let's add an interactive confirmation. +Update the command class to the following: + +``` php +namespace App\Command; + +use Cake\Command\Command; +use Cake\Console\Arguments; +use Cake\Console\ConsoleIo; +use Cake\Console\ConsoleOptionParser; +use Cake\I18n\DateTime; + +class UpdateTableCommand extends Command +{ + protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser + { + $parser + ->setDescription('My cool console app') + ->addArgument('table', [ + 'help' => 'Table to update', + 'required' => true + ]); + + return $parser; + } + + public function execute(Arguments $args, ConsoleIo $io): int + { + $table = $args->getArgument('table'); + if ($io->ask('Are you sure?', 'n', ['y', 'n']) !== 'y') { + $io->error('You need to be sure.'); + $this->abort(); + } + $this->fetchTable($table)->updateQuery() + ->set([ + 'modified' => new DateTime() + ]) + ->execute(); + + return static::CODE_SUCCESS; + } +} +``` + +Now that we have an interactive command, we can add a test case that tests +that we receive the proper response, and one that tests that we receive an +incorrect response. Remove the `testUpdateModified` method and, add the following methods to +**tests/TestCase/Command/UpdateTableCommandTest.php**: + +``` php +public function testUpdateModifiedSure() +{ + $now = new DateTime('2017-01-01 00:00:00'); + DateTime::setTestNow($now); + + $this->loadFixtures('Users'); + + $this->exec('update_table Users', ['y']); + $this->assertExitCode(Command::CODE_SUCCESS); + + $user = $this->getTableLocator()->get('Users')->get(1); + $this->assertSame($user->modified->timestamp, $now->timestamp); + + DateTime::setTestNow(null); +} + +public function testUpdateModifiedUnsure() +{ + $user = $this->getTableLocator()->get('Users')->get(1); + $original = $user->modified->timestamp; + + $this->exec('my_console best_framework', ['n']); + $this->assertExitCode(Command::CODE_ERROR); + $this->assertErrorContains('You need to be sure.'); + + $user = $this->getTableLocator()->get('Users')->get(1); + $this->assertSame($original, $user->timestamp); +} +``` + +In the first test case, we confirm the question, and records are updated. In the +second test we don't confirm and records are not updated, and we can check that +our error message was written to `stderr`. + +### Assertion methods + +The `Cake\TestSuite\ConsoleIntegrationTestTrait` trait provides a number of +assertion methods that make help assert against console output: + +``` php +// assert that the command exited as success +$this->assertExitSuccess(); + +// assert that the command exited as an error +$this->assertExitError(); + +// assert that the command exited with the expected code +$this->assertExitCode($expected); + +// assert that stdout contains a string +$this->assertOutputContains($expected); + +// assert that stderr contains a string +$this->assertErrorContains($expected); + +// assert that stdout matches a regular expression +$this->assertOutputRegExp($expected); + +// assert that stderr matches a regular expression +$this->assertErrorRegExp($expected); +``` + +### Debug Helpers + +You can use `debugOutput()` to output the exit code, stdout and stderr of the +last run command: + +``` php +$this->exec('update_table Users'); +$this->assertExitCode(Command::CODE_SUCCESS); +$this->debugOutput(); +``` + +::: info Added in version 4.2.0 +The `debugOutput()` method was added. +::: + +## Lifecycle Callbacks + +Like Controllers, Commands offer lifecycle events that allow you to observe +the framework calling your application code. Commands have: + +- `Command.beforeExecute` is called before a command's `execute()` method. + The event is passed the `Arguments` parameter as `args` and the + `ConsoleIo` parameter as `io`. This event cannot be stopped or have its + result replaced. +- `Command.afterExecute` is called after a command's `execute()` method is + complete. The event contains `Arguments` as `args`, `ConsoleIo` as + `io` and the command result as `result`. This event cannot be stopped or + have its result replaced. + +::: info Added in version 5.3.0 +The `beforeExecute()` and `afterExecute()` hook methods were added. +::: + +### beforeExecute() + +`method` Cake\\Console\\Command::**beforeExecute**(EventInterface $event, Arguments $args, ConsoleIo $io): void + +Called before the `execute()` method runs. Useful for initialization and +validation: + +``` php +use Cake\Event\EventInterface; + +class MyCommand extends Command +{ + public function beforeExecute(EventInterface $event, Arguments $args, ConsoleIo $io): void + { + parent::beforeExecute($event); + + $io->out('Starting command execution'); + + if (!$this->checkPrerequisites()) { + $io->abort('Prerequisites not met'); + } + } +} +``` + +### afterExecute() + +`method` Cake\\Console\\Command::**afterExecute**(EventInterface $event, Arguments $args, ConsoleIo $io): void + +Called after the `execute()` method completes. Useful for cleanup and +logging: + +``` php +public function afterExecute(EventInterface $event, Arguments $args, ConsoleIo $io, mixed $result): void +{ + parent::afterExecute($event); + + $this->cleanup(); + $io->out('Command execution completed'); +} +``` diff --git a/docs/en/console-commands/completion.md b/docs/en/console-commands/completion.md new file mode 100644 index 0000000000..a4605c36ed --- /dev/null +++ b/docs/en/console-commands/completion.md @@ -0,0 +1,174 @@ +# Completion Tool + +Working with the console gives the developer a lot of possibilities but having +to completely know and write those commands can be tedious. Especially when +developing new shells where the commands differ per minute iteration. The +Completion Shells aids in this matter by providing an API to write completion +scripts for shells like bash, zsh, fish etc. + +## Sub Commands + +The Completion Shell consists of a number of sub commands to assist the +developer creating its completion script. Each for a different step in the +autocompletion process. + +### Commands + +For the first step commands outputs the available Shell Commands, including +plugin name when applicable. (All returned possibilities, for this and the other +sub commands, are separated by a space.) For example: + + bin/cake Completion commands + +Returns: + + acl api bake command_list completion console i18n schema server test testsuite upgrade + +Your completion script can select the relevant commands from that list to +continue with. (For this and the following sub commands.) + +### subCommands + +Once the preferred command has been chosen subCommands comes in as the second +step and outputs the possible sub command for the given shell command. For +example: + + bin/cake Completion subcommands bake + +Returns: + + controller db_config fixture model plugin project test view + +### options + +As the third and final options outputs options for the given (sub) command as +set in getOptionParser. (Including the default options inherited from Shell.) +For example: + + bin/cake Completion options bake + +Returns: + + --help -h --verbose -v --quiet -q --everything --connection -c --force -f --plugin -p --prefix --theme -t + +You can also pass an additional argument being the shell sub-command : it will +output the specific options of this sub-command. + +## How to enable Bash autocompletion for the CakePHP Console + +First, make sure the **bash-completion** library is installed. If not, you do it +with the following command: + + apt-get install bash-completion + +Create a file named **cake** in **/etc/bash_completion.d/** and put the +[Bash Completion File Content](#bash-completion-file-content) inside it. + +Save the file, then restart your console. + +> [!NOTE] +> If you are using MacOS X, you can install the **bash-completion** library +> using **homebrew** with the command `brew install bash-completion`. +> The target directory for the **cake** file will be +> **/usr/local/etc/bash_completion.d/**. + + + +### Bash Completion file content + +This is the code you need to put inside the **cake** file in the correct location +in order to get autocompletion when using the CakePHP console: + +``` bash +# +# Bash completion file for CakePHP console +# + +_cake() +{ + local cur prev opts cake + COMPREPLY=() + cake="${COMP_WORDS[0]}" + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ "$cur" == -* ]] ; then + if [[ ${COMP_CWORD} = 1 ]] ; then + opts=$(${cake} Completion options) + elif [[ ${COMP_CWORD} = 2 ]] ; then + opts=$(${cake} Completion options "${COMP_WORDS[1]}") + else + opts=$(${cake} Completion options "${COMP_WORDS[1]}" "${COMP_WORDS[2]}") + fi + + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + + if [[ ${COMP_CWORD} = 1 ]] ; then + opts=$(${cake} Completion commands) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + + if [[ ${COMP_CWORD} = 2 ]] ; then + opts=$(${cake} Completion subcommands $prev) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + if [[ $COMPREPLY = "" ]] ; then + _filedir + return 0 + fi + return 0 + fi + + opts=$(${cake} Completion fuzzy "${COMP_WORDS[@]:1}") + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + if [[ $COMPREPLY = "" ]] ; then + _filedir + return 0 + fi + return 0; +} + +complete -F _cake cake bin/cake +``` + +## Using autocompletion + +Once enabled, the autocompletion can be used the same way than for other +built-in commands, using the **TAB** key. +Three type of autocompletion are provided. The following output are from a fresh CakePHP install. + +### Commands + +Sample output for commands autocompletion: + +``` bash +$ bin/cake +bake i18n schema_cache routes +console migrations plugin server +``` + +### Subcommands + +Sample output for subcommands autocompletion: + +``` bash +$ bin/cake bake +behavior helper command +cell mailer command_helper +component migration template +controller migration_snapshot test +fixture model +form plugin +``` + +### Options + +Sample output for subcommands options autocompletion: + +``` bash +$ bin/cake bake - +-c --everything --force --help --plugin -q -t -v +--connection -f -h -p --prefix --quiet --theme --verbose +``` diff --git a/docs/en/console-commands/counter-cache.md b/docs/en/console-commands/counter-cache.md new file mode 100644 index 0000000000..ad3eab7f80 --- /dev/null +++ b/docs/en/console-commands/counter-cache.md @@ -0,0 +1,24 @@ +# CounterCache Tool + +The CounterCacheCommand provides a CLI tool for rebuilding the counter caches +in your application and plugin models. It can be used in maintenance and +recovery operations, or to populate new counter caches added to your +application. + +``` bash +bin/cake counter_cache --assoc Comments Articles +``` + +This would rebuild the `Comments` related counters on the `Articles` table. +For very large tables you may need to rebuild counters in batches. You can use +the `--limit` and `--page` options to incrementally rebuild counter state. + +``` bash +bin/cake counter_cache --assoc Comments --limit 100 --page 2 Articles +``` + +When `limit` and `page` are used, records will be ordered by the table's +primary key. + +::: info Added in version 5.2.0 +::: diff --git a/docs/en/console-commands/cron-jobs.md b/docs/en/console-commands/cron-jobs.md new file mode 100644 index 0000000000..3d4ed76dc2 --- /dev/null +++ b/docs/en/console-commands/cron-jobs.md @@ -0,0 +1,34 @@ +# Running Shells as Cron Jobs + +A common thing to do with a shell is making it run as a cronjob to +clean up the database once in a while or send newsletters. This is +trivial to setup, for example: + +``` text +*/5 * * * * cd /full/path/to/root && bin/cake myshell myparam +# * * * * * command to execute +# │ │ │ │ │ +# │ │ │ │ │ +# │ │ │ │ \───── day of week (0 - 6) (0 to 6 are Sunday to Saturday, +# | | | | or use names) +# │ │ │ \────────── month (1 - 12) +# │ │ \─────────────── day of month (1 - 31) +# │ \──────────────────── hour (0 - 23) +# \───────────────────────── min (0 - 59) +``` + +You can see more info here: + +> [!TIP] +> Use `-q` (or --quiet) to silence any output for cronjobs. + +## Cron Jobs on Shared Hosting + +On some shared hostings `cd /full/path/to/root && bin/cake mycommand myparam` +might not work. Instead you can use +`php /full/path/to/root/bin/cake.php mycommand myparam`. + +> [!NOTE] +> register_argc_argv has to be turned on by including `register_argc_argv = 1` in your php.ini. If you cannot change register_argc_argv globally, +> you can tell the cron job to use your own configuration by +> specifying it with `-d register_argc_argv=1` parameter. Example: `php -d register_argc_argv=1 /full/path/to/root/bin/cake.php myshell myparam` diff --git a/docs/en/console-commands/i18n.md b/docs/en/console-commands/i18n.md new file mode 100644 index 0000000000..d91c4990cf --- /dev/null +++ b/docs/en/console-commands/i18n.md @@ -0,0 +1,81 @@ +# I18N Tool + +The i18n features of CakePHP use [po files](https://en.wikipedia.org/wiki/GNU_gettext) +as their translation source. PO files integrate with commonly used translation tools +like [Poedit](https://www.poedit.net/). + +The i18n commands provides a quick way to generate po template files. +These templates files can then be given to translators so they can translate the +strings in your application. Once you have translations done, pot files can be +merged with existing translations to help update your translations. + +## Generating POT Files + +POT files can be generated for an existing application using the `extract` +command. This command will scan your entire application for `__()` style +function calls, and extract the message string. Each unique string in your +application will be combined into a single POT file: + +``` bash +bin/cake i18n extract +``` + +The above will run the extraction command. The result of this command will be the +file **resources/locales/default.pot**. You use the pot file as a template for creating +po files. If you are manually creating po files from the pot file, be sure to +correctly set the `Plural-Forms` header line. + +### Generating POT Files for Plugins + +You can generate a POT file for a specific plugin using: + +``` bash +bin/cake i18n extract --plugin +``` + +This will generate the required POT files used in the plugins. + +### Extracting from multiple folders at once + +Sometimes, you might need to extract strings from more than one directory of +your application. For instance, if you are defining some strings in the +`config/` directory of your application, you probably want to extract strings +from this directory as well as from the `src/` directory. You can do it by +using the `--paths` option. It takes a comma-separated list of absolute paths +to extract: + +``` bash +bin/cake i18n extract --paths /var/www/app/config,/var/www/app/src +``` + +### Excluding Folders + +You can pass a comma separated list of folders that you wish to be excluded. +Any path containing a path segment with the provided values will be ignored: + +``` bash +bin/cake i18n extract --exclude vendor,tests +``` + +### Skipping Overwrite Warnings for Existing POT Files + +By adding `--overwrite`, the shell script will no longer warn you if a POT +file already exists and will overwrite by default: + +``` bash +bin/cake i18n extract --overwrite +``` + +### Extracting Messages from the CakePHP Core Libraries + +By default, the extract shell script will ask you if you like to extract +the messages used in the CakePHP core libraries. Set `--extract-core` to yes +or no to set the default behavior: + +``` bash +bin/cake i18n extract --extract-core yes + +// or + +bin/cake i18n extract --extract-core no +``` diff --git a/docs/en/console-commands/input-output.md b/docs/en/console-commands/input-output.md new file mode 100644 index 0000000000..0a7a77cb54 --- /dev/null +++ b/docs/en/console-commands/input-output.md @@ -0,0 +1,412 @@ +# Command Input/Output + +`class` Cake\\Console\\**ConsoleIo** + +CakePHP provides the `ConsoleIo` object to commands so that they can +interactively read user input and output information to the user. + + + +## Command Helpers + +Formatting console output can be tedious and lead to maintenance issues. To +enable better re-use and testability of console formatting code, CakePHP command +helpers provide 'macros' for console formatting logic. Command Helpers can be +accessed and used from any command: + +``` php +// Output some data as a table. +$io->helper('Table')->output($data); + +// Get a helper from a plugin. +$io->helper('Plugin.HelperName')->output($data); +``` + +You can also get instances of helpers and call any public methods, to manipulate +state and generate updated output: + +``` php +// Get and use the Progress Helper. +$progress = $io->helper('Progress'); +$progress->increment(10); +$progress->draw(); +``` + +## Creating Helpers + +While CakePHP comes with a few command helpers you can create more in your +application or plugins. As an example, we'll create a simple helper to generate +fancy headings. First create the **src/Command/Helper/HeadingHelper.php** and put +the following in it: + +``` php +_io->out($marker . ' ' . $args[0] . ' ' . $marker); + } +} +``` + +We can then use this new helper in one of our shell commands by calling it: + +``` php +// With ### on either side +$this->helper('Heading')->output(['It works!']); + +// With ~~~~ on either side +$this->helper('Heading')->output(['It works!', '~', 4]); +``` + +Helpers generally implement the `output()` method which takes an array of +parameters. However, because Console Helpers are vanilla classes they can +implement additional methods that take any form of arguments. + +> [!NOTE] +> Helpers can also live in `src/Shell/Helper` for backwards compatibility. + +## Built-In Helpers + +### Table Helper + +The TableHelper assists in making well formatted ASCII art tables. Using it is +pretty simple: + +``` php +$data = [ + ['Header 1', 'Header', 'Long Header'], + ['short', 'Longish thing', 'short'], + ['Longer thing', 'short', 'Longest Value'], +]; +$io->helper('Table')->output($data); + +// Outputs ++--------------+---------------+---------------+ +| Header 1 | Header | Long Header | ++--------------+---------------+---------------+ +| short | Longish thing | short | +| Longer thing | short | Longest Value | ++--------------+---------------+---------------+ +``` + +You can use the `` formatting tag in tables to right align +content: + +``` php +$data = [ + ['Name', 'Total Price'], + ['Cake Mix', '1.50'], +]; +$io->helper('Table')->output($data); + +// Outputs ++----------+-------------+ +| Name 1 | Total Price | ++----------+-------------+ +| Cake Mix | 1.50 | ++----------+-------------+ +``` + +### Progress Helper + +The ProgressHelper can be used in two different ways. The simple mode lets you +provide a callback that is invoked until the progress is complete: + +``` php +$io->helper('Progress')->output(['callback' => function ($progress) { + // Do work here. + $progress->increment(20); + $progress->draw(); +}]); +``` + +You can control the progress bar more by providing additional options: + +- `total` The total number of items in the progress bar. Defaults + to 100. +- `width` The width of the progress bar. Defaults to 80. +- `callback` The callback that will be called in a loop to advance the + progress bar. + +An example of all the options in use would be: + +``` php +$io->helper('Progress')->output([ + 'total' => 10, + 'width' => 20, + 'callback' => function ($progress) { + $progress->increment(2); + $progress->draw(); + } +]); +``` + +The progress helper can also be used manually to increment and re-render the +progress bar as necessary: + +``` php +$progress = $io->helper('Progress'); +$progress->init([ + 'total' => 10, + 'width' => 20, +]); + +$progress->increment(4); +$progress->draw(); +``` + +### Banner Helper + +The `BannerHelper` can be used to format one or more lines of text into +a banner with a background and horizontal padding: + +``` php +$io->helper('Banner') + ->withPadding(5) + ->withStyle('success.bg') + ->output(['Work complete']); +``` + +::: info Added in version 5.1.0 +The `BannerHelper` was added in 5.1 +::: + +## Getting User Input + +`method` Cake\\Console\\ConsoleIo::**ask**($question, $choices = null, $default = null): string + +When building interactive console applications you'll need to get user input. +CakePHP provides a way to do this: + +``` php +// Get arbitrary text from the user. +$color = $io->ask('What color do you like?'); + +// Get a choice from the user. +$selection = $io->askChoice('Red or Green?', ['R', 'G'], 'R'); +``` + +Selection validation is case-insensitive. + +## Creating Files + +`method` Cake\\Console\\ConsoleIo::**createFile**($path, $contents): bool + +Creating files is often important part of many console commands that help +automate development and deployment. The `createFile()` method gives you +a simple interface for creating files with interactive confirmation: + +``` php +// Create a file with confirmation on overwrite +$io->createFile('bower.json', $stuff); + +// Force overwriting without asking +$io->createFile('bower.json', $stuff, true); +``` + +## Creating Output + +### ConsoleIo::out() + +Writing to `stdout` is done using the `out()` method: + +``` php +// Write to stdout +$io->out('Normal message'); +``` + +### ConsoleIo::err() + +Writing to `stderr` is done using the `err()` method: + +``` php +// Write to stderr +$io->err('Error message'); +``` + +In addition to vanilla output methods, CakePHP provides wrapper methods that +style output with appropriate ANSI colors: + +``` php +// Green text on stdout +$io->success('Success message'); + +// Cyan text on stdout +$io->info('Informational text'); + +// Blue text on stdout +$io->comment('Additional context'); + +// Red text on stderr +$io->error('Error text'); + +// Yellow text on stderr +$io->warning('Warning text'); +``` + +Color formatting will automatically be disabled if `posix_isatty` returns +true, or if the `NO_COLOR` environment variable is set. + +`ConsoleIo` provides two convenience methods regarding the output level: + +``` php +// Would only appear when verbose output is enabled (-v) +$io->verbose('Verbose message'); + +// Would appear at all levels. +$io->quiet('Quiet message'); +``` + +You can also create blank lines or draw lines of dashes: + +``` php +// Output 2 newlines +$io->out($io->nl(2)); + +// Draw a horizontal line +$io->hr(); +``` + +Lastly, you can update the current line of text on the screen: + +``` php +$io->out('Counting down'); +$io->out('10', 0); +for ($i = 9; $i > 0; $i--) { + sleep(1); + $io->overwrite($i, 0, 2); +} +``` + +> [!NOTE] +> It is important to remember, that you cannot overwrite text +> once a new line has been output. + + + +## Output Levels + +Console applications often need different levels of verbosity. For example, when +running as a cron job, most output is un-necessary. You can use output levels to +flag output appropriately. The user of the shell, can then decide what level of +detail they are interested in by setting the correct flag when calling the +command. There are 3 levels: + +- `QUIET` - Only absolutely important information should be marked for quiet + output. +- `NORMAL` - The default level, and normal usage. +- `VERBOSE` - Mark messages that may be too noisy for everyday use, but + helpful for debugging as `VERBOSE`. + +You can mark output as follows: + +``` php +// Would appear at all levels. +$io->out('Quiet message', 1, ConsoleIo::QUIET); +$io->quiet('Quiet message'); + +// Would not appear when quiet output is toggled. +$io->out('normal message', 1, ConsoleIo::NORMAL); +$io->out('loud message', 1, ConsoleIo::VERBOSE); +$io->verbose('Verbose output'); + +// Would only appear when verbose output is enabled. +$io->out('extra message', 1, ConsoleIo::VERBOSE); +$io->verbose('Verbose output'); +``` + +You can control the output level of commands, by using the `--quiet` and +`--verbose` options. These options are added by default, and allow you to +consistently control output levels inside your CakePHP comands. + +The `--quiet` and `--verbose` options also control how logging data is +output to stdout/stderr. Normally info and higher log messages are output to +stdout/stderr. When `--verbose` is used, debug logs will be output to stdout. +When `--quiet` is used, only warning and higher log messages will be output to +stderr. + +## Styling Output + +Styling output is done by including tags - just like HTML - in your output. +These tags will be replaced with the correct ansi code sequence, or +stripped if you are on a console that doesn't support ansi codes. There +are several built-in styles, and you can create more. The built-in ones are + +- `success` Success messages. Green text. +- `error` Error messages. Red text. +- `warning` Warning messages. Yellow text. +- `info` Informational messages. Cyan text. +- `comment` Additional text. Blue text. +- `question` Text that is a question, added automatically by shell. +- `info.bg` White background with cyan text. +- `warning.bg` Yellow background with black text. +- `error.bg` Red background with black text. +- `success.bg` Green background with black text. + +You can create additional styles using `$io->setStyle()`. To declare a +new output style you could do: + +``` php +$io->setStyle('flashy', ['text' => 'magenta', 'blink' => true]); +``` + +This would then allow you to use a `` tag in your shell output, and if +ansi colors are enabled, the following would be rendered as blinking magenta +text `$this->out('Whoooa Something went wrong');`. When +defining styles you can use the following colors for the `text` and +`background` attributes: + +- black +- blue +- cyan +- green +- magenta +- red +- white +- yellow + +You can also use the following options as boolean switches, setting them to a +truthy value enables them. + +- blink +- bold +- reverse +- underline + +Adding a style makes it available on all instances of ConsoleOutput as well, +so you don't have to redeclare styles for both stdout and stderr objects. + +::: info Changed in version 5.1.0 +The `info.bg`, `warning.bg`, `error.bg`, and `success.bg` were added. +::: + +## Turning Off Coloring + +Although coloring is pretty, there may be times when you want to turn it off, +or force it on: + +``` php +$io->outputAs(ConsoleOutput::RAW); +``` + +The above will put the output object into raw output mode. In raw output mode, +no styling is done at all. There are three modes you can use. + +- `ConsoleOutput::COLOR` - Output with color escape codes in place. +- `ConsoleOutput::PLAIN` - Plain text output, known style tags will be + stripped from the output. +- `ConsoleOutput::RAW` - Raw output, no styling or formatting will be done. + This is a good mode to use if you are outputting XML or, want to debug why + your styling isn't working. + +By default on \*nix systems ConsoleOutput objects default to color output. +On Windows systems, plain output is the default unless the `ANSICON` +environment variable is present. diff --git a/docs/en/console-commands/option-parsers.md b/docs/en/console-commands/option-parsers.md new file mode 100644 index 0000000000..4ae45e25e3 --- /dev/null +++ b/docs/en/console-commands/option-parsers.md @@ -0,0 +1,378 @@ +# Option Parsers + +`class` Cake\\Console\\**ConsoleOptionParser** + +Console applications typically take options and arguments as the primary way to +get information from the terminal into your commands. + +## Defining an OptionParser + +Commands and Shells provide a `buildOptionParser($parser)` hook method that +you can use to define the options and arguments for your commands: + +``` php +protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser +{ + // Define your options and arguments. + + // Return the completed parser + return $parser; +} +``` + +Shell classes use the `getOptionParser()` hook method to define their option +parser: + +``` php +public function getOptionParser() +{ + // Get an empty parser from the framework. + $parser = parent::getOptionParser(); + + // Define your options and arguments. + + // Return the completed parser + return $parser; +} +``` + +## Using Arguments + +`method` Cake\\Console\\ConsoleOptionParser::**addArgument**($name, $params = []) + +Positional arguments are frequently used in command line tools, +and `ConsoleOptionParser` allows you to define positional +arguments as well as make them required. You can add arguments +one at a time with `$parser->addArgument();` or multiple at once +with `$parser->addArguments();`: + +``` php +$parser->addArgument('model', ['help' => 'The model to bake']); +``` + +You can use the following options when creating an argument: + +- `help` The help text to display for this argument. +- `required` Whether this parameter is required. +- `index` The index for the arg, if left undefined the argument will be put + onto the end of the arguments. If you define the same index twice the + first option will be overwritten. +- `choices` An array of valid choices for this argument. If left empty all + values are valid. An exception will be raised when parse() encounters an + invalid value. +- `separator` A character sequence that separates arguments that should be + parsed into an array. + +Arguments that have been marked as required will throw an exception when +parsing the command if they have been omitted. So you don't have to +handle that in your shell. + +::: info Added in version 5.2.0 +The `separator` option was added. +::: + +### Adding Multiple Arguments + +`method` Cake\\Console\\ConsoleOptionParser::**addArguments**(array $args) + +If you have an array with multiple arguments you can use +`$parser->addArguments()` to add multiple arguments at once. : + +``` php +$parser->addArguments([ + 'node' => ['help' => 'The node to create', 'required' => true], + 'parent' => ['help' => 'The parent node', 'required' => true], +]); +``` + +As with all the builder methods on ConsoleOptionParser, addArguments +can be used as part of a fluent method chain. + +### Validating Arguments + +When creating positional arguments, you can use the `required` flag, to +indicate that an argument must be present when a shell is called. +Additionally you can use `choices` to force an argument to be from a list of +valid choices: + +``` php +$parser->addArgument('type', [ + 'help' => 'The type of node to interact with.', + 'required' => true, + 'choices' => ['aro', 'aco'], +]); +``` + +The above will create an argument that is required and has validation on the +input. If the argument is either missing, or has an incorrect value an exception +will be raised and the shell will be stopped. + +## Using Options + +`method` Cake\\Console\\ConsoleOptionParser::**addOption**($name, array $options = []) + +Options or flags are used in command line tools to provide unordered key/value +arguments for your commands. Options can define both verbose and short aliases. +They can accept a value (e.g `--connection=default`) or be boolean options +(e.g `--verbose`). Options are defined with the `addOption()` method: + +``` php +$parser->addOption('connection', [ + 'short' => 'c', + 'help' => 'connection', + 'default' => 'default', +]); +``` + +The above would allow you to use either `cake myshell --connection=other`, +`cake myshell --connection other`, or `cake myshell -c other` +when invoking the shell. + +Boolean switches do not accept or consume values, and their presence just +enables them in the parsed parameters: + +``` php +$parser->addOption('no-commit', ['boolean' => true]); +``` + +This option when used like `cake mycommand --no-commit something` would have +a value of `true`, and 'something' would be a treated as a positional +argument. + +When creating options you can use the following options to define the behavior +of the option: + +- `short` - The single letter variant for this option, leave undefined for + none. +- `help` - Help text for this option. Used when generating help for the + option. +- `default` - The default value for this option. If not defined the default + will be `true`. +- `boolean` - The option uses no value, it's just a boolean switch. + Defaults to `false`. +- `multiple` - The option can be provided multiple times. The parsed option + will be an array of values when this option is enabled. +- `separator` - A character sequence that the option value is split into an + array with. +- `choices` - An array of valid choices for this option. If left empty all + values are valid. An exception will be raised when parse() encounters an + invalid value. + +::: info Added in version 5.2.0 +The `separator` option was added. +::: + +### Adding Multiple Options + +`method` Cake\\Console\\ConsoleOptionParser::**addOptions**(array $options) + +If you have an array with multiple options you can use `$parser->addOptions()` +to add multiple options at once. : + +``` php +$parser->addOptions([ + 'node' => ['short' => 'n', 'help' => 'The node to create'], + 'parent' => ['short' => 'p', 'help' => 'The parent node'], +]); +``` + +As with all the builder methods on ConsoleOptionParser, addOptions can be used +as part of a fluent method chain. + +### Validating Options + +Options can be provided with a set of choices much like positional arguments +can be. When an option has defined choices, those are the only valid choices +for an option. All other values will raise an `InvalidArgumentException`: + +``` php +$parser->addOption('accept', [ + 'help' => 'What version to accept.', + 'choices' => ['working', 'theirs', 'mine'], +]); +``` + +### Using Boolean Options + +Options can be defined as boolean options, which are useful when you need to +create some flag options. Like options with defaults, boolean options always +include themselves into the parsed parameters. When the flags are present they +are set to `true`, when they are absent they are set to `false`: + +``` php +$parser->addOption('verbose', [ + 'help' => 'Enable verbose output.', + 'boolean' => true +]); +``` + +The following option would always have a value in the parsed parameter. When not +included its default value would be `false`, and when defined it will be +`true`. + +### Building a ConsoleOptionParser from an Array + +`method` Cake\\Console\\ConsoleOptionParser::**buildFromArray**($spec): static + +Option parsers can also be defined as arrays. Within the array, you can define +keys for `arguments`, `options`, `description` and `epilog`. The values +for arguments, and options, should follow the format that +`Cake\Console\ConsoleOptionParser::addArguments()` and +`Cake\Console\ConsoleOptionParser::addOptions()` use. You can also +use `buildFromArray` on its own, to build an option parser: + +``` php +public function getOptionParser() +{ + return ConsoleOptionParser::buildFromArray([ + 'description' => [ + __("Use this command to grant ACL permissions. Once executed, the "), + __("ARO specified (and its children, if any) will have ALLOW access "), + __("to the specified ACO action (and the ACO's children, if any).") + ], + 'arguments' => [ + 'aro' => ['help' => __('ARO to check.'), 'required' => true], + 'aco' => ['help' => __('ACO to check.'), 'required' => true], + 'action' => ['help' => __('Action to check')], + ], + ]); +} +``` + +### Merging Option Parsers + +`method` Cake\\Console\\ConsoleOptionParser::**merge**($spec) + +When building a group command, you maybe want to combine several parsers for +this: + +``` php +$parser->merge($anotherParser); +``` + +Note that the order of arguments for each parser must be the same, and that +options must also be compatible for it work. So do not use keys for different +things. + +## Getting Help from Shells + +By defining your options and arguments with the option parser CakePHP can +automatically generate rudimentary help information and add a `--help` and +`-h` to each of your commands. Using one of these options will allow you to +see the generated help content: + +``` bash +bin/cake bake --help +bin/cake bake -h +``` + +Would both generate the help for bake. You can also get help for nested +commands: + +``` bash +bin/cake bake model --help +bin/cake bake model -h +``` + +The above would get you the help specific to bake's model command. + +### Getting Help as XML + +When building automated tools or development tools that need to interact with +CakePHP shell commands, it's nice to have help available in a machine parse-able format. +By providing the `xml` option when requesting help you can have help content +returned as XML: + +``` bash +cake bake --help xml +cake bake -h xml +``` + +The above would return an XML document with the generated help, options, and +arguments for the selected shell. A sample XML document would +look like: + +``` xml + + + bake fixture + Generate fixtures for use with the test suite. You can use + `bake fixture all` to bake all fixtures. + + Omitting all arguments and options will enter into an interactive + mode. + + + + + + + + + + + + + + + + +``` + +## Customizing Help Output + +You can further enrich the generated help content by adding a description, and +epilog. + +### Set the Description + +`method` Cake\\Console\\ConsoleOptionParser::**setDescription**($text) + +The description displays above the argument and option information. By passing +in either an array or a string, you can set the value of the description: + +``` php +// Set multiple lines at once +$parser->setDescription(['line one', 'line two']); + +// Read the current value +$parser->getDescription(); +``` + +### Set the Epilog + +`method` Cake\\Console\\ConsoleOptionParser::**setEpilog**($text) + +Gets or sets the epilog for the option parser. The epilog is displayed after the +argument and option information. By passing in either an array or a string, you +can set the value of the epilog: + +``` php +// Set multiple lines at once +$parser->setEpilog(['line one', 'line two']); + +// Read the current value +$parser->getEpilog(); +``` diff --git a/docs/en/console-commands/plugin.md b/docs/en/console-commands/plugin.md new file mode 100644 index 0000000000..0848f03db6 --- /dev/null +++ b/docs/en/console-commands/plugin.md @@ -0,0 +1,58 @@ + + +# Plugin Tool + +The plugin tool allows you to load and unload plugins via the command prompt. +If you need help, run: + +``` bash +bin/cake plugin --help +``` + +## Loading Plugins + +Via the `Load` task you are able to load plugins in your +**config/bootstrap.php**. You can do this by running: + +``` bash +bin/cake plugin load MyPlugin +``` + +This will add the following to your **src/Application.php**: + +``` php +// In the bootstrap method add: +$this->addPlugin('MyPlugin'); +``` + +## Unloading Plugins + +You can unload a plugin by specifying its name: + +``` bash +bin/cake plugin unload MyPlugin +``` + +This will remove the line `$this->addPlugin('MyPlugin',...)` from +**src/Application.php**. + +## Plugin Assets + +CakePHP by default serves plugins assets using the `AssetMiddleware` middleware. +While this is a good convenience, it is recommended to symlink / copy +the plugin assets under app's webroot so that they can be directly served by the +web server without invoking PHP. You can do this by running: + +``` bash +bin/cake plugin assets symlink +``` + +Running the above command will symlink all plugins assets under app's webroot. +On Windows, which doesn't support symlinks, the assets will be copied in +respective folders instead of being symlinked. + +You can symlink assets of one particular plugin by specifying its name: + +``` bash +bin/cake plugin assets symlink MyPlugin +``` diff --git a/docs/en/console-commands/repl.md b/docs/en/console-commands/repl.md new file mode 100644 index 0000000000..bc0cabc9a2 --- /dev/null +++ b/docs/en/console-commands/repl.md @@ -0,0 +1,52 @@ +# Interactive Console (REPL) + +CakePHP offers +[REPL(Read Eval Print Loop) plugin](https://github.com/cakephp/repl) to let +you explore some CakePHP and your application in an interactive console. + +> [!NOTE] +> The plugin was shipped with the CakePHP app skeleton before 4.3. + +You can start the interactive console using: + +``` bash +bin/cake console +``` + +This will bootstrap your application and start an interactive console. At this +point you can interact with your application code and execute queries using your +application's models: + +``` bash +bin/cake console + +>>> $articles = Cake\Datasource\FactoryLocator::get('Table')->get('Articles'); +// object(Cake\ORM\Table)( +// +// ) +>>> $articles->find()->all(); +``` + +Since your application has been bootstrapped you can also test routing using the +REPL: + +``` php +>>> Cake\Routing\Router::parse('/articles/view/1'); +// [ +// 'controller' => 'Articles', +// 'action' => 'view', +// 'pass' => [ +// 0 => '1' +// ], +// 'plugin' => NULL +// ] +``` + +You can also test generating URLs: + +``` php +>>> Cake\Routing\Router::url(['controller' => 'Articles', 'action' => 'edit', 99]); +// '/articles/edit/99' +``` + +To quit the REPL you can use `CTRL-C` or by typing `exit`. diff --git a/docs/en/console-commands/routes.md b/docs/en/console-commands/routes.md new file mode 100644 index 0000000000..c0f3607c3e --- /dev/null +++ b/docs/en/console-commands/routes.md @@ -0,0 +1,35 @@ +# Routes Tool + +The routes tool provides a simple to use CLI interface for testing and debugging +routes. You can use it to test how routes are parsed, and what URLs routing +parameters will generate. + +## Getting a List of all Routes + +``` bash +bin/cake routes +``` + +## Testing URL parsing + +You can quickly see how a URL will be parsed using the `check` method: + +``` bash +bin/cake routes check /articles/edit/1 +``` + +If your route contains any query string parameters remember to surround the URL +in quotes: + +``` bash +bin/cake routes check "/articles/?page=1&sort=title&direction=desc" +``` + +## Testing URL Generation + +You can see the URL a `routing array` will generate using the +`generate` method: + +``` bash +bin/cake routes generate controller:Articles action:edit 1 +``` diff --git a/docs/en/console-commands/schema-cache.md b/docs/en/console-commands/schema-cache.md new file mode 100644 index 0000000000..5fe13e4172 --- /dev/null +++ b/docs/en/console-commands/schema-cache.md @@ -0,0 +1,29 @@ +# Schema Cache Tool + +The SchemaCacheCommand provides a simple CLI tool for managing your application's +metadata caches. In deployment situations it is helpful to rebuild the metadata +cache in-place without clearing the existing cache data. You can do this by +running: + +``` bash +bin/cake schema_cache build --connection default +``` + +This will rebuild the metadata cache for all tables on the `default` +connection. If you only need to rebuild a single table you can do that by +providing its name: + +``` bash +bin/cake schema_cache build --connection default articles +``` + +In addition to building cached data, you can use the SchemaCacheShell to remove +cached metadata as well: + +``` bash +# Clear all metadata +bin/cake schema_cache clear + +# Clear a single table +bin/cake schema_cache clear articles +``` diff --git a/docs/en/console-commands/server.md b/docs/en/console-commands/server.md new file mode 100644 index 0000000000..a92629ab82 --- /dev/null +++ b/docs/en/console-commands/server.md @@ -0,0 +1,26 @@ +# Server Tool + +The `ServerCommand` lets you stand up a simple webserver using the built in PHP +webserver. While this server is *not* intended for production use it can +be handy in development when you want to quickly try an idea out and don't want +to spend time configuring Apache or Nginx. You can start the server command with: + +``` bash +bin/cake server +``` + +You should see the server boot up and attach to port 8765. You can visit the +CLI server by visiting `http://localhost:8765` +in your web-browser. You can close the server by pressing `CTRL-C` in your +terminal. + +> [!NOTE] +> Try `bin/cake server -H 0.0.0.0` if the server is unreachable from other hosts. + +## Changing the Port and Document Root + +You can customize the port and document root using options: + +``` bash +bin/cake server --port 8080 --document_root path/to/app +``` diff --git a/docs/en/contents.md b/docs/en/contents.md new file mode 100644 index 0000000000..f825b6d1c0 --- /dev/null +++ b/docs/en/contents.md @@ -0,0 +1,79 @@ +# Contents + +### Preface + +- [CakePHP at a Glance](intro) +- [Quick Start Guide](quickstart) +- [Migration Guides](appendices/migration-guides) +- [Tutorials & Examples](tutorials-and-examples) +- [Contributing](contributing) +- [Release Policy](release-policy) + +### Getting Started + +- [Installation](installation) +- [Configuration](development/configuration) +- [Application](development/application) +- [Dependency Injection](development/dependency-injection) +- [Routing](development/routing) +- [Request & Response Objects](controllers/request-response) +- [Middleware](controllers/middleware) +- [Controllers](controllers) +- [Views](views) +- [Database Access & ORM](orm) + +### Using CakePHP + +- [Caching](core-libraries/caching) +- [Console Commands](console-commands) +- [Debugging](development/debugging) +- [Deployment](deployment) +- [Mailer](core-libraries/email) +- [Error & Exception Handling](development/errors) +- [Events System](core-libraries/events) +- [Internationalization & Localization](core-libraries/internationalization-and-localization) +- [Logging](core-libraries/logging) +- [Modelless Forms](core-libraries/form) +- [Pagination](controllers/pagination) +- [Plugins](plugins) +- [REST](development/rest) +- [Security](security) +- [Sessions](development/sessions) +- [Testing](development/testing) +- [Validation](core-libraries/validation) + +### Utility Classes + +- [App Class](core-libraries/app) +- [Collections](core-libraries/collections) +- [Hash](core-libraries/hash) +- [Http Client](core-libraries/httpclient) +- [Inflector](core-libraries/inflector) +- [Number](core-libraries/number) +- [Plugin Class](core-libraries/plugin) +- [Registry Objects](core-libraries/registry-objects) +- [Text](core-libraries/text) +- [Date & Time](core-libraries/time) +- [Xml](core-libraries/xml) + +### Plugins & Packages + +- [Standalone Packages](standalone-packages) +- [Authentication](https://book.cakephp.org/authentication/3/) +- [Authorization](https://book.cakephp.org/authorization/3/) +- [Bake](https://book.cakephp.org/bake/3/) +- [Debug Kit](https://book.cakephp.org/debugkit/5/) +- [Migrations](https://book.cakephp.org/migrations/4/) +- [Elasticsearch](https://book.cakephp.org/elasticsearch/4/) +- [Phinx](https://book.cakephp.org/phinx/0/en/) +- [Chronos](https://book.cakephp.org/chronos/3/) +- [Queue](https://book.cakephp.org/queue/2/) + +### Other + +- [Constants & Functions](core-libraries/global-constants-and-functions) +- [Appendices](appendices) + +
+ +
diff --git a/docs/en/contributing.md b/docs/en/contributing.md new file mode 100644 index 0000000000..d6c4fb4aaf --- /dev/null +++ b/docs/en/contributing.md @@ -0,0 +1,10 @@ +# Contributing + +There are a number of ways you can contribute to CakePHP. The following sections +cover the various ways you can contribute to CakePHP: + +- [Documentation](contributing/documentation) +- [Tickets](contributing/tickets) +- [Code](contributing/code) +- [Coding Standards](contributing/cakephp-coding-conventions) +- [Backwards Compatibility Guide](contributing/backwards-compatibility) diff --git a/docs/en/contributing/backwards-compatibility.md b/docs/en/contributing/backwards-compatibility.md new file mode 100644 index 0000000000..58b01c7b7a --- /dev/null +++ b/docs/en/contributing/backwards-compatibility.md @@ -0,0 +1,298 @@ +# Backwards Compatibility Guide + +Ensuring that you can upgrade your applications easily and smoothly is important +to us. That's why we only break compatibility at major release milestones. +You might be familiar with [semantic versioning](https://semver.org/), which is +the general guideline we use on all CakePHP projects. In short, semantic +versioning means that only major releases (such as 2.0, 3.0, 4.0) can break +backwards compatibility. Minor releases (such as 2.1, 3.1, 3.2) may introduce new +features, but are not allowed to break compatibility. Bug fix releases (such as 2.1.2, +3.0.1) do not add new features, but fix bugs or enhance performance only. + +> [!NOTE] +> Deprecations are removed with the next major version of the framework. +> It is advised that you adapt to deprecations as they are introduced to +> ensure future upgrades are easier. + +To clarify what changes you can expect in each release tier we have more +detailed information for developers using CakePHP, and for developers working on +CakePHP that helps set expectations of what can be done in minor releases. Major +releases can have as many breaking changes as required. + +## Migration Guides + +For each major and minor release, the CakePHP team will provide a migration +guide. These guides explain the new features and any breaking changes that are +in each release. They can be found in the [Appendices](../appendices) section of the +cookbook. + +## Using CakePHP + +If you are building your application with CakePHP, the following guidelines +explain the stability you can expect. + +### Interfaces + +Outside of major releases, interfaces provided by CakePHP will **not** have any +existing methods changed. New methods may be added, but no existing methods will +be changed. + +### Classes + +Classes provided by CakePHP can be constructed and have their public methods and +properties used by application code and outside of major releases backwards +compatibility is ensured. + +> [!NOTE] +> Some classes in CakePHP are marked with the `@internal` API doc tag. These +> classes are **not** stable and do not have any backwards compatibility +> promises. + +In minor releases, new methods may be added to classes, and existing methods may +have new arguments added. Any new arguments will have default values, but if +you've overridden methods with a differing signature you may see fatal errors. +Methods that have new arguments added will be documented in the migration guide +for that release. + +The following table outlines several use cases and what compatibility you can +expect from CakePHP: + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
If you...Backwards compatibility?
Typehint against the classYes
Create a new instanceYes
Extend the classYes
Access a public propertyYes
Call a public methodYes
Extend a class and...
Override a public propertyYes
Access a protected propertyNo1
Override a protected propertyNo2
Override a protected methodNo3
Call a protected methodNo4
Add a public propertyNo
Add a public methodNo
Add an argument to an overridden methodNo5
Add a default argument value to an existing method argumentYes
+
+
+
    +
  1. Your code may be broken by minor releases. Check the migration guide for details.↩︎

  2. +
  3. Your code may be broken by minor releases. Check the migration guide for details.↩︎

  4. +
  5. Your code may be broken by minor releases. Check the migration guide for details.↩︎

  6. +
  7. Your code may be broken by minor releases. Check the migration guide for details.↩︎

  8. +
  9. Your code may be broken by minor releases. Check the migration guide for details.↩︎

  10. +
+
+ +## Working on CakePHP + +If you are helping make CakePHP even better please keep the following guidelines +in mind when adding/changing functionality: + +In a minor release you can: + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
In a minor release can you...
Classes
Remove a classNo
Remove an interfaceNo
Remove a traitNo
Make finalNo
Make abstractNo
Change nameYes1
Properties
Add a public propertyYes
Remove a public propertyNo
Add a protected propertyYes
Remove a protected propertyYes2
Methods
Add a public methodYes
Remove a public methodNo
Add a protected methodYes
Move to parent classYes
Remove a protected methodYes3
Reduce visibilityNo
Change method nameYes4
Add a new argument with default valueYes
Add a new required argument to an existing method.No
Remove a default value from an existing argumentNo
Change method type voidYes
+
+
+
    +
  1. You can change a class/method name as long as the old name remains available. This is generally avoided unless renaming has significant benefit.↩︎

  2. +
  3. Avoid whenever possible. Any removals need to be documented in the migration guide.↩︎

  4. +
  5. Avoid whenever possible. Any removals need to be documented in the migration guide.↩︎

  6. +
  7. You can change a class/method name as long as the old name remains available. This is generally avoided unless renaming has significant benefit.↩︎

  8. +
+
+ +## Deprecations + +In each minor release, features may be deprecated. If features are deprecated, +API documentation and runtime warnings will be added. Runtime errors help you +locate code that needs to be updated before it breaks. If you wish to disable +runtime warnings you can do so using the `Error.errorLevel` configuration +value: + +``` text +// in config/app.php +// ... +'Error' => [ + 'errorLevel' => E_ALL ^ E_USER_DEPRECATED, +] +// ... +``` + +Will disable runtime deprecation warnings. + + + +## Experimental Features + +Experimental features are **not included** in the above backwards compatibility +promises. Experimental features can have breaking changes made in minor releases +as long as they remain experimental. Experimental features can be identified by +the warning in the book and the usage of `@experimental` in the API +documentation. + +Experimental features are intended to help gather feedback on how a feature +works before it becomes stable. Once the interfaces and behavior has been vetted +with the community the experimental flags will be removed. diff --git a/docs/en/contributing/cakephp-coding-conventions.md b/docs/en/contributing/cakephp-coding-conventions.md new file mode 100644 index 0000000000..3e923a4134 --- /dev/null +++ b/docs/en/contributing/cakephp-coding-conventions.md @@ -0,0 +1,690 @@ +# Coding Standards + +CakePHP developers will use the [PSR-12 coding style guide](https://www.php-fig.org/psr/psr-12/) in addition to the following rules as +coding standards. + +It is recommended that others developing CakeIngredients follow the same +standards. + +You can use the [CakePHP Code Sniffer](https://github.com/cakephp/cakephp-codesniffer) to check that your code +follows required standards. + +## Adding New Features + +No new features should be added, without having their own tests – which +should be passed before committing them to the repository. + +## IDE Setup + +Please make sure your IDE is set up to "trim right" on whitespaces. +There should be no trailing spaces per line. + +Most modern IDEs also support an `.editorconfig` file. The CakePHP app +skeleton ships with it by default. It already contains best practise defaults. + +We recommend to use the [IdeHelper](https://github.com/dereuromark/cakephp-ide-helper) plugin if you +want to maximize IDE compatibility. It will assist to keep the annotations up-to-date which will make +the IDE fully understand how all classes work together and provides better type-hinting and auto-completion. + +## Indentation + +Four spaces will be used for indentation. + +So, indentation should look like this: + +``` text +// base level + // level 1 + // level 2 + // level 1 +// base level +``` + +Or: + +``` php +$booleanVariable = true; +$stringVariable = 'moose'; +if ($booleanVariable) { + echo 'Boolean value is true'; + if ($stringVariable === 'moose') { + echo 'We have encountered a moose'; + } +} +``` + +In cases where you're using a multi-line function call use the following +guidelines: + +- Opening parenthesis of a multi-line function call must be the last content on + the line. +- Only one argument is allowed per line in a multi-line function call. +- Closing parenthesis of a multi-line function call must be on a line by itself. + +As an example, instead of using the following formatting: + +``` php +$matches = array_intersect_key($this->_listeners, + array_flip(preg_grep($matchPattern, + array_keys($this->_listeners), 0))); +``` + +Use this instead: + +``` php +$matches = array_intersect_key( + $this->_listeners, + array_flip( + preg_grep($matchPattern, array_keys($this->_listeners), 0) + ) +); +``` + +## Line Length + +It is recommended to keep lines at approximately 100 characters long for better +code readability. A limit of 80 or 120 characters makes it necessary to +distribute complex logic or expressions by function, as well as give functions +and objects shorter, more expressive names. Lines must not be +longer than 120 characters. + +In short: + +- 100 characters is the soft limit. +- 120 characters is the hard limit. + +## Control Structures + +Control structures are for example "`if`", "`for`", "`foreach`", +"`while`", "`switch`" etc. Below, an example with "`if`": + +``` text +if ((expr_1) || (expr_2)) { + // action_1; +} elseif (!(expr_3) && (expr_4)) { + // action_2; +} else { + // default_action; +} +``` + +- In the control structures there should be 1 (one) space before the first + parenthesis and 1 (one) space between the last parenthesis and the opening + bracket. +- Always use curly brackets in control structures, even if they are not needed. + They increase the readability of the code, and they give you fewer logical + errors. +- Opening curly brackets should be placed on the same line as the control + structure. Closing curly brackets should be placed on new lines, and they + should have same indentation level as the control structure. The statement + included in curly brackets should begin on a new line, and code contained + within it should gain a new level of indentation. +- Inline assignments should not be used inside of the control structures. + +``` php +// wrong = no brackets, badly placed statement +if (expr) statement; + +// wrong = no brackets +if (expr) + statement; + +// good +if (expr) { + statement; +} + +// wrong = inline assignment +if ($variable = Class::function()) { + statement; +} + +// good +$variable = Class::function(); +if ($variable) { + statement; +} +``` + +### Ternary Operator + +Ternary operators are permissible when the entire ternary operation fits on one +line. Longer ternaries should be split into `if else` statements. Ternary +operators should not ever be nested. Optionally parentheses can be used around +the condition check of the ternary for clarity: + +``` php +// Good, simple and readable +$variable = isset($options['variable']) ? $options['variable'] : true; + +// Nested ternaries are bad +$variable = isset($options['variable']) ? isset($options['othervar']) ? true : false : false; +``` + +### Template Files + +In template files developers should use keyword control structures. +Keyword control structures are easier to read in complex template files. Control +structures can either be contained in a larger PHP block, or in separate PHP +tags: + +``` php +You are the admin user.

'; +endif; +?> +

The following is also acceptable:

+ +

You are the admin user.

+ +``` + +## Comparison + +Always try to be as strict as possible. If a non-strict test is deliberate it +might be wise to comment it as such to avoid confusing it for a mistake. + +For testing if a variable is null, it is recommended to use a strict check: + +``` php +if ($value === null) { + // ... +} +``` + +The value to check against should be placed on the right side: + +``` php +// not recommended +if (null === $this->foo()) { + // ... +} + +// recommended +if ($this->foo() === null) { + // ... +} +``` + +## Function Calls + +Functions should be called without space between function's name and starting +parenthesis. There should be one space between every parameter of a function +call: + +``` php +$var = foo($bar, $bar2, $bar3); +``` + +As you can see above there should be one space on both sides of equals sign (=). + +## Method Definition + +Example of a method definition: + +``` php +public function someFunction($arg1, $arg2 = '') +{ + if (expr) { + statement; + } + + return $var; +} +``` + +Parameters with a default value, should be placed last in function definition. +Try to make your functions return something, at least `true` or `false`, so +it can be determined whether the function call was successful: + +``` php +public function connection($dns, $persistent = false) +{ + if (is_array($dns)) { + $dnsInfo = $dns; + } else { + $dnsInfo = BD::parseDNS($dns); + } + + if (!($dnsInfo) || !($dnsInfo['phpType'])) { + return $this->addError(); + } + + return true; +} +``` + +There are spaces on both side of the equals sign. + +## Bail Early + +Try to avoid unnecessary nesting by bailing early: + +``` php +public function run(array $data) +{ + ... + if (!$success) { + return false; + } + + ... +} + +public function check(array $data) +{ + ... + if (!$success) { + throw new RuntimeException(/* ... */); + } + + ... +} +``` + +This helps to keep the logic sequential which improves readability. + +### Typehinting + +Arguments that expect objects, arrays or callbacks (callable) can be typehinted. +We only typehint public methods, though, as typehinting is not cost-free: + +``` php +/** + * Some method description. + * + * @param \Cake\ORM\Table $table The table class to use. + * @param array $array Some array value. + * @param callable $callback Some callback. + * @param bool $boolean Some boolean value. + */ +public function foo(Table $table, array $array, callable $callback, $boolean) +{ +} +``` + +Here `$table` must be an instance of `\Cake\ORM\Table`, `$array` must be +an `array` and `$callback` must be of type `callable` (a valid callback). + +Note that if you want to allow `$array` to be also an instance of +`\ArrayObject` you should not typehint as `array` accepts only the primitive +type: + +``` php +/** + * Some method description. + * + * @param array|\ArrayObject $array Some array value. + */ +public function foo($array) +{ +} +``` + +### Anonymous Functions (Closures) + +Defining anonymous functions follows the [PSR-12](https://www.php-fig.org/psr/psr-12/) coding style guide, where they are +declared with a space after the function keyword, and a space before and after +the use keyword: + +``` php +$closure = function ($arg1, $arg2) use ($var1, $var2) { + // code +}; +``` + +## Method Chaining + +Method chaining should have multiple methods spread across separate lines, and +indented with four spaces: + +``` php +$email->from('foo@example.com') + ->to('bar@example.com') + ->subject('A great message') + ->send(); +``` + +## Commenting Code + +All comments should be written in English, and should in a clear way describe +the commented block of code. + +Comments can include the following [phpDocumentor](https://phpdoc.org) +tags: + +- [@deprecated](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/deprecated.html) + Using the `@version ` format, where `version` + and `description` are mandatory. Version refers to the one it got deprecated in. +- [@example](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/example.html) +- [@ignore](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/ignore.html) +- [@internal](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/internal.html) +- [@link](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/link.html) +- [@see](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/see.html) +- [@since](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/since.html) +- [@version](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/version.html) + +PhpDoc tags are very much like JavaDoc tags in Java. Tags are only processed if +they are the first thing in a DocBlock line, for example: + +``` text +/** + * Tag example. + * + * @author this tag is parsed, but this @version is ignored + * @version 1.0 this tag is also parsed + */ +``` + +``` text +/** + * Example of inline phpDoc tags. + * + * This function works hard with foo() to rule the world. + * + * @return void + */ +function bar() +{ +} + +/** + * Foo function. + * + * @return void + */ +function foo() +{ +} +``` + +Comment blocks, with the exception of the first block in a file, should always +be preceded by a newline. + +### Variable Types + +Variable types for use in DocBlocks: + +Type +Description + +mixed +A variable with undefined (or multiple) type. + +int +Integer type variable (whole number). + +float +Float type (point number). + +bool +Logical type (true or false). + +string +String type (any value in " " or ' '). + +null +Null type. Usually used in conjunction with another type. + +array +Array type. + +object +Object type. A specific class name should be used if possible. + +resource +Resource type (returned by for example mysql_connect()). +Remember that when you specify the type as mixed, you should indicate +whether it is unknown, or what the possible types are. + +callable +Callable function. + +You can also combine types using the pipe char: + + int|bool + +For more than two types it is usually best to just use `mixed`. + +When returning the object itself (for example, for chaining), one should use `$this` +instead: + +``` php +/** + * Foo function. + * + * @return $this + */ +public function foo() +{ + return $this; +} +``` + +## Including Files + +`include`, `require`, `include_once` and `require_once` do not have +parentheses: + +``` text +// wrong = parentheses +require_once('ClassFileName.php'); +require_once ($class); + +// good = no parentheses +require_once 'ClassFileName.php'; +require_once $class; +``` + +When including files with classes or libraries, use only and always the +[require_once](https://php.net/require_once) function. + +## PHP Tags + +Always use long tags (``) instead of short tags (``). The short +echo should be used in template files where appropriate. + +### Short Echo + +The short echo should be used in template files in place of ` + +// good = spaces, no semicolon + +``` + +As of PHP 5.4 the short echo tag (` +- WWW: +- FTP: + +The "example.com" domain name has been reserved for this (see `2606`) and +is recommended for use in documentation or as examples. + +### Files + +File names which do not contain classes should be lowercased and underscored, +for example: + + long_file_name.php + +### Casting + +For casting we use: + +Type +Description + +(bool) +Cast to boolean. + +(int) +Cast to integer. + +(float) +Cast to float. + +(string) +Cast to string. + +(array) +Cast to array. + +(object) +Cast to object. + +Please use `(int)$var` instead of `intval($var)` and `(float)$var` instead +of `floatval($var)` when applicable. + +### Constants + +Constants should be defined in capital letters: + +``` text +define('CONSTANT', 1); +``` + +If a constant name consists of multiple words, they should be separated by an +underscore character, for example: + +``` text +define('LONG_NAMED_CONSTANT', 2); +``` + +### Enums + +Enum cases are defined in `CamelCase` style: + +``` text +enum ArticleStatus: string +{ + case Published = 'Y'; + case NotPublishedYet = 'N'; +} +``` + +## Careful when using empty()/isset() + +While `empty()` often seems correct to use, it can mask errors +and cause unintended effects when `'0'` and `0` are given. When variables or +properties are already defined, the usage of `empty()` is not recommended. +When working with variables, it is better to rely on type-coercion to boolean +instead of `empty()`: + +``` php +function manipulate($var) +{ + // Not recommended, $var is already defined in the scope + if (empty($var)) { + // ... + } + + // Use boolean type coercion + if (!$var) { + // ... + } + if ($var) { + // ... + } +} +``` + +When dealing with defined properties you should favour `null` checks over +`empty()`/`isset()` checks: + +``` php +class Thing +{ + private $property; // Defined + + public function readProperty() + { + // Not recommended as the property is defined in the class + if (!isset($this->property)) { + // ... + } + // Recommended + if ($this->property === null) { + + } + } +} +``` + +When working with arrays, it is better to merge in defaults over using +`empty()` checks. By merging in defaults, you can ensure that required keys +are defined: + +``` php +function doWork(array $array) +{ + // Merge defaults to remove need for empty checks. + $array += [ + 'key' => null, + ]; + + // Not recommended, the key is already set + if (isset($array['key'])) { + // ... + } + + // Recommended + if ($array['key'] !== null) { + // ... + } +} +``` diff --git a/docs/en/contributing/code.md b/docs/en/contributing/code.md new file mode 100644 index 0000000000..0c9d7c119d --- /dev/null +++ b/docs/en/contributing/code.md @@ -0,0 +1,141 @@ +# Code + +Patches and pull requests are a great way to contribute code back to CakePHP. +Pull requests can be created in GitHub, and are preferred over patch files in +ticket comments. + +## Initial Setup + +Before working on patches for CakePHP, it's a good idea to get your environment +setup. You'll need the following software: + +- Git +- PHP |minphpversion| or greater - PHPUnit 5.7.0 or greater + +Set up your user information with your name/handle and working email address: + + git config --global user.name 'Bob Barker' + git config --global user.email 'bob.barker@example.com' + +> [!NOTE] +> If you are new to Git, we highly recommend you to read the excellent and +> free [ProGit](https://git-scm.com/book/) book. + +Get a clone of the CakePHP source code from GitHub: + +- If you don't have a [GitHub](https://github.com) account, create one. +- Fork the [CakePHP repository](https://github.com/cakephp/cakephp) by clicking + the **Fork** button. + +After your fork is made, clone your fork to your local machine: + + git clone git@github.com:YOURNAME/cakephp.git + +Add the original CakePHP repository as a remote repository. You'll use this +later to fetch changes from the CakePHP repository. This will let you stay up +to date with CakePHP: + + cd cakephp + git remote add upstream git://github.com/cakephp/cakephp.git + +Now that you have CakePHP setup you should be able to define a `$test` +[database connection](../orm/database-basics#database-configuration), and +[run all the tests](../development/testing#running-tests). + +## Working on a Patch + +Each time you want to work on a bug, feature or enhancement create a topic +branch. + +The branch you create should be based on the version that your fix/enhancement +is for. For example if you are fixing a bug in `3.x` you would want to use the +`master` branch as the base for your branch. If your change is a bug fix for +the 2.x release series, you should use the `2.x` branch: + +``` text +# fixing a bug on 3.x +git fetch upstream +git checkout -b ticket-1234 upstream/master + +# fixing a bug on 2.x +git fetch upstream +git checkout -b ticket-1234 upstream/2.x +``` + +> [!TIP] +> Use a descriptive name for your branch. Referencing the ticket or feature +> name is a good convention. Examples include `ticket-1234` and `feature-awesome`. + +The above will create a local branch based on the upstream (CakePHP) 2.x branch. +Work on your fix, and make as many commits as you need; but keep in mind the +following: + +- Follow the [Coding Standards](../contributing/cakephp-coding-conventions). +- Add a test case to show the bug is fixed, or that the new feature works. +- Keep your commits logical, and write clear commit messages that provide + context on what you changed and why. + +## Submitting a Pull Request + +Once your changes are done and you're ready for them to be merged into CakePHP, +you'll want to update your branch: + +``` text +# Rebase fix on top of master +git checkout master +git fetch upstream +git merge upstream/master +git checkout +git rebase master +``` + +This will fetch + merge in any changes that have happened in CakePHP since you +started. It will then rebase - or replay your changes on top of the current +code. You might encounter a conflict during the `rebase`. If the rebase quits +early you can see which files are conflicted/un-merged with `git status`. +Resolve each conflict, and then continue the rebase: + +``` text +git add # do this for each conflicted file. +git rebase --continue +``` + +Check that all your tests continue to pass. Then push your branch to your fork: + +``` text +git push origin +``` + +If you've rebased after pushing your branch, you'll need to use force push: + +``` text +git push --force origin +``` + +Once your branch is on GitHub, you can submit a pull request on GitHub. + +### Choosing Where Your Changes will be Merged Into + +When making pull requests you should make sure you select the correct base +branch, as you cannot edit it once the pull request is created. + +- If your change is a **bugfix** and doesn't introduce new functionality and + only corrects existing behavior that is present in the current release. Then + choose **master** as your merge target. +- If your change is a **new feature** or an addition to the framework, then you + should choose the branch with the next version number. For example if the + current stable release is `4.0.0`, the branch accepting new features will + be `4.next`. +- If your change is a breaks existing functionality, or APIs then you'll have + to choose then next major release. For example, if the current release is + `4.0.0` then the next time existing behavior can be broken will be in + `5.x` so you should target that branch. + +> [!NOTE] +> Remember that all code you contribute to CakePHP will be licensed under the +> MIT License, and the [Cake Software Foundation](https://cakefoundation.org/old) will become the owner of any +> contributed code. Contributors should follow the [CakePHP Community +> Guidelines](https://cakephp.org/get-involved). + +All bug fixes merged into a maintenance branch will also be merged into upcoming +releases periodically by the core team. diff --git a/docs/en/contributing/documentation.md b/docs/en/contributing/documentation.md new file mode 100644 index 0000000000..70378deef9 --- /dev/null +++ b/docs/en/contributing/documentation.md @@ -0,0 +1,599 @@ +# Documentation + +Contributing to the documentation is simple. The files are hosted on +. Feel free to fork the repo, add your +changes/improvements/translations and give back by issuing a pull request. +You can even edit the docs online with GitHub, without ever downloading the +files -- the "Edit this page" link on any given page will direct you to +GitHub's online editor for that page. + +The CakePHP documentation is built with [VitePress](https://vitepress.dev), a static site +generator powered by Vue and Vite. The documentation is +[continuously integrated](https://en.wikipedia.org/wiki/Continuous_integration) +and deployed after each pull request is merged. + +## Translations + +Email the docs team (docs at cakephp dot org) or hop on IRC +(#cakephp on freenode) to discuss any translation efforts you would +like to participate in. + +### New Translation Language + +We want to provide translations that are as complete as possible. However, there +may be times where a translation file is not up-to-date. You should always +consider the English version as the authoritative version. + +If your language is not in the current languages, please contact us through +Github and we will consider creating a skeleton folder for it. The following +sections are the first one you should consider translating as these +files don't change often: + +``` +- index.md +- intro.md +- quickstart.md +- installation.md +- /intro folder +- /tutorials-and-examples folder +``` + +### Reminder for Docs Administrators + +The structure of all language folders should mirror the English folder +structure. If the structure changes for the English version, we should apply +those changes in the other languages. + +For example, if a new English file is created in **en/file.md**, we should: + +- Add the file in all other languages : **fr/file.md**, **zh/file.md**, ... + +- Delete the content, but keep the frontmatter and add a translation notice. + The following note will be added while nobody has translated the file: + + ```markdown + --- + title: File Title + description: Brief description of the page + --- + + # File Title + + ::: warning Translation Needed + The documentation is not currently supported in XX language for this page. + + Please feel free to send us a pull request on + [GitHub](https://github.com/cakephp/docs) or use the **Edit this page** + link to directly propose your changes. + + You can refer to the English version in the language selector to have + information about this page's topic. + ::: + ``` + +### Translator tips + +- Browse and edit in the language you want the content to be + translated to - otherwise you won't see what has already been + translated. +- Feel free to dive right in if your chosen language already + exists on the book. +- Use [Informal Form](https://en.wikipedia.org/wiki/Register#Linguistics). +- Translate both the content and the title at the same time. +- Do compare to the English content before submitting a correction + (if you correct something, but don't integrate an 'upstream' change + your submission won't be accepted). +- If you need to write an English term, wrap it in `*asterisks*` for emphasis. + For example, "asdf asdf *Controller* asdf" or "asdf asdf Kontroller + (*Controller*) asfd". +- Do not submit partial translations. +- Do not edit a section with a pending change. +- Do not use + [HTML entities](https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references) + for accented characters, the documentation uses UTF-8. +- Do not significantly change the markup or add new content. +- If the original content is missing some info, submit an edit for + that first. + +## Documentation Formatting Guide + +The CakePHP documentation is written in [Markdown](https://en.wikipedia.org/wiki/Markdown) +and built with [VitePress](https://vitepress.dev). Markdown is a lightweight +markup language that's easy to read and write. To maintain consistency, please +follow these guidelines when contributing to the CakePHP documentation. + +For comprehensive information about VitePress features and configuration, +refer to the [VitePress Guide](https://vitepress.dev/guide). For detailed +information about Markdown features and VitePress-specific extensions, see the +[VitePress Markdown Guide](https://vitepress.dev/guide/markdown). + +### Line Length + +Lines of text should be wrapped at 80 columns. The only exception should be +long URLs, and code snippets. + +### Headings and Sections + +Headings are created using hash symbols (`#`) at the start of a line: + +- `#` Page title (H1) - Use only once per page +- `##` Major sections (H2) +- `###` Subsections (H3) +- `####` Sub-subsections (H4) +- `#####` Sub-sub-subsections (H5) +- `######` Lowest level heading (H6) + +Headings should not be nested more than 4 levels deep for readability. +Always add a blank line before and after headings. + +Example: +```markdown +## Major Section + +Paragraph text here. + +### Subsection + +More content. +``` + +### Paragraphs + +Paragraphs are simply blocks of text, with all the lines at the same level of +indentation. Paragraphs should be separated by one blank line. + +### Inline Markup + +- *Italics*: Use single asterisks or underscores: `*text*` or `_text_` + - Use for general emphasis and highlighting + - Example: *Controller*, *important note* + +- **Bold**: Use double asterisks or underscores: `**text**` or `__text__` + - Use for strong emphasis, directory paths, and key terms + - Example: **config/Migrations**, **articles table** + +- `Inline Code`: Use single backticks: `` `text` `` + - Use for method names, variable names, class names, and code snippets + - Example: `cascadeCallbacks`, `PagesController`, `config()`, `true` + +**Tips:** +- Ensure proper spacing around inline markup for it to render correctly +- Inline code doesn't require escaping special characters +- You can escape special characters with backslash if needed: `\*not italic\*` + +### Lists + +**Unordered Lists**: Start lines with `-`, `*`, or `+` followed by a space: + +```markdown +- First item +- Second item +- Third item with + multiple lines +``` + +**Ordered Lists**: Use numbers followed by a period: + +```markdown +1. First item +2. Second item +3. Third item +``` + +**Nested Lists**: Indent with 2 or 4 spaces: + +```markdown +- First level + - Second level + - Third level + - Back to second level +- Back to first level +``` + +**Mixed Lists**: + +```markdown +1. Ordered item + - Unordered sub-item + - Another sub-item +2. Another ordered item +``` + +### Links + +#### External Links + +Use standard Markdown link syntax: + +```markdown +[Link Text](https://example.com) +``` + +Example: [CakePHP Website](https://cakephp.org) + +#### Internal Links + +Link to other pages in the documentation using relative paths: + +```markdown +[Link Text](../path/to/page.md) +``` + +Examples: +- Same directory: `[Installation](./installation.md)` +- Parent directory: `[Controllers](../controllers.md)` +- Subdirectory: `[Components](./controllers/components.md)` + +**Note**: VitePress automatically converts `.md` extensions in links to appropriate +URL paths in the built site. + +#### Anchor Links + +Link to specific sections within a page using heading anchors: + +```markdown +[Link to section](#heading-text) +``` + +VitePress automatically generates anchor IDs from headings in kebab-case. +For example, `## My Section Title` becomes `#my-section-title`. + +Link to a section in another page: +```markdown +[Link text](./other-page.md#section-anchor) +``` + +### Frontmatter + +Each documentation page should start with YAML frontmatter containing metadata: + +```markdown +--- +title: Page Title +description: A brief description of the page content +--- +``` + +Optional frontmatter fields: +- `title`: Page title (used in browser tab and navigation) +- `description`: Page description for SEO +- `head`: Additional head tags +- `layout`: Custom layout (default is 'doc') + +Example: +```markdown +--- +title: Controllers +description: Learn about CakePHP controllers and request handling +--- + +# Controllers + +Your content here... +``` + +### Code Blocks + +Code blocks are created using triple backticks with an optional language identifier: + +````markdown +```php +class MyController extends Controller +{ + public function index() + { + // Your code here + } +} +``` +```` + +Common language identifiers: +- `php` - PHP code +- `bash` or `shell` - Shell commands +- `javascript` or `js` - JavaScript +- `json` - JSON data +- `yaml` - YAML configuration +- `html` - HTML markup +- `css` - CSS styles + +**Line Highlighting**: Highlight specific lines in code blocks: + +````markdown +```php{3-6} +class Example +{ + public function highlighted() // This line is highlighted + { + // These lines are + // also highlighted + } +} +``` +```` + +**Line Numbers**: Add line numbers to code blocks: + +````markdown +```php:line-numbers +class Example +{ + public function withLineNumbers() + { + return true; + } +} +``` +```` + +**Code Groups**: Display multiple code examples with tabs: + +````markdown +::: code-group + +```php [Controller] +class ArticlesController extends Controller +{ + public function index() + { + $articles = $this->Articles->find('all'); + } +} +``` + +```php [Model] +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Timestamp'); + } +} +``` + +```php [Template] + +

title) ?>

+ +``` + +::: +```` + +### Admonitions + +VitePress provides custom containers for tips, warnings, and other callouts. + +**Available Container Types:** + +```markdown +::: tip +This is a tip. Use for helpful information or best practices. +::: + +::: info +This is an info box. Use for additional context or explanations. +::: + +::: warning +This is a warning. Use for important caveats or potential issues. +::: + +::: danger +This is a danger notice. Use for critical information or security concerns. +::: + +::: details Click to expand +This is a collapsible details block. Content is hidden by default. +::: +``` + +**Custom Titles:** + +```markdown +::: tip Custom Tip Title +You can customize the title of any container. +::: + +::: warning Important Security Note +Always validate user input before processing. +::: +``` + +**Version Notices:** + +For version-specific information: + +```markdown +::: info New in 5.0.0 +This feature was added in CakePHP 5.0.0. +::: + +::: warning Deprecated in 5.1.0 +This method is deprecated as of CakePHP 5.1.0. Use `newMethod()` instead. +::: +``` + +**Examples:** + +::: tip Performance Tip +Caching query results can significantly improve performance. +::: + +::: warning Security Warning +Never store passwords in plain text. Always use proper hashing. +::: + +::: info New in 5.0.0 +The new ORM improvements provide better type safety. +::: + +## Validating Your Changes + +Before submitting a pull request, you should validate your documentation changes to ensure they meet quality standards. + +### Installing Validation Tools + +You can use `npx` to run the tools without installing them globally, or install them for faster repeated use: + +::: code-group + +```bash [Using npx (Recommended)] +# No installation needed - npx downloads and runs the tools +npx cspell --config .github/cspell.json "docs/en/**/*.md" +npx markdownlint-cli --config .github/markdownlint.json "docs/en/**/*.md" +``` + +```bash [Global Installation] +# Install globally for faster execution +npm install -g cspell markdownlint-cli + +# Then run without npx +cspell --config .github/cspell.json "docs/en/**/*.md" +markdownlint-cli --config .github/markdownlint.json "docs/en/**/*.md" +``` + +::: + +### Running Spell Check + +We use [cspell](https://cspell.org/) to check spelling in documentation. To check your changes: + +::: code-group + +```bash [Single File] +# Check a single file +npx cspell --config .github/cspell.json docs/en/your-file.md +``` + +```bash [Directory] +# Check all files in a directory +npx cspell --config .github/cspell.json "docs/en/controllers/*.md" +``` + +```bash [All Files] +# Check all documentation recursively +npx cspell --config .github/cspell.json "docs/**/*.md" +``` + +::: + +::: tip Adding Technical Terms +If cspell flags legitimate technical terms (class names, CakePHP-specific terms), add them to the `words` array in [.github/cspell.json](https://github.com/cakephp/docs/blob/5.x/.github/cspell.json). +::: + +### Running Markdown Lint + +We use [markdownlint](https://github.com/DavidAnson/markdownlint) to maintain consistent markdown formatting: + +::: code-group + +```bash [Single File] +# Check a single file +npx markdownlint-cli --config .github/markdownlint.json docs/en/your-file.md +``` + +```bash [Directory] +# Check all files in a directory +npx markdownlint-cli --config .github/markdownlint.json "docs/en/controllers/*.md" +``` + +```bash [All Docs] +# Check all English documentation +npx markdownlint-cli --config .github/markdownlint.json "docs/en/**/*.md" +``` + +```bash [Auto-fix] +# Automatically fix formatting issues +npx markdownlint-cli --config .github/markdownlint.json --fix cs/en/**/*.md" +``` + +::: + +::: warning Auto-fix Limitations +The `--fix` flag can automatically correct many formatting issues, but not all. Review changes before committing. +::: + +### Running Link Checker + +We use a custom link checker to validate internal markdown links in the documentation: + +::: code-group + +```bash [Single File] +# Check a single file +node bin/check-links.js docs/en/your-file.md +``` + +```bash [Directory] +# Check all files in a directory +node bin/check-links.js "docs/en/controllers/*.md" +``` + +```bash [All Docs] +# Check all documentation recursively +node bin/check-links.js "docs/**/*.md" +``` + +```bash [With Baseline] +# Check while ignoring known issues in baseline +node bin/check-links.js --baseline .github/linkchecker-baseline.json "docs/**/*.md" +``` + +::: + +The link checker validates: +- Internal file references (relative links) +- Anchor links to headings within pages +- Directory index links + +::: info External Links +The link checker only validates internal markdown links. External URLs (http://, https://) are not checked. +::: + +::: tip Known Issues Baseline +The repository maintains a baseline of known broken links in `.github/linkchecker-baseline.json`. When you run the checker with `--baseline`, it filters out these known issues and only reports new problems. This is useful when working on specific changes without being overwhelmed by pre-existing issues. +::: + +### GitHub Actions Validation + +When you submit a pull request, our CI pipeline automatically runs: + +1. **JavaScript syntax validation** - Validates `config.js` +2. **JSON validation** - Validates `toc_*.json` files +3. **Markdown linting** - Checks all markdown files +4. **Spell checking** - Scans documentation for typos +5. **Link checking** - Validates internal markdown links + +::: tip Pre-flight Check +::: code-group + +```bash [Quick Check] +# Validate markdown and spelling +npx markdownlint-cli --config .github/markdownlint.json "docs/**/*.md" +npx cspell --config .github/cspell.json "docs/**/*.md" +node bin/check-links.js "docs/**/*.md" +``` + +```bash [Full Validation] +# Run all CI checks locally +npx markdownlint-cli --config .github/markdownlint.json "docs/**/*.md" +npx cspell --config .github/cspell.json "docs/**/*.md" +node bin/check-links.js "docs/**/*.md" +node --check config.js +jq empty toc_en.json +``` + +```bash [Single File] +# Check your current file before committing +npx markdownlint-cli --config .github/markdownlint.json docs/en/your-file.md +npx cspell --config .github/cspell.json docs/en/your-file.md +node bin/check-links.js docs/en/your-file.md +``` + +:::e --check config.js +jq empty toc_en.json +``` +::: + +If the CI checks fail, review the error messages and fix the issues before requesting a review. diff --git a/docs/en/contributing/tickets.md b/docs/en/contributing/tickets.md new file mode 100644 index 0000000000..0bcafd44eb --- /dev/null +++ b/docs/en/contributing/tickets.md @@ -0,0 +1,43 @@ +# Tickets + +Getting feedback and help from the community in the form of tickets is an +extremely important part of the CakePHP development process. All of CakePHP's +tickets are hosted on [GitHub](https://github.com/cakephp/cakephp/issues). + +## Reporting Bugs + +Well written bug reports are very helpful. There are a few steps to help create +the best bug report possible: + +- **Do**: Please [search](https://github.com/cakephp/cakephp/search?q=it+is+broken&ref=cmdform&type=Issues) + for a similar existing ticket, and ensure someone hasn't already reported your + issue, or that it hasn't already been fixed in the repository. +- **Do**: Please include detailed instructions on **how to reproduce the bug**. + This could be in the form of a test-case or a snippet of code that + demonstrates the issue. Not having a way to reproduce an issue means it's less + likely to get fixed. +- **Do**: Please give as many details as possible about your environment: (OS, + PHP version, CakePHP version). +- **Don't**: Please don't use the ticket system to ask support questions. Both the support channel on the + [CakePHP Slack workspace](https://cakesf.herokuapp.com) and the \#cakephp IRC channel on [Freenode](https://webchat.freenode.net) have many + developers available to help answer your questions. Also have a look at + [Stack Overflow](https://stackoverflow.com/questions/tagged/cakephp) or the [official CakePHP forum](https://discourse.cakephp.org). + +## Reporting Security Issues + +If you've found a security issue in CakePHP, please use the following procedure +instead of the normal bug reporting system. Instead of using the bug tracker, +mailing list or IRC please send an email to **security \[at\] cakephp.org**. +Emails sent to this address go to the CakePHP core team on a private mailing +list. + +For each report, we try to first confirm the vulnerability. Once confirmed, the +CakePHP team will take the following actions: + +- Acknowledge to the reporter that we've received the issue, and are working on + a fix. We ask that the reporter keep the issue confidential until we announce + it. +- Get a fix/patch prepared. +- Prepare a post describing the vulnerability, and the possible exploits. +- Release new versions of all affected versions. +- Prominently feature the problem in the release announcement. diff --git a/docs/en/controllers.md b/docs/en/controllers.md new file mode 100644 index 0000000000..1c66ba908b --- /dev/null +++ b/docs/en/controllers.md @@ -0,0 +1,642 @@ +# Controllers + +`class` Cake\\Controller\\**Controller** + +Controllers are the 'C' in MVC. After routing has been applied and the correct +controller has been found, your controller's action is called. Your controller +should handle interpreting the request data, making sure the correct models +are called, and the right response or view is rendered. Controllers can be +thought of as middle layer between the Model and View. You want to keep your +controllers thin, and your models fat. This will help you reuse +your code and makes your code easier to test. + +Commonly, a controller is used to manage the logic around a single model. For +example, if you were building a site for an online bakery, you might have a +RecipesController managing your recipes and an IngredientsController managing your +ingredients. However, it's also possible to have controllers work with more than +one model. In CakePHP, a controller is named after the primary model it +handles. + +Your application's controllers extend the `AppController` class, which in turn +extends the core `Controller` class. The `AppController` +class can be defined in **src/Controller/AppController.php** and it should +contain methods that are shared between all of your application's controllers. + +Controllers provide a number of methods that handle requests. These are called +*actions*. By default, each public method in +a controller is an action, and is accessible from a URL. An action is responsible +for interpreting the request and creating the response. Usually responses are +in the form of a rendered view, but there are other ways to create responses as +well. + + + +## The App Controller + +As stated in the introduction, the `AppController` class is the parent class +to all of your application's controllers. `AppController` itself extends the +`Cake\Controller\Controller` class included in CakePHP. +`AppController` is defined in **src/Controller/AppController.php** as +follows: + +``` php +namespace App\Controller; + +use Cake\Controller\Controller; + +class AppController extends Controller +{ +} +``` + +Controller attributes and methods created in your `AppController` will be +available in all controllers that extend it. Components (which you'll +learn about later) are best used for code that is used in many (but not +necessarily all) controllers. + +You can use your `AppController` to load components that will be used in every +controller in your application. CakePHP provides a `initialize()` method that +is invoked at the end of a Controller's constructor for this kind of use: + +``` php +namespace App\Controller; + +use Cake\Controller\Controller; + +class AppController extends Controller +{ + public function initialize(): void + { + // Always enable the FormProtection component. + $this->loadComponent('FormProtection'); + } +} +``` + +## Request Flow + +When a request is made to a CakePHP application, CakePHP's +`Cake\Routing\Router` and `Cake\Routing\Dispatcher` +classes use [Routes Configuration](development/routing#routes-configuration) to find and create the correct +controller instance. The request data is encapsulated in a request object. +CakePHP puts all of the important request information into the `$this->request` +property. See the section on [Cake Request](controllers/request-response#cake-request) for more information on the +CakePHP request object. + +## Controller Actions + +Controller actions are responsible for converting the request parameters into a +response for the browser/user making the request. CakePHP uses conventions to +automate this process and remove some boilerplate code you would otherwise need +to write. + +By convention, CakePHP renders a view with an inflected version of the action +name. Returning to our online bakery example, our RecipesController might contain the +`view()`, `share()`, and `search()` actions. The controller would be found +in **src/Controller/RecipesController.php** and contain: + +``` php +// src/Controller/RecipesController.php + +class RecipesController extends AppController +{ + public function view($id) + { + // Action logic goes here. + } + + public function share($customerId, $recipeId) + { + // Action logic goes here. + } + + public function search($query) + { + // Action logic goes here. + } +} +``` + +The template files for these actions would be **templates/Recipes/view.php**, +**templates/Recipes/share.php**, and **templates/Recipes/search.php**. The +conventional view file name is the lowercased and underscored version of the +action name. + +Controller actions generally use +`Controller::set()` to create a context that +`View` uses to render the view layer. Because of the conventions that +CakePHP uses, you don't need to create and render the view manually. Instead, +once a controller action has completed, CakePHP will handle rendering and +delivering the View. + +If for some reason you'd like to skip the default behavior, you can return a +`Cake\Http\Response` object from the action with the fully +created response. + +In order for you to use a controller effectively in your own application, we'll +cover some of the core attributes and methods provided by CakePHP's controllers. + +## Interacting with Views + +Controllers interact with views in a number of ways. First, they +are able to pass data to the views, using `Controller::set()`. You can also +decide which view class to use, and which view file should be +rendered from the controller. + + + +### Setting View Variables + +`method` Cake\\Controller\\Controller::**set**(string $var, mixed $value) + +The `Controller::set()` method is the main way to send data from your +controller to your view. Once you've used `Controller::set()`, the variable +can be accessed in your view: + +``` php +// First you pass data from the controller: + +$this->set('color', 'pink'); + +// Then, in the view, you can utilize the data: +?> + +You have selected icing for the cake. +``` + +The `Controller::set()` method also takes an +associative array as its first parameter. This can often be a quick way to +assign a set of information to the view: + +``` php +$data = [ + 'color' => 'pink', + 'type' => 'sugar', + 'base_price' => 23.95, +]; + +// Make $color, $type, and $base_price +// available to the view: + +$this->set($data); +``` + +Keep in mind that view vars are shared among all parts rendered by your view. +They will be available in all parts of the view: the template, the layout and +all elements inside the former two. + +### Setting View Options + +If you want to customize the view class, layout/template paths, helpers or the +theme that will be used when rendering the view, you can use the +`viewBuilder()` method to get a builder. This builder can be used to define +properties of the view before it is created: + +``` php +$this->viewBuilder() + ->addHelper('MyCustom') + ->setTheme('Modern') + ->setClassName('Modern.Admin'); +``` + +The above shows how you can load custom helpers, set the theme and use a custom +view class. + +### Rendering a View + +`method` Cake\\Controller\\Controller::**render**(string $view, string $layout): Response + +The `Controller::render()` method is automatically called at the end of each requested +controller action. This method performs all the view logic (using the data +you've submitted using the `Controller::set()` method), places the view inside its +`View::$layout`, and serves it back to the end user. + +The default view file used by render is determined by convention. +If the `search()` action of the RecipesController is requested, +the view file in **templates/Recipes/search.php** will be rendered: + +``` php +namespace App\Controller; + +class RecipesController extends AppController +{ +// ... + public function search() + { + // Render the view in templates/Recipes/search.php + return $this->render(); + } +// ... +} +``` + +Although CakePHP will automatically call it after every action's logic +(unless you've called `$this->disableAutoRender()`), you can use it to specify +an alternate view file by specifying a view file name as first argument of +`Controller::render()` method. + +If `$view` starts with '/', it is assumed to be a view or +element file relative to the **templates** folder. This allows +direct rendering of elements, very useful in AJAX calls: + +``` php +// Render the element in templates/element/ajaxreturn.php +$this->render('/element/ajaxreturn'); +``` + +The second parameter `$layout` of `Controller::render()` allows you to specify the layout +with which the view is rendered. + +#### Rendering a Specific Template + +In your controller, you may want to render a different view than the +conventional one. You can do this by calling `Controller::render()` directly. Once you +have called `Controller::render()`, CakePHP will not try to re-render the view: + +``` php +namespace App\Controller; + +class PostsController extends AppController +{ + public function my_action() + { + return $this->render('custom_file'); + } +} +``` + +This would render **templates/Posts/custom_file.php** instead of +**templates/Posts/my_action.php**. + +You can also render views inside plugins using the following syntax: +`$this->render('PluginName.PluginController/custom_file')`. +For example: + +``` php +namespace App\Controller; + +class PostsController extends AppController +{ + public function myAction() + { + return $this->render('Users.UserDetails/custom_file'); + } +} +``` + +This would render **plugins/Users/templates/UserDetails/custom_file.php** + + + +## Content Type Negotiation + +`method` Cake\\Controller\\Controller::**addViewClasses**(array $viewClasses) + +Controllers can define a list of view classes they support. After the +controller's action is complete CakePHP will use the view list to perform +content-type negotiation with either [File Extensions](development/routing#file-extensions) or `Accept` +headers. This enables your application to re-use the same controller action to +render an HTML view or render a JSON or XML response. To define the list of +supported view classes for a controller is done with the `addViewClasses()` +method: + +``` php +namespace App\Controller; + +use Cake\View\JsonView; +use Cake\View\XmlView; + +class PostsController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + + $this->addViewClasses([JsonView::class, XmlView::class]); + } +} +``` + +The application's `View` class is automatically used as a fallback when no +other view can be selected based on the request's `Accept` header or routing +extension. If your application only supports content types for a specific +actions, you can call `addClasses()` within your action too: + +``` php +public function export(): void +{ + // Use a custom CSV view for data exports. + $this->addViewClasses([CsvView::class]); + + // Rest of the action code +} +``` + +If within your controller actions you need to process the request or load data +differently based on the content type you can use +[Check The Request](controllers/request-response#check-the-request): + +``` php +// In a controller action + +// Load additional data when preparing JSON responses +if ($this->request->is('json')) { + $query->contain('Authors'); +} +``` + +In case your app need more complex logic to decide which view classes to use +then you can override the `Controller::viewClasses()` method and return +an array of view classes as required. + +> [!NOTE] +> View classes must implement the static `contentType()` hook method to +> participate in content-type negotiation. + +## Content Type Negotiation Fallbacks + +If no View can be matched with the request's content type preferences, CakePHP +will use the base `View` class. If you want to require content-type +negotiation, you can use the `NegotiationRequiredView` which sets a `406` status +code: + +``` php +public function initialize(): void +{ + parent::initialize(); + + // Require Accept header negotiation or return a 406 response. + $this->addViewClasses([JsonView::class, NegotiationRequiredView::class]); +} +``` + +You can use the `TYPE_MATCH_ALL` content type value to build your own fallback +view logic: + +``` php +namespace App\View; + +use Cake\View\View; + +class CustomFallbackView extends View +{ + public static function contentType(): string + { + return static::TYPE_MATCH_ALL; + } + +} +``` + +It is important to remember that match-all views are applied only *after* +content-type negotiation is attempted. + +## Using AjaxView + +In applications that use hypermedia or AJAX clients, you often need to render +view contents without the wrapping layout. You can use the `AjaxView` that +is bundled with the application skeleton: + +``` php +// In a controller action, or in beforeRender. +if ($this->request->is('ajax')) { + $this->viewBuilder()->setClassName('Ajax'); +} +``` + +`AjaxView` will respond as `text/html` and use the `ajax` layout. +Generally this layout is minimal or contains client specific markup. This +replaces usage of `RequestHandlerComponent` automatically using the +`AjaxView` in 4.x. + +## Redirecting to Other Pages + +`method` Cake\\Controller\\Controller::**redirect**(string|array $url, integer $status): Response|null + +The `redirect()` method adds a `Location` header and sets the status code of +a response and returns it. You should return the response created by +`redirect()` to have CakePHP send the redirect instead of completing the +controller action and rendering a view. + +You can redirect using `routing array` values: + +``` php +return $this->redirect([ + 'controller' => 'Orders', + 'action' => 'confirm', + $order->id, + '?' => [ + 'product' => 'pizza', + 'quantity' => 5 + ], + '#' => 'top' +]); +``` + +Or using a relative or absolute URL: + +``` php +return $this->redirect('/orders/confirm'); + +return $this->redirect('http://www.example.com'); +``` + +Or to the referer page: + +``` php +return $this->redirect($this->referer()); +``` + +By using the second parameter you can define a status code for your redirect: + +``` php +// Do a 301 (moved permanently) +return $this->redirect('/order/confirm', 301); + +// Do a 303 (see other) +return $this->redirect('/order/confirm', 303); +``` + +See the [Redirect Component Events](controllers/components#redirect-component-events) section for how to redirect out of +a life-cycle handler. + +## Loading Additional Tables/Models + +`method` Cake\\Controller\\Controller::**fetchTable**(string $alias, array $config = []) + +The `fetchTable()` method comes handy when you need to use an ORM table that is not +the controller's default one: + +``` php +// In a controller method. +$recentArticles = $this->fetchTable('Articles')->find('all', + limit: 5, + order: 'Articles.created DESC' + ) + ->all(); +``` + +### fetchModel() + +`method` Cake\\Controller\\Controller::**fetchModel**(string|null $modelClass = null, string|null $modelType = null) + +The `fetchModel()` method is useful to load non ORM models or ORM tables that +are not the controller's default: + +``` php +// ModelAwareTrait need to be explicity added to your controler first for fetchModel() to work. +use ModelAwareTrait; + +// Get an ElasticSearch model +$articles = $this->fetchModel('Articles', 'Elastic'); + +// Get a webservices model +$github = $this->fetchModel('GitHub', 'Webservice'); + +// If you skip the 2nd argument it will by default try to load a ORM table. +$authors = $this->fetchModel('Authors'); +``` + +::: info Added in version 4.5.0 +::: + +## Paginating a Model + +`method` Cake\\Controller\\Controller::**paginate**(): PaginatedInterface + +This method is used for paginating results fetched by your models. +You can specify page sizes, model find conditions and more. See the +[pagination](controllers/pagination) section for more details on +how to use `paginate()`. + +The `$paginate` attribute gives you a way to customize how `paginate()` +behaves: + +``` php +class ArticlesController extends AppController +{ + protected array $paginate = [ + 'Articles' => [ + 'conditions' => ['published' => 1], + ], + ]; +} +``` + +## Configuring Components to Load + +`method` Cake\\Controller\\Controller::**loadComponent**($name, $config = []): Component + +In your Controller's `initialize()` method you can define any components you +want loaded, and any configuration data for them: + +``` php +public function initialize(): void +{ + parent::initialize(); + $this->loadComponent('Flash'); + $this->loadComponent('Comments', Configure::read('Comments')); +} +``` + + + +## Request Life-cycle Callbacks + +CakePHP controllers trigger several events/callbacks that you can use to insert +logic around the request life-cycle: + +### Event List + +- `Controller.initialize` +- `Controller.startup` +- `Controller.beforeRedirect` +- `Controller.beforeRender` +- `Controller.shutdown` + +### Controller Callback Methods + +By default the following callback methods are connected to related events if the +methods are implemented by your controllers + +#### beforeFilter() + +`method` Cake\\Controller\\Controller::**beforeFilter**(EventInterface $event): void + +#### beforeRender() + +`method` Cake\\Controller\\Controller::**beforeRender**(EventInterface $event): void + +#### afterFilter() + +`method` Cake\\Controller\\Controller::**afterFilter**(EventInterface $event): void + +In addition to controller life-cycle callbacks, [Components](controllers/components) +also provide a similar set of callbacks. + +Remember to call `AppController`'s callbacks within child controller callbacks +for best results: + +``` php +//use Cake\Event\EventInterface; +public function beforeFilter(EventInterface $event): void +{ + parent::beforeFilter($event); +} +``` + + + +## Using Redirects in Controller Events + +To redirect from within a controller callback method you can use the following: + +``` php +public function beforeFilter(EventInterface $event): void +{ + if (...) { + $event->setResult($this->redirect('/')); + + return; + } + + ... +} +``` + +By setting a redirect as event result you let CakePHP know that you don't want any other +component callbacks to run, and that the controller should not handle the action +any further. + +As of 4.1.0 you can also raise a `RedirectException` to signal a redirect. + +## Controller Middleware + +`method` Cake\\Controller\\Controller::**middleware**($middleware, array $options = []): void + +[Middleware](controllers/middleware) can be defined globally, in +a routing scope or within a controller. To define middleware for a specific +controller use the `middleware()` method from your controller's +`initialize()` method: + +``` php +public function initialize(): void +{ + parent::initialize(); + + $this->middleware(function ($request, $handler) { + // Do middleware logic. + + // Make sure you return a response or call handle() + return $handler->handle($request); + }); +} +``` + +Middleware defined by a controller will be called **before** `beforeFilter()` and action methods are called. + +## More on Controllers + +- [The Pages Controller](controllers/pages-controller) +- [Components](controllers/components) +- [Rate Limiting Middleware](controllers/middleware/rate-limit) diff --git a/docs/en/controllers/components.md b/docs/en/controllers/components.md new file mode 100644 index 0000000000..8baeb532c4 --- /dev/null +++ b/docs/en/controllers/components.md @@ -0,0 +1,361 @@ +# Components + +Components are packages of logic that are shared between controllers. +CakePHP comes with a fantastic set of core components you can use to aid in +various common tasks. You can also create your own components. If you find +yourself wanting to copy and paste things between controllers, you should +consider creating your own component to contain the functionality. Creating +components keeps controller code clean and allows you to reuse code between +different controllers. + +For more information on the components included in CakePHP, check out the +chapter for each component: + +- [Flash](../controllers/components/flash) +- [Form Protection Component](../controllers/components/form-protection) +- [Checking HTTP Cache](../controllers/components/check-http-cache) + + + +## Configuring Components + +Many of the core components require configuration. One example would be +the [Form Protection Component](../controllers/components/form-protection). Configuration for these components, +and for components in general, is usually done via `loadComponent()` in your +Controller's `initialize()` method or via the `$components` array: + +``` php +class PostsController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('FormProtection', [ + 'unlockedActions' => ['index'], + ]); + $this->loadComponent('Flash'); + } +} +``` + +You can configure components at runtime using the `setConfig()` method. Often, +this is done in your controller's `beforeFilter()` method. The above could +also be expressed as: + +``` php +public function beforeFilter(EventInterface $event): void +{ + $this->FormProtection->setConfig('unlockedActions', ['index']); +} +``` + +Like helpers, components implement `getConfig()` and `setConfig()` methods +to read and write configuration data: + +``` php +// Read config data. +$this->FormProtection->getConfig('unlockedActions'); + +// Set config +$this->Flash->setConfig('key', 'myFlash'); +``` + +As with helpers, components will automatically merge their `$_defaultConfig` +property with constructor configuration to create the `$_config` property +which is accessible with `getConfig()` and `setConfig()`. + +### Aliasing Components + +One common setting to use is the `className` option, which allows you to +alias components. This feature is useful when you want to +replace `$this->Flash` or another common Component reference with a custom +implementation: + +``` php +// src/Controller/PostsController.php +class PostsController extends AppController +{ + public function initialize(): void + { + $this->loadComponent('Flash', [ + 'className' => 'MyFlash', + ]); + } +} + +// src/Controller/Component/MyFlashComponent.php +use Cake\Controller\Component\FlashComponent; + +class MyFlashComponent extends FlashComponent +{ + // Add your code to override the core FlashComponent +} +``` + +The above would *alias* `MyFlashComponent` to `$this->Flash` in your +controllers. + +> [!NOTE] +> Aliasing a component replaces that instance anywhere that component is used, +> including inside other Components. + +### Loading Components on the Fly + +You might not need all of your components available on every controller +action. In situations like this you can load a component at runtime using the +`loadComponent()` method in your controller: + +``` php +// In a controller action +$this->loadComponent('OneTimer'); +$time = $this->OneTimer->getTime(); +``` + +> [!NOTE] +> Keep in mind that components loaded on the fly will not have missed +> callbacks called. If you rely on the `beforeFilter` or `startup` +> callbacks being called, you may need to call them manually depending on when +> you load your component. + +## Using Components + +Once you've included some components in your controller, using them is pretty +simple. Each component you use is exposed as a property on your controller. If +you had loaded up the `Cake\Controller\Component\FlashComponent` +in your controller, you could access it like so: + +``` php +class PostsController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('Flash'); + } + + public function delete() + { + if ($this->Post->delete($this->request->getData('Post.id')) { + $this->Flash->success('Post deleted.'); + + return $this->redirect(['action' => 'index']); + } + } +``` + +> [!NOTE] +> Since both Models and Components are added to Controllers as +> properties they share the same 'namespace'. Be sure to not give a +> component and a model the same name. + +::: info Changed in version 5.1.0 +Components are able to use [Dependency Injection](../development/dependency-injection) to receive services. +::: + + + +## Creating a Component + +Suppose our application needs to perform a complex mathematical operation in +many different parts of the application. We could create a component to house +this shared logic for use in many different controllers. + +The first step is to create a new component file and class. Create the file in +**src/Controller/Component/MathComponent.php**. The basic structure for the +component would look something like this: + +``` php +namespace App\Controller\Component; + +use Cake\Controller\Component; + +class MathComponent extends Component +{ + public function doComplexOperation($amount1, $amount2) + { + return $amount1 + $amount2; + } +} +``` + +> [!NOTE] +> All components must extend `Cake\Controller\Component`. Failing +> to do this will trigger an exception. + +Components can use [Dependency Injection](../development/dependency-injection) to receive services +as constructor parameters: + +``` php +namespace App\Controller\Component; + +use Cake\Controller\Component; +use App\Service\UserService; + +class SsoComponent extends Component +{ + private Users $users; + + public function __construct( + ComponentRegistry $registry, + array $config = [], + UserService $users + ) { + parent::__construct($registry, $config); + $this->users = $users; + } +} +``` + +### Including your Component in your Controllers + +Once our component is finished, we can use it in the application's +controllers by loading it during the controller's `initialize()` method. +Once loaded, the controller will be given a new attribute named after the +component, through which we can access an instance of it: + +``` php +// In a controller +// Make the new component available at $this->Math, +// as well as the standard $this->Flash +public function initialize(): void +{ + parent::initialize(); + $this->loadComponent('Math'); + $this->loadComponent('Flash'); +} +``` + +When including Components in a Controller you can also declare a +set of parameters that will be passed on to the Component's +constructor. These parameters can then be handled by +the Component: + +``` php +// In your controller. +public function initialize(): void +{ + parent::initialize(); + $this->loadComponent('Math', [ + 'precision' => 2, + 'randomGenerator' => 'srand', + ]); + $this->loadComponent('Flash'); +} +``` + +The above would pass the array containing precision and randomGenerator to +`MathComponent::initialize()` in the `$config` parameter. + +### Using Other Components in your Component + +Sometimes one of your components may need to use another component. +You can load other components by adding them to the \$components property: + +``` php +// src/Controller/Component/CustomComponent.php +namespace App\Controller\Component; + +use Cake\Controller\Component; + +class CustomComponent extends Component +{ + // The other component your component uses + protected array $components = ['Existing']; + + // Execute any other additional setup for your component. + public function initialize(array $config): void + { + $this->Existing->foo(); + } + + public function bar() + { + // ... + } +} + +// src/Controller/Component/ExistingComponent.php +namespace App\Controller\Component; + +use Cake\Controller\Component; + +class ExistingComponent extends Component +{ + public function foo() + { + // ... + } +} +``` + +> [!NOTE] +> In contrast to a component included in a controller +> no callbacks will be triggered on a component's component. + +### Accessing a Component's Controller + +From within a Component you can access the current controller through the +registry: + +``` php +$controller = $this->getController(); +``` + +## Component Callbacks + +Components also offer a few request life-cycle callbacks that allow them to +augment the request cycle. + +`method` Class::**beforeFilter**(EventInterface $event): void + +`method` Class::**startup**(EventInterface $event): void + +`method` Class::**beforeRender**(EventInterface $event): void + +`method` Class::**afterFilter**(EventInterface $event): void + +`method` Class::**beforeRedirect**(EventInterface $event, $url, Response $response): void + + + +## Using Redirects in Component Events + +To redirect from within a component callback method you can use the following: + +``` php +public function beforeFilter(EventInterface $event): void +{ + if (...) { + $event->setResult($this->getController()->redirect('/')); + + return; + } + + ... +} +``` + +By setting a redirect as event result you let CakePHP know that you don't want any other +component callbacks to run, and that the controller should not handle the action +any further. As of 4.1.0 you can raise a `RedirectException` to signal +a redirect: + +``` php +use Cake\Http\Exception\RedirectException; +use Cake\Routing\Router; + +public function beforeFilter(EventInterface $event): void +{ + throw new RedirectException(Router::url('/')) +} +``` + +Raising an exception will halt all other event listeners and create a new +response that doesn't retain or inherit any of the current response's headers. +When raising a `RedirectException` you can include additional headers: + +``` php +throw new RedirectException(Router::url('/'), 302, [ + 'Header-Key' => 'value', +]); +``` diff --git a/docs/en/controllers/components/check-http-cache.md b/docs/en/controllers/components/check-http-cache.md new file mode 100644 index 0000000000..eab42313a9 --- /dev/null +++ b/docs/en/controllers/components/check-http-cache.md @@ -0,0 +1,33 @@ +# Checking HTTP Cache + +`class` **CheckHttpCacheComponent**(ComponentCollection $collection, array $config = []) + +The HTTP cache validation model is one of the processes used for cache gateways, +also known as reverse proxies, to determine if they can serve a stored copy of +a response to the client. Under this model, you mostly save bandwidth, but when +used correctly you can also save some CPU processing, reducing response +times: + +``` php +// in a Controller +public function initialize(): void +{ + parent::initialize(); + + $this->addComponent('CheckHttpCache'); +} +``` + +Enabling the `CheckHttpCacheComponent` in your controller automatically +activates a `beforeRender` check. This check compares caching headers set in +the response object to the caching headers sent in the request to determine +whether the response was not modified since the last time the client asked for +it. The following request headers are used: + +- `If-None-Match` is compared with the response's `Etag` header. +- `If-Modified-Since` is compared with the response's `Last-Modified` + header. + +If response headers match the request header criteria, then view rendering is +skipped. This saves your application generating a view, saving bandwidth and +time. When response headers match, an empty response is returned with a `304 Not Modified` status code. diff --git a/docs/en/controllers/components/flash.md b/docs/en/controllers/components/flash.md new file mode 100644 index 0000000000..47f98bd2df --- /dev/null +++ b/docs/en/controllers/components/flash.md @@ -0,0 +1,107 @@ +# Flash + +`class` Cake\\Controller\\Component\\**FlashComponent**(ComponentCollection $collection, array $config = []) + +FlashComponent provides a way to set one-time notification messages to be +displayed after processing a form or acknowledging data. CakePHP refers to these +messages as "flash messages". FlashComponent writes flash messages to +`$_SESSION`, to be rendered in a View using +[FlashHelper](../../views/helpers/flash). + +## Setting Flash Messages + +FlashComponent provides two ways to set flash messages: its `__call()` magic +method and its `set()` method. To furnish your application with verbosity, +FlashComponent's `__call()` magic method allows you use a method name that +maps to an element located under the **templates/element/flash** directory. +By convention, camelcased methods will map to the lowercased and underscored +element name: + +``` php +// Uses templates/element/flash/success.php +$this->Flash->success('This was successful'); + +// Uses templates/element/flash/great_success.php +$this->Flash->greatSuccess('This was greatly successful'); +``` + +Alternatively, to set a plain-text message without rendering an element, you can +use the `set()` method: + +``` php +$this->Flash->set('This is a message'); +``` + +Flash messages are appended to an array internally. Successive calls to +`set()` or `__call()` with the same key will append the messages in the +`$_SESSION`. If you want to overwrite existing messages when setting a flash +message, set the `clear` option to `true` when configuring the Component. + +FlashComponent's `__call()` and `set()` methods optionally take a second +parameter, an array of options: + +- `key` Defaults to 'flash'. The array key found under the `Flash` key in + the session. +- `element` Defaults to `null`, but will automatically be set when using the + `__call()` magic method. The element name to use for rendering. +- `params` An optional array of keys/values to make available as variables + within an element. +- `clear` expects a `bool` and allows you to delete all messages in the + current stack and start a new one. + +An example of using these options: + +``` php +// In your Controller +$this->Flash->success('The user has been saved', [ + 'key' => 'positive', + 'clear' => true, + 'params' => [ + 'name' => $user->name, + 'email' => $user->email, + ], +]); + +// In your View +Flash->render('positive') ?> + + +
+ : , . +
+``` + +Note that the parameter `element` will be always overridden while using +`__call()`. In order to retrieve a specific element from a plugin, you should +set the `plugin` parameter. For example: + +``` php +// In your Controller +$this->Flash->warning('My message', ['plugin' => 'PluginName']); +``` + +The code above will use the **warning.php** element under +**plugins/PluginName/templates/element/flash** for rendering the flash +message. + +> [!NOTE] +> By default, CakePHP escapes the content in flash messages to prevent cross +> site scripting. User data in your flash messages will be HTML encoded and +> safe to be printed. If you want to include HTML in your flash messages, you +> need to pass the `escape` option and adjust your flash message templates +> to allow disabling escaping when the escape option is passed. + +## HTML in Flash Messages + +It is possible to output HTML in flash messages by using the `'escape'` option +key: + +``` php +$this->Flash->info(sprintf('%s %s', h($highlight), h($message)), ['escape' => false]); +``` + +Make sure that you escape the input manually, then. In the above example +`$highlight` and `$message` are non-HTML input and therefore escaped. + +For more information about rendering your flash messages, please refer to the +[FlashHelper](../../views/helpers/flash) section. diff --git a/docs/en/controllers/components/form-protection.md b/docs/en/controllers/components/form-protection.md new file mode 100644 index 0000000000..b81bab9d14 --- /dev/null +++ b/docs/en/controllers/components/form-protection.md @@ -0,0 +1,159 @@ +# Form Protection Component + +`class` **FormProtection**(ComponentCollection $collection, array $config = []) + +The FormProtection Component provides protection against form data tampering. + +Like all components it is configured through several configurable parameters. +All of these properties can be set directly or through setter methods of the +same name in your controller's `initialize()` or `beforeFilter()` methods. + +If you are using other components that process form data in their `startup()` +callbacks, be sure to place FormProtection Component before those components +in your `initialize()` method. + +> [!NOTE] +> When using the FormProtection Component you **must** use the FormHelper to create +> your forms. In addition, you must **not** override any of the fields' "name" +> attributes. The FormProtection Component looks for certain indicators that are +> created and managed by the FormHelper (especially those created in +> `Cake\View\Helper\FormHelper::create()` and +> `Cake\View\Helper\FormHelper::end()`). Dynamically altering +> the fields that are submitted in a POST request, such as disabling, deleting +> or creating new fields via JavaScript, is likely to cause the form token +> validation to fail. + +## Form tampering prevention + +By default the `FormProtectionComponent` prevents users from tampering with +forms in specific ways. It will prevent the following things: + +- Form's action (URL) cannot be modified. +- Unknown fields cannot be added to the form. +- Fields cannot be removed from the form. +- Values in hidden inputs cannot be modified. + +Preventing these types of tampering is accomplished by working with the `FormHelper` +and tracking which fields are in a form. The values for hidden fields are +tracked as well. All of this data is combined and turned into a hash and hidden +token fields are automatically be inserted into forms. When a form is submitted, +the `FormProtectionComponent` will use the POST data to build the same structure +and compare the hash. + +> [!NOTE] +> The FormProtectionComponent will **not** prevent select options from being +> added/changed. Nor will it prevent radio options from being added/changed. + +## Usage + +Configuring the form protection component is generally done in the controller's +`initialize()` or `beforeFilter()` callbacks + +Available options are: + +validate +Set to `false` to completely skip the validation of POST +requests, essentially turning off form validation. + +unlockedFields +Set to a list of form fields to exclude from POST validation. Fields can be +unlocked either in the Component, or with +`FormHelper::unlockField()`. Fields that have been unlocked are +not required to be part of the POST and hidden unlocked fields do not have +their values checked. + +unlockedActions +Actions to exclude from POST validation checks. + +validationFailureCallback +Callback to call in case of validation failure. Must be a valid Closure. +Unset by default in which case exception is thrown on validation failure. + +## Disabling form tampering checks + +``` php +namespace App\Controller; + +use App\Controller\AppController; +use Cake\Event\EventInterface; + +class WidgetsController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + + $this->loadComponent('FormProtection'); + } + + public function beforeFilter(EventInterface $event): void + { + parent::beforeFilter($event); + + if ($this->request->getParam('prefix') === 'Admin') { + $this->FormProtection->setConfig('validate', false); + } + } +} +``` + +The above example would disable form tampering prevention for admin prefixed +routes. + +## Disabling form tampering for specific actions + +There may be cases where you want to disable form tampering prevention for an +action (ex. AJAX requests). You may "unlock" these actions by listing them in +`$this->FormProtection->setConfig('unlockedActions', ['edit']);` in your `beforeFilter()`: + +``` php +namespace App\Controller; + +use App\Controller\AppController; +use Cake\Event\EventInterface; + +class WidgetController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('FormProtection'); + } + + public function beforeFilter(EventInterface $event): void + { + parent::beforeFilter($event); + + $this->FormProtection->setConfig('unlockedActions', ['edit']); + } +} +``` + +This example would disable all security checks for the edit action. + +## Handling validation failure through callbacks + +If form protection validation fails it will result in a 400 error by default. +You can configure this behavior by setting the `validationFailureCallback` +configuration option to a callback function in the controller. + +By configuring a callback method you can customize how the failure handling process +works: + +``` php +use Cake\Controller\Exception\FormProtectionException; + +public function beforeFilter(EventInterface $event): void +{ + parent::beforeFilter($event); + + $this->FormProtection->setConfig( + 'validationFailureCallback', + // Prior to 5.2 use Cake\Http\Exception\BadRequestException. + function (FormProtectionException $exception) { + // You can either return a response instance or throw the exception + // received as argument. + } + ); +} +``` diff --git a/docs/en/controllers/middleware.md b/docs/en/controllers/middleware.md new file mode 100644 index 0000000000..939022c328 --- /dev/null +++ b/docs/en/controllers/middleware.md @@ -0,0 +1,310 @@ +# Middleware + +Middleware objects give you the ability to 'wrap' your application in re-usable, +composable layers of Request handling, or response building logic. Visually, +your application ends up at the center, and middleware is wrapped around the app +like an onion. Here we can see an application wrapped with Routes, Assets, +Exception Handling and CORS header middleware. + +![image](/middleware-setup.png) + +When a request is handled by your application it enters from the outermost +middleware. Each middleware can either delegate the request/response to the next +layer, or return a response. Returning a response prevents lower layers from +ever seeing the request. An example of that is the AssetMiddleware handling +a request for a plugin image during development. + +![image](/middleware-request.png) + +If no middleware take action to handle the request, a controller will be located +and have its action invoked, or an exception will be raised generating an error +page. + +Middleware are part of the new HTTP stack in CakePHP that leverages the PSR-7 +request and response interfaces. CakePHP also supports the PSR-15 standard for +server request handlers so you can use any PSR-15 compatible middleware available +on [The Packagist](https://packagist.org). + +## Middleware in CakePHP + +CakePHP provides several middleware to handle common tasks in web applications: + +- `Cake\Error\Middleware\ErrorHandlerMiddleware` traps exceptions from the + wrapped middleware and renders an error page using the + [Error & Exception Handling](../development/errors) Exception handler. +- `Cake\Routing\AssetMiddleware` checks whether the request is referring to a + theme or plugin asset file, such as a CSS, JavaScript or image file stored in + either a plugin's webroot folder or the corresponding one for a Theme. +- `Cake\Routing\Middleware\RoutingMiddleware` uses the `Router` to parse the + incoming URL and assign routing parameters to the request. +- `Cake\I18n\Middleware\LocaleSelectorMiddleware` enables automatic language + switching from the `Accept-Language` header sent by the browser. +- `Cake\Http\Middleware\EncryptedCookieMiddleware` gives you the ability to + manipulate encrypted cookies in case you need to manipulate cookie with + obfuscated data. +- `Cake\Http\Middleware\BodyParserMiddleware` allows you to decode JSON, XML + and other encoded request bodies based on `Content-Type` header. +- [Cake\Http\Middleware\HttpsEnforcerMiddleware](../security/https-enforcer) + requires HTTPS to be used. +- [Cake\Http\Middleware\CsrfProtectionMiddleware](../security/csrf) adds + double-submit cookie based CSRF protection to your application. +- [Cake\Http\Middleware\SessionCsrfProtectionMiddleware](../security/csrf) + adds session based CSRF protection to your application. +- [Cake\Http\Middleware\CspMiddleware](../security/content-security-policy) + makes it simpler to add Content-Security-Policy headers to your application. +- [Cake\Http\Middleware\SecurityHeadersMiddleware](../security/security-headers) + makes it possible to add security related headers like `X-Frame-Options` to + responses. +- [Cake\Http\Middleware\RateLimitMiddleware](../controllers/middleware/rate-limit) + provides configurable rate limiting to protect against abuse and ensure fair + usage of resources. + + + +## Using Middleware + +Middleware can be applied to your application globally, to individual +routing scopes, or to specific controllers. + +To apply middleware to all requests, use the `middleware` method of your +`App\Application` class. Your application's `middleware` hook method will be +called at the beginning of the request process, you can use the +`MiddlewareQueue` object to attach middleware: + +``` php +namespace App; + +use Cake\Core\Configure; +use Cake\Error\Middleware\ErrorHandlerMiddleware; +use Cake\Http\BaseApplication; +use Cake\Http\MiddlewareQueue; + +class Application extends BaseApplication +{ + public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue + { + // Bind the error handler into the middleware queue. + $middlewareQueue->add(new ErrorHandlerMiddleware(Configure::read('Error'), $this)); + + // Add middleware by classname. + // As of 4.5.0 classname middleware are optionally resolved + // using the DI container. If the class is not found in the + // container then an instance is created by the middleware queue. + $middlewareQueue->add(UserRateLimiting::class); + + return $middlewareQueue; + } +} +``` + +In addition to adding to the end of the `MiddlewareQueue` you can do +a variety of operations: + +``` php +$layer = new \App\Middleware\CustomMiddleware; + +// Added middleware will be last in line. +$middlewareQueue->add($layer); + +// Prepended middleware will be first in line. +$middlewareQueue->prepend($layer); + +// Insert in a specific slot. If the slot is out of +// bounds, it will be added to the end. +$middlewareQueue->insertAt(2, $layer); + +// Insert before another middleware. +// If the named class cannot be found, +// an exception will be raised. +$middlewareQueue->insertBefore( + 'Cake\Error\Middleware\ErrorHandlerMiddleware', + $layer +); + +// Insert after another middleware. +// If the named class cannot be found, the +// middleware will added to the end. +$middlewareQueue->insertAfter( + 'Cake\Error\Middleware\ErrorHandlerMiddleware', + $layer +); +``` + +If your middleware is only applicable to a subset of routes or individual +controllers you can use [Route scoped middleware](../development/routing#route-scoped-middleware), +or [Controller middleware](../controllers#controller-middleware). + +### Adding Middleware from Plugins + +Plugins can use their `middleware` hook method to apply any middleware they +have to the application's middleware queue: + +``` php +// in plugins/ContactManager/src/Plugin.php +namespace ContactManager; + +use Cake\Core\BasePlugin; +use Cake\Http\MiddlewareQueue; +use ContactManager\Middleware\ContactManagerContextMiddleware; + +class Plugin extends BasePlugin +{ + public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue + { + $middlewareQueue->add(new ContactManagerContextMiddleware()); + + return $middlewareQueue; + } +} +``` + +## Creating Middleware + +Middleware can either be implemented as anonymous functions (Closures), or classes +which extend `Psr\Http\Server\MiddlewareInterface`. While Closures are suitable +for smaller tasks they make testing harder, and can create a complicated +`Application` class. Middleware classes in CakePHP have a few conventions: + +- Middleware class files should be put in **src/Middleware**. For example: + **src/Middleware/CorsMiddleware.php** +- Middleware classes should be suffixed with `Middleware`. For example: + `LinkMiddleware`. +- Middleware must implement `Psr\Http\Server\MiddlewareInterface`. + +Middleware can return a response either by calling `$handler->handle()` or by +creating their own response. We can see both options in our simple middleware: + +``` php +// In src/Middleware/TrackingCookieMiddleware.php +namespace App\Middleware; + +use Cake\Http\Cookie\Cookie; +use Cake\I18n\Time; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Psr\Http\Server\MiddlewareInterface; + +class TrackingCookieMiddleware implements MiddlewareInterface +{ + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface + { + // Calling $handler->handle() delegates control to the *next* middleware + // In your application's queue. + $response = $handler->handle($request); + + if (!$request->getCookie('landing_page')) { + $expiry = new Time('+ 1 year'); + $response = $response->withCookie(new Cookie( + 'landing_page', + $request->getRequestTarget(), + $expiry + )); + } + + return $response; + } +} +``` + +Now that we've made a very simple middleware, let's attach it to our +application: + +``` php +// In src/Application.php +namespace App; + +use App\Middleware\TrackingCookieMiddleware; +use Cake\Http\MiddlewareQueue; + +class Application +{ + public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue + { + // Add your simple middleware onto the queue + $middlewareQueue->add(new TrackingCookieMiddleware()); + + // Add some more middleware onto the queue + + return $middlewareQueue; + } +} +``` + + + +## Routing Middleware + +Routing middleware is responsible for applying your application's routes and +resolving the plugin, controller, and action a request is going to: + +``` php +// In Application.php +public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue +{ + // ... + $middlewareQueue->add(new RoutingMiddleware($this)); +} +``` + + + +## Encrypted Cookie Middleware + +If your application has cookies that contain data you want to obfuscate and +protect against user tampering, you can use CakePHP's encrypted cookie +middleware to transparently encrypt and decrypt cookie data via middleware. +Cookie data is encrypted with via OpenSSL using AES: + +``` php +use Cake\Http\Middleware\EncryptedCookieMiddleware; + +$cookies = new EncryptedCookieMiddleware( + // Names of cookies to protect + ['secrets', 'protected'], + Configure::read('Security.cookieKey') +); + +$middlewareQueue->add($cookies); +``` + +> [!NOTE] +> It is recommended that the encryption key you use for cookie data, is used +> *exclusively* for cookie data. + +The encryption algorithms and padding style used by the cookie middleware are +backwards compatible with `CookieComponent` from earlier versions of CakePHP. + + + +## Body Parser Middleware + +If your application accepts JSON, XML or other encoded request bodies, the +`BodyParserMiddleware` will let you decode those requests into an array that +is available via `$request->getParsedData()` and `$request->getData()`. By +default only `json` bodies will be parsed, but XML parsing can be enabled with +an option. You can also define your own parsers: + +``` php +use Cake\Http\Middleware\BodyParserMiddleware; + +// only JSON will be parsed. +$bodies = new BodyParserMiddleware(); + +// Enable XML parsing +$bodies = new BodyParserMiddleware(['xml' => true]); + +// Disable JSON parsing +$bodies = new BodyParserMiddleware(['json' => false]); + +// Add your own parser matching content-type header values +// to the callable that can parse them. +$bodies = new BodyParserMiddleware(); +$bodies->addParser(['text/csv'], function ($body, $request) { + // Use a CSV parsing library. + return Csv::parse($body); +}); +``` diff --git a/docs/en/controllers/middleware/rate-limit.md b/docs/en/controllers/middleware/rate-limit.md new file mode 100644 index 0000000000..a53e7d0657 --- /dev/null +++ b/docs/en/controllers/middleware/rate-limit.md @@ -0,0 +1,391 @@ +# Rate Limiting Middleware + +::: info Added in version 5.3 +::: + +The `RateLimitMiddleware` provides configurable rate limiting for your +application to protect against abuse and ensure fair usage of resources. + +## Basic Usage + +To use rate limiting in your application, add the middleware to your +middleware queue: + +``` php +// In src/Application.php +use Cake\Http\Middleware\RateLimitMiddleware; + +public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue +{ + $middlewareQueue + // ... other middleware + ->add(new RateLimitMiddleware([ + 'limit' => 60, // 60 requests + 'window' => 60, // per 60 seconds + 'identifier' => RateLimitMiddleware::IDENTIFIER_IP, + ])); + + return $middlewareQueue; +} +``` + +When a client exceeds the rate limit, they will receive a +`429 Too Many Requests` response. + +## Constants + +The middleware provides constants for common identifier and strategy values: + +### Identifier Constants + +- `RateLimitMiddleware::IDENTIFIER_IP` - Client IP address (default) +- `RateLimitMiddleware::IDENTIFIER_USER` - Authenticated user +- `RateLimitMiddleware::IDENTIFIER_ROUTE` - Route (controller/action combination) +- `RateLimitMiddleware::IDENTIFIER_API_KEY` - API key from token headers +- `RateLimitMiddleware::IDENTIFIER_TOKEN` - Alias for API key + +### Strategy Constants + +- `RateLimitMiddleware::STRATEGY_SLIDING_WINDOW` - Sliding window algorithm (default) +- `RateLimitMiddleware::STRATEGY_FIXED_WINDOW` - Fixed window algorithm +- `RateLimitMiddleware::STRATEGY_TOKEN_BUCKET` - Token bucket algorithm + +## Configuration Options + +The middleware accepts the following configuration options: + +- **limit** - Maximum number of requests allowed (default: 60) +- **window** - Time window in seconds (default: 60) +- **identifier** - How to identify clients. Use identifier constants (default: `IDENTIFIER_IP`) +- **strategy** - Rate limiting algorithm. Use strategy constants (default: `STRATEGY_SLIDING_WINDOW`) +- **strategyClass** - Fully qualified class name of a custom rate limiter strategy. Takes precedence over `strategy` option +- **cache** - Cache configuration to use (default: 'default') +- **headers** - Whether to include rate limit headers in responses (default: true) +- **includeRetryAfter** - Whether to include Retry-After header in 429 responses (default: true) +- **message** - Custom error message for rate limit exceeded (default: 'Rate limit exceeded. Please try again later.') +- **ipHeader** - Header name(s) to check for client IP when behind a proxy (default: 'x-forwarded-for') +- **tokenHeaders** - Array of headers to check for API tokens (default: `['Authorization', 'X-API-Key']`) +- **skipCheck** - Callback to determine if a request should skip rate limiting +- **costCallback** - Callback to determine the cost of a request +- **identifierCallback** - Callback to determine the identifier for a request +- **limitCallback** - Callback to determine the limit for a specific identifier +- **keyGenerator** - Callback for custom cache key generation +- **limiters** - Array of named limiter configurations for different rate limit profiles +- **limiterResolver** - Callback to resolve which named limiter applies to a request + +## Identifier Types + +### IP Address + +The default identifier type tracks requests by IP address: + +``` php +use Cake\Http\Middleware\RateLimitMiddleware; + +new RateLimitMiddleware([ + 'identifier' => RateLimitMiddleware::IDENTIFIER_IP, + 'limit' => 100, + 'window' => 60, +]) +``` + +The middleware automatically handles proxy headers. You can configure +which headers to check using the `ipHeader` option: + +``` text +new RateLimitMiddleware([ + 'identifier' => RateLimitMiddleware::IDENTIFIER_IP, + 'ipHeader' => ['CF-Connecting-IP', 'X-Forwarded-For'], +]) +``` + +### User-based + +Track requests per authenticated user: + +``` text +new RateLimitMiddleware([ + 'identifier' => RateLimitMiddleware::IDENTIFIER_USER, + 'limit' => 1000, + 'window' => 3600, // 1 hour +]) +``` + +This requires authentication middleware to be loaded before rate limiting. +The middleware checks for users implementing `Authentication\IdentityInterface`. + +### Route-based + +Apply different limits to different routes: + +``` text +new RateLimitMiddleware([ + 'identifier' => RateLimitMiddleware::IDENTIFIER_ROUTE, + 'limit' => 10, + 'window' => 60, +]) +``` + +This creates separate limits for each controller/action combination. + +### API Key / Token + +Track requests by API key or token: + +``` text +new RateLimitMiddleware([ + 'identifier' => RateLimitMiddleware::IDENTIFIER_API_KEY, + 'limit' => 5000, + 'window' => 3600, +]) +``` + +By default, the middleware looks for tokens in the `Authorization` and +`X-API-Key` headers. You can customize which headers to check: + +``` text +new RateLimitMiddleware([ + 'identifier' => RateLimitMiddleware::IDENTIFIER_TOKEN, + 'tokenHeaders' => ['Authorization', 'X-API-Key', 'X-Auth-Token'], +]) +``` + +## Custom Identifiers + +You can create custom identifiers using a callback: + +``` php +new RateLimitMiddleware([ + 'identifierCallback' => function ($request) { + // Custom logic to identify the client + $tenant = $request->getHeader('X-Tenant-ID'); + return 'tenant_' . $tenant[0]; + }, +]) +``` + +## Rate Limiting Strategies + +### Sliding Window + +The default strategy that provides smooth rate limiting by continuously +adjusting the window based on request timing: + +``` text +new RateLimitMiddleware([ + 'strategy' => RateLimitMiddleware::STRATEGY_SLIDING_WINDOW, +]) +``` + +### Fixed Window + +Resets the counter at fixed intervals: + +``` text +new RateLimitMiddleware([ + 'strategy' => RateLimitMiddleware::STRATEGY_FIXED_WINDOW, +]) +``` + +### Token Bucket + +Allows for burst capacity while maintaining an average rate: + +``` text +new RateLimitMiddleware([ + 'strategy' => RateLimitMiddleware::STRATEGY_TOKEN_BUCKET, + 'limit' => 100, // bucket capacity + 'window' => 60, // refill rate +]) +``` + +### Custom Strategy + +You can use a custom rate limiter strategy by specifying the `strategyClass` +option. Your class must implement `Cake\Http\RateLimiter\RateLimiterInterface`: + +``` text +new RateLimitMiddleware([ + 'strategyClass' => App\RateLimiter\MyCustomRateLimiter::class, +]) +``` + +The `strategyClass` option takes precedence over the `strategy` option. + +## Named Limiters + +For complex applications, you can define named limiter configurations +and resolve them dynamically per request: + +``` php +new RateLimitMiddleware([ + 'limiters' => [ + 'default' => [ + 'limit' => 60, + 'window' => 60, + ], + 'api' => [ + 'limit' => 1000, + 'window' => 3600, + ], + 'premium' => [ + 'limit' => 10000, + 'window' => 3600, + ], + ], + 'limiterResolver' => function ($request) { + $user = $request->getAttribute('identity'); + if ($user && $user->plan === 'premium') { + return 'premium'; + } + if (str_starts_with($request->getPath(), '/api/')) { + return 'api'; + } + return 'default'; + }, +]) +``` + +## Advanced Usage + +### Skip Rate Limiting + +Skip rate limiting for certain requests: + +``` php +new RateLimitMiddleware([ + 'skipCheck' => function ($request) { + // Skip rate limiting for health checks + return $request->getParam('action') === 'health'; + }, +]) +``` + +### Request Cost + +Assign different costs to different types of requests: + +``` php +new RateLimitMiddleware([ + 'costCallback' => function ($request) { + // POST requests cost 5x more + return $request->getMethod() === 'POST' ? 5 : 1; + }, +]) +``` + +### Dynamic Limits + +Set different limits for different users or plans: + +``` php +new RateLimitMiddleware([ + 'limitCallback' => function ($request, $identifier) { + $user = $request->getAttribute('identity'); + if ($user && $user->plan === 'premium') { + return 10000; // Premium users get higher limit + } + return 100; // Free tier limit + }, +]) +``` + +### Custom Key Generation + +Customize how cache keys are generated: + +``` php +new RateLimitMiddleware([ + 'keyGenerator' => function ($request, $identifier) { + // Include the HTTP method in the key for per-method limits + return $identifier . '_' . $request->getMethod(); + }, +]) +``` + +### Resetting Rate Limits + +To programmatically reset a rate limit for a specific identifier, use the +`reset()` method on the rate limiter strategy directly. This is useful for: + +- Admin actions to unblock users who were incorrectly rate-limited +- Resetting limits when a user upgrades their plan +- Clearing state between tests + +``` php +use Cake\Cache\Cache; +use Cake\Http\RateLimit\SlidingWindowRateLimiter; + +// Create a rate limiter with the same cache config as your middleware +$limiter = new SlidingWindowRateLimiter(Cache::pool('default')); + +// Reset using the hashed identifier format +$identifier = 'rate_limit_' . hash('xxh3', $userId); +$limiter->reset($identifier); +``` + +> [!NOTE] +> The identifier format used internally is `'rate_limit_' . hash('xxh3', $value)` +> where `$value` is the raw identifier (IP address, user ID, etc.). + +## Rate Limit Headers + +When enabled, the middleware adds the following headers to responses: + +- `X-RateLimit-Limit` - The maximum number of requests allowed +- `X-RateLimit-Remaining` - The number of requests remaining +- `X-RateLimit-Reset` - Unix timestamp when the rate limit resets + +When a client exceeds the limit, a `Retry-After` header is also included +(controlled by the `includeRetryAfter` option). + +## Multiple Rate Limiters + +You can apply multiple rate limiters with different configurations: + +``` php +// Strict limit for login attempts +$middlewareQueue->add(new RateLimitMiddleware([ + 'identifier' => RateLimitMiddleware::IDENTIFIER_IP, + 'limit' => 5, + 'window' => 900, // 15 minutes + 'skipCheck' => function ($request) { + return $request->getParam('action') !== 'login'; + }, +])); + +// General API rate limit +$middlewareQueue->add(new RateLimitMiddleware([ + 'identifier' => RateLimitMiddleware::IDENTIFIER_API_KEY, + 'limit' => 1000, + 'window' => 3600, +])); +``` + +## Cache Configuration + +The rate limiter stores its data in cache. Make sure you have a persistent +cache configured: + +``` text +// In config/app.php +'Cache' => [ + 'rate_limit' => [ + 'className' => 'Redis', + 'prefix' => 'rate_limit_', + 'duration' => '+1 hour', + ], +], +``` + +Then use it in the middleware: + +``` text +new RateLimitMiddleware([ + 'cache' => 'rate_limit', +]) +``` + +> [!WARNING] +> The `File` cache engine is not recommended for production use with +> rate limiting as it may not handle concurrent requests properly. diff --git a/docs/en/controllers/pages-controller.md b/docs/en/controllers/pages-controller.md new file mode 100644 index 0000000000..ae7286c05b --- /dev/null +++ b/docs/en/controllers/pages-controller.md @@ -0,0 +1,12 @@ +# The Pages Controller + +CakePHP's official skeleton app ships with a default controller **PagesController.php**. +This is a simple and optional controller for serving up static content. The home page +you see after installation is generated using this controller and the view +file **templates/Pages/home.php**. If you make the view file +**templates/Pages/about_us.php** you can access it using the URL +**http://example.com/pages/about_us**. You are free to modify the Pages +Controller to meet your needs. + +When you "bake" an app using Composer the Pages Controller is created in your +**src/Controller/** folder. diff --git a/docs/en/controllers/pagination.md b/docs/en/controllers/pagination.md new file mode 100644 index 0000000000..20c44a1c14 --- /dev/null +++ b/docs/en/controllers/pagination.md @@ -0,0 +1,412 @@ +# Pagination + +One of the main obstacles of creating flexible and user-friendly web +applications is designing an intuitive user interface. Many applications tend to +grow in size and complexity quickly, and designers and programmers alike find +they are unable to cope with displaying hundreds or thousands of records. +Refactoring takes time, and performance and user satisfaction can suffer. + +Displaying a reasonable number of records per page has always been a critical +part of every application and used to cause many headaches for developers. +CakePHP eases the burden on the developer by providing a terse way to +paginate data. + +Pagination in CakePHP controllers is done through the `paginate()` method. You +then use `Cake\View\Helper\PaginatorHelper` in your view templates +to generate pagination controls. + +## Basic Usage + +You can call `paginate()` using an ORM table instance or `Query` object: + +``` php +public function index() +{ + // Paginate the ORM table. + $this->set('articles', $this->paginate($this->Articles)); + + // Paginate a select query + $query = $this->Articles->find('published')->contain('Comments'); + $this->set('articles', $this->paginate($query)); +} +``` + +## Advanced Usage + +More complex use cases are supported by configuring the `$paginate` +controller property or as the `$settings` argument to `paginate()`. These +conditions serve as the basis for you pagination queries. They are augmented +by the `sort`, `direction`, `limit`, and `page` parameters passed in +from the URL: + +``` php +class ArticlesController extends AppController +{ + protected array $paginate = [ + 'limit' => 25, + 'order' => [ + 'Articles.title' => 'asc', + ], + ]; +} +``` + +> [!TIP] +> Default `order` options must be defined as an array. + +You can also use [Custom Find Methods](../orm/retrieving-data-and-resultsets#custom-find-methods) in pagination by using the `finder` option: + +``` php +class ArticlesController extends AppController +{ + protected array $paginate = [ + 'finder' => 'published', + ]; +} +``` + +Note: This only works with Table as string input in `$this->paginate('MyTable')`. Once you use `$this->MyTable->find()` as input for `paginate()`, you must directly use that Query object instead. + +If your finder method requires additional options you can pass those +as values for the finder: + +``` php +class ArticlesController extends AppController +{ + // find articles by tag + public function tags() + { + $tags = $this->request->getParam('pass'); + + $customFinderOptions = [ + 'tags' => $tags + ]; + // We're using the $settings argument to paginate() here. + // But the same structure could be used in $this->paginate + // + // Our custom finder is called findTagged inside ArticlesTable.php + // which is why we're using `tagged` as the key. + // Our finder should look like: + // public function findTagged(Query $query, array $tagged = []) + $settings = [ + 'finder' => [ + 'tagged' => $customFinderOptions + ] + ]; + $articles = $this->paginate($this->Articles, $settings); + $this->set(compact('articles', 'tags')); + } +} +``` + +In addition to defining general pagination values, you can define more than one +set of pagination defaults in the controller. The name of each model can be used +as a key in the `$paginate` property: + +``` php +class ArticlesController extends AppController +{ + protected array $paginate = [ + 'Articles' => [], + 'Authors' => [], + ]; +} +``` + +The values of the `Articles` and `Authors` keys could contain all the keys +that a basic `$paginate` array would. + +`Controller::paginate()` returns an instance of `Cake\Datasource\Paging\PaginatedResultSet` +which implements the `Cake\Datasource\Paging\PaginatedInterface`. + +This object contains the paginated records and the paging params. + +## Simple Pagination + +By default `Controller::paginate()` uses the `Cake\Datasource\Paging\NumericPaginator` +class which does a `COUNT()` query to calculate the size of the result set so +that page number links can be rendered. On very large datasets this count query +can be very expensive. In situations where you only want to show 'Next' and 'Previous' +links you can use the 'simple' paginator which does not do a count query: + +``` php +class ArticlesController extends AppController +{ + protected array $paginate = [ + 'className' => 'Simple', // Or use Cake\Datasource\Paging\SimplePaginator::class FQCN + ]; +} +``` + +When using the `SimplePaginator` you will not be able to generate page +numbers, counter data, links to the last page, or total record count controls. + + + +## Paginating Multiple Queries + +You can paginate multiple models in a single controller action, using the +`scope` option both in the controller's `$paginate` property and in the +call to the `paginate()` method: + +``` php +// Paginate property +protected array $paginate = [ + 'Articles' => ['scope' => 'article'], + 'Tags' => ['scope' => 'tag'] +]; + +// In a controller action +$articles = $this->paginate($this->Articles, ['scope' => 'article']); +$tags = $this->paginate($this->Tags, ['scope' => 'tag']); +$this->set(compact('articles', 'tags')); +``` + +The `scope` option will result in the paginator looking in +scoped query string parameters. For example, the following URL could be used to +paginate both tags and articles at the same time: + + /dashboard?article[page]=1&tag[page]=3 + +See the [Paginator Helper Multiple](../views/helpers/paginator#paginator-helper-multiple) section for how to generate scoped HTML +elements and URLs for pagination. + +### Paginating the Same Model multiple Times + +To paginate the same model multiple times within a single controller action you +need to define an alias for the model.: + +``` php +// In a controller action +$this->paginate = [ + 'Articles' => [ + 'scope' => 'published_articles', + 'limit' => 10, + 'order' => [ + 'id' => 'desc', + ], + ], + 'UnpublishedArticles' => [ + 'scope' => 'unpublished_articles', + 'limit' => 10, + 'order' => [ + 'id' => 'desc', + ], + ], +]; + +$publishedArticles = $this->paginate( + $this->Articles->find('all', scope: 'published_articles') + ->where(['published' => true]) +); + +// Load an additional table object to allow differentiating in the paginator +$unpublishedArticlesTable = $this->fetchTable('UnpublishedArticles', [ + 'className' => 'App\Model\Table\ArticlesTable', + 'table' => 'articles', + 'entityClass' => 'App\Model\Entity\Article', +]); + +$unpublishedArticles = $this->paginate( + $unpublishedArticlesTable->find('all', scope: 'unpublished_articles') + ->where(['published' => false]) +); +``` + + + +## Control which Fields Used for Ordering + +By default sorting can be done on any non-virtual column a table has. This is +sometimes undesirable as it allows users to sort on un-indexed columns that can +be expensive to order by. You can set the allowed list of fields that can be sorted +using the `sortableFields` option. This option is required when you want to +sort on any associated data, or computed fields that may be part of your +pagination query: + +``` php +protected array $paginate = [ + 'sortableFields' => [ + 'id', 'title', 'Users.username', 'created', + ], +]; +``` + +Any requests that attempt to sort on fields not in the allowed list will be +ignored. + +## Advanced Sorting with SortableFieldsBuilder + +The `SortableFieldsBuilder` provides a powerful way to create user-friendly +sort URLs while maintaining control over database ordering. Instead of exposing +raw field names in URLs, you can map friendly keys to complex sorting logic with +multi-column support and direction control. + +### Using the Builder + +You can configure sortable fields using a callable that receives a +`SortableFieldsBuilder` instance: + +``` php +use Cake\Datasource\Paging\SortField; +use Cake\Datasource\Paging\SortableFieldsBuilder; + +protected array $paginate = [ + 'sortableFields' => function (SortableFieldsBuilder $builder) { + return $builder + ->add('id', 'Articles.id') + ->add('title', 'Articles.title') + ->add('username', 'Users.username'); + }, +]; +``` + +This configuration allows users to sort using friendly keys like `?sort=username` +instead of exposing the full field name `?sort=Users.username`. + +### Multi-Column Sorting + +The builder supports mapping a single sort key to multiple database fields with +independent direction control. Use the `SortField` class to define complex +sorting: + +``` php +use Cake\Datasource\Paging\SortField; + +protected array $paginate = [ + 'sortableFields' => function ($builder) { + return $builder + ->add('best-deal', [ + SortField::desc('in_stock'), + SortField::asc('price'), + ]) + ->add('popularity', [ + SortField::desc('view_count'), + SortField::asc('title'), + ]); + }, +]; +``` + +Now `?sort=best-deal` will order by in-stock items first (descending), then by +price (ascending). When users toggle the direction (e.g., `?sort=best-deal&direction=asc`), +all fields in the array will toggle their directions: in_stock becomes ASC and +price becomes DESC. + +### Locked Sort Directions + +You can lock a sort direction to prevent users from toggling it. This is useful +when a field should always be sorted in a specific direction: + +``` php +protected array $paginate = [ + 'sortableFields' => function ($builder) { + return $builder + ->add('latest', SortField::desc('created', locked: true)) + ->add('id', SortField::asc('id', locked: true)); + }, +]; +``` + +With locked directions, the sort key will always use the specified direction +regardless of the `direction` parameter in the URL. + +### Combined Sorting Keys + +In addition to the traditional `?sort=field&direction=asc` format, you can use +combined sorting keys in URLs: + +``` text +// These are equivalent +?sort=title&direction=asc +?sort=title-asc + +// These are equivalent +?sort=price&direction=desc +?sort=price-desc +``` + +This provides a cleaner URL format for applications that prefer it. + +### Simple Array Configuration + +For basic use cases where you just need to allow sorting on specific fields +without mapping or multi-column support, you can still use the simple array +format: + +``` php +protected array $paginate = [ + 'sortableFields' => [ + 'id', 'title', 'Users.username', 'created', + ], +]; +``` + +## Limit the Maximum Number of Rows per Page + +The number of results that are fetched per page is exposed to the user as the +`limit` parameter. It is generally undesirable to allow users to fetch all +rows in a paginated set. The `maxLimit` option asserts that no one can set +this limit too high from the outside. By default CakePHP limits the maximum +number of rows that can be fetched to 100. If this default is not appropriate +for your application, you can adjust it as part of the pagination options, for +example reducing it to `10`: + +``` php +protected array $paginate = [ + // Other keys here. + 'maxLimit' => 10 +]; +``` + +If the request's limit param is greater than this value, it will be reduced to +the `maxLimit` value. + +## Out of Range Page Requests + +`Controller::paginate()` will throw a `NotFoundException` when trying to +access a non-existent page, i.e. page number requested is greater than total +page count. + +So you could either let the normal error page be rendered or use a try catch +block and take appropriate action when a `NotFoundException` is caught: + +``` php +use Cake\Http\Exception\NotFoundException; + +public function index() +{ + try { + $this->paginate(); + } catch (NotFoundException $e) { + // Do something here like redirecting to first or last page. + // $e->getPrevious()->getAttributes('pagingParams') will give you required info. + } +} +``` + +## Using a paginator class directly + +You can also use a paginator directly.: + +``` php +// Create a paginator +$paginator = new \Cake\Datasource\Paginator\NumericPaginator(); + +// Paginate the model +$results = $paginator->paginate( + // Query or table instance which you need to paginate + $this->fetchTable('Articles'), + // Request params + $this->request->getQueryParams(), + // Config array having the same structure as options as Controller::$paginate + [ + 'finder' => 'latest', + ] +); +``` + +## Pagination in the View + +Check the `Cake\View\Helper\PaginatorHelper` documentation for +how to create links for pagination navigation. diff --git a/docs/en/controllers/request-response.md b/docs/en/controllers/request-response.md new file mode 100644 index 0000000000..c9b10140cd --- /dev/null +++ b/docs/en/controllers/request-response.md @@ -0,0 +1,1472 @@ +# Request & Response Objects + +The request and response objects provide an abstraction around HTTP requests and +responses. The request object in CakePHP allows you to introspect an incoming +request, while the response object allows you to effortlessly create HTTP +responses from your controllers. + +
+ +\$this-\>request + +
+ + + +## Request + +`class` Cake\\Http\\**ServerRequest** + +`ServerRequest` is the default request object used in CakePHP. It centralizes a +number of features for interrogating and interacting with request data. +On each request one Request is created and then passed by reference to the +various layers of an application that use request data. By default the request +is assigned to `$this->request`, and is available in Controllers, Cells, Views +and Helpers. You can also access it in Components using the controller +reference. + +::: info Changed in version 4.4.0 +The `ServerRequest` is available via DI. So you can get it from container or use it as a dependency for your service. +::: + +Some of the duties `ServerRequest` performs include: + +- Processing the GET, POST, and FILES arrays into the data structures you are + familiar with. +- Providing environment introspection pertaining to the request. Information + like the headers sent, the client's IP address, and the subdomain/domain + names the server your application is running on. +- Providing access to request parameters both as array indexes and object + properties. + +CakePHP's request object implements the [PSR-7 +ServerRequestInterface](https://www.php-fig.org/psr/psr-7/) making it easier to +use libraries from outside of CakePHP. + + + +### Request Parameters + +The request exposes routing parameters through the `getParam()` method: + +``` php +$controllerName = $this->request->getParam('controller'); +``` + +To get all routing parameters as an array use `getAttribute()`: + +``` php +$parameters = $this->request->getAttribute('params'); +``` + +All [Route Elements](../development/routing#route-elements) are accessed through this interface. + +In addition to [Route Elements](../development/routing#route-elements), you also often need access to +[Passed Arguments](../development/routing#passed-arguments). These are both available on the request object as +well: + +``` php +// Passed arguments +$passedArgs = $this->request->getParam('pass'); +``` + +Will all provide you access to the passed arguments. There +are several important/useful parameters that CakePHP uses internally, these +are also all found in the routing parameters: + +- `plugin` The plugin handling the request. Will be null when there is no + plugin. +- `controller` The controller handling the current request. +- `action` The action handling the current request. +- `prefix` The prefix for the current action. See [Prefix Routing](../development/routing#prefix-routing) for + more information. + +### Query String Parameters + +`method` Cake\\Http\\ServerRequest::**getQuery**($name, $default = null): mixed + +Query string parameters can be read using the `getQuery()` method: + +``` php +// URL is /posts/index?page=1&sort=title +$page = $this->request->getQuery('page'); +``` + +You can either directly access the query property, or you can use +`getQuery()` method to read the URL query array in an error-free manner. +Any keys that do not exist will return `null`: + +``` php +$foo = $this->request->getQuery('value_that_does_not_exist'); +// $foo === null + +// You can also provide default values +$foo = $this->request->getQuery('does_not_exist', 'default val'); +``` + +If you want to access all the query parameters you can use +`getQueryParams()`: + +``` php +$query = $this->request->getQueryParams(); +``` + +You can use the casting utility functions to provide typesafe access to request +data and other input: + +``` php +use function Cake\Core\toBool; +use function Cake\Core\toInt; +use function Cake\Core\toString; +use function Cake\I18n\toDate; +use function Cake\I18n\toDateTime; + +// $active is bool|null. +$active = toBool($this->request->getQuery('active')); + +// $page is int|null. +$page = toInt($this->request->getQuery('page')); + +// $query is string|null. +$query = toString($this->request->getQuery('query')); + +// Parse a date based on the format or null +$date = toDate($this->request->getQuery('date'), 'Y-m-d'); + +// Parse a datetime based on a format or null +$date = toDateTime($this->request->getQuery('datetime'), 'Y-m-d H:i:s'); +``` + +::: info Added in version 5.1.0 +Casting functions were added. +::: + +### Request Body Data + +`method` Cake\\Http\\ServerRequest::**getData**($name, $default = null): mixed + +All POST data normally available through PHP's `$_POST` global variable can be +accessed using `Cake\Http\ServerRequest::getData()`. For example: + +``` php +// An input with a name attribute equal to 'title' is accessible at +$title = $this->request->getData('title'); +``` + +You can use a dot separated names to access nested data. For example: + +``` php +$value = $this->request->getData('address.street_name'); +``` + +For non-existent names the `$default` value will be returned: + +``` php +$foo = $this->request->getData('value.that.does.not.exist'); +// $foo == null +``` + +You can also use [Body Parser Middleware](../controllers/middleware#body-parser-middleware) to parse request body of different +content types into an array, so that it's accessible through `ServerRequest::getData()`. + +If you want to access all the data parameters you can use +`getParsedBody()`: + +``` php +$data = $this->request->getParsedBody(); +``` + + + +### File Uploads + +Uploaded files can be accessed through the request body data, using the `Cake\Http\ServerRequest::getData()` +method described above. For example, a file from an input element with a name attribute of `attachment`, can +be accessed like this: + +``` php +$attachment = $this->request->getData('attachment'); +``` + +By default file uploads are represented in the request data as objects that implement +[\Psr\Http\Message\UploadedFileInterface](https://www.php-fig.org/psr/psr-7/#16-uploaded-files). In the current +implementation, the `$attachment` variable in the above example would by default hold an instance of +`\Laminas\Diactoros\UploadedFile`. + +Accessing the uploaded file details is fairly simple, here's how you can obtain the same data as provided by the old +style file upload array: + +``` php +$name = $attachment->getClientFilename(); +$type = $attachment->getClientMediaType(); +$size = $attachment->getSize(); +$tmpName = $attachment->getStream()->getMetadata('uri'); +$error = $attachment->getError(); +``` + +Moving the uploaded file from its temporary location to the desired target +location, doesn't require manually accessing the temporary file, instead it can +be easily done by using the objects `moveTo()` method: + +``` php +$attachment->moveTo($targetPath); +``` + +In an HTTP environment, the `moveTo()` method will automatically validate +whether the file is an actual uploaded file, and throw an exception in case +necessary. In an CLI environment, where the concept of uploading files doesn't +exist, it will allow to move the file that you've referenced irrespective of its +origins, which makes testing file uploads possible. + +`method` Cake\\Http\\ServerRequest::**getUploadedFile**($path): UploadedFileInterface|null + +Returns the uploaded file at a specific path. The path uses the same dot syntax as the +`Cake\Http\ServerRequest::getData()` method: + +``` php +$attachment = $this->request->getUploadedFile('attachment'); +``` + +Unlike `Cake\Http\ServerRequest::getData()`, `Cake\Http\ServerRequest::getUploadedFile()` would +only return data when an actual file upload exists for the given path, if there is regular, non-file request body data +present at the given path, then this method will return `null`, just like it would for any non-existent path. + +`method` Cake\\Http\\ServerRequest::**getUploadedFiles**(): array + +Returns all uploaded files in a normalized array structure. For the above example with the file input name of +`attachment`, the structure would look like: + +``` php +[ + 'attachment' => object(Laminas\Diactoros\UploadedFile) { + // ... + } +] +``` + +`method` Cake\\Http\\ServerRequest::**withUploadedFiles**(array $files): static + +This method sets the uploaded files of the request object, it accepts an array of objects that implement +[\Psr\Http\Message\UploadedFileInterface](https://www.php-fig.org/psr/psr-7/#16-uploaded-files). It will +replace all possibly existing uploaded files: + +``` php +$files = [ + 'MyModel' => [ + 'attachment' => new \Laminas\Diactoros\UploadedFile( + $streamOrFile, + $size, + $errorStatus, + $clientFilename, + $clientMediaType + ), + 'anotherAttachment' => new \Laminas\Diactoros\UploadedFile( + '/tmp/hfz6dbn.tmp', + 123, + \UPLOAD_ERR_OK, + 'attachment.txt', + 'text/plain' + ), + ], +]; + +$this->request = $this->request->withUploadedFiles($files); +``` + +> [!NOTE] +> Uploaded files that have been added to the request via this method, will *not* be available in the request body +> data, ie you cannot retrieve them via `Cake\Http\ServerRequest::getData()`! If you need them in the +> request data (too), then you have to set them via `Cake\Http\ServerRequest::withData()` or +> `Cake\Http\ServerRequest::withParsedBody()`. + +### PUT, PATCH or DELETE Data + +`method` Cake\\Http\\ServerRequest::**getBody**(): StreamInterface + +When building REST services, you often accept request data on `PUT` and +`DELETE` requests. Any `application/x-www-form-urlencoded` request body data +will automatically be parsed and available via `$request->getData()` for `PUT` and +`DELETE` requests. If you are accepting JSON or XML data, you can +access the raw data with `getBody()`: + +``` php +// Get the stream wrapper on the request body +$body = $request->getBody(); + +// Get the request body as a string +$bodyString = (string)$request->getBody(); +``` + +If your requests contain XML or JSON request content, you should consider using +[Body Parser Middleware](../controllers/middleware#body-parser-middleware) to have CakePHP automatically parse those content +types making the parsed data available in `$request->getData()` and +`$request->getParsedBody()`. + +### Environment Variables (from \$\_SERVER and \$\_ENV) + +`method` Cake\\Http\\ServerRequest::**getEnv**($key, $default = null): string|null + +`ServerRequest::getEnv()` is a wrapper for `getenv()` global function and acts as +a getter for environment variables without possible undefined keys: + +``` php +$host = $this->request->getEnv('HTTP_HOST'); +``` + +To access all the environment variables in a request use `getServerParams()`: + +``` php +$env = $this->request->getServerParams(); +``` + +`method` Cake\\Http\\ServerRequest::**withEnv**($key, $value): static + +`ServerRequest::withEnv()` is a wrapper for `putenv()` global function and acts as +a setter for environment variables without having to modify globals +`$_SERVER` and `$_ENV`: + +``` php +// Set a value, generally helpful in testing. +$this->request->withEnv('REQUEST_METHOD', 'POST'); +``` + +### XML or JSON Data + +Applications employing [REST](../development/rest) often exchange data in +non-URL-encoded post bodies. You can read input data in any format using +`Cake\Http\ServerRequest::input()`. By providing a decoding function, +you can receive the content in a deserialized format: + +``` php +// Get JSON encoded data submitted to a PUT/POST action +$jsonData = $this->request->input('json_decode'); +``` + +Some deserializing methods require additional parameters when called, such as +the 'as array' parameter on `json_decode`. If you want XML converted into a +DOMDocument object, `Cake\Http\ServerRequest::input()` supports +passing in additional parameters as well: + +``` php +// Get XML encoded data submitted to a PUT/POST action +$data = $this->request->input('Cake\Utility\Xml::build', ['return' => 'domdocument']); +``` + +### Path Information + +The request object also provides useful information about the paths in your +application. The `base` and `webroot` attributes are useful for +generating URLs, and determining whether or not your application is in a +subdirectory. The attributes you can use are: + +``` php +// Assume the current request URL is /subdir/articles/edit/1?page=1 + +// Holds /subdir/articles/edit/1?page=1 +$here = $request->getRequestTarget(); + +// Holds /subdir +$base = $request->getAttribute('base'); + +// Holds /subdir/ +$base = $request->getAttribute('webroot'); +``` + + + +### Checking Request Conditions + +`method` Cake\\Http\\ServerRequest::**is**($type, $args...): bool + +The request object provides a way to inspect certain conditions in a given +request. By using the `is()` method you can check a number of common +conditions, as well as inspect other application specific request criteria: + +``` php +$isPost = $this->request->is('post'); +``` + +You can also extend the request detectors that are available, by using +`Cake\Http\ServerRequest::addDetector()` to create new kinds of +detectors. There are different types of detectors that you can create: + +- Environment value comparison - Compares a value fetched from `env()` + for equality with the provided value. +- Header value comparison - If the specified header exists with the specified + value, or if the callable returns true. +- Pattern value comparison - Pattern value comparison allows you to compare a + value fetched from `env()` to a regular expression. +- Option based comparison - Option based comparisons use a list of options to + create a regular expression. Subsequent calls to add an already defined + options detector will merge the options. +- Callback detectors - Callback detectors allow you to provide a 'callback' type + to handle the check. The callback will receive the request object as its only + parameter. + +`method` Cake\\Http\\ServerRequest::**addDetector**($name, $options): void + +Some examples would be: + +``` php +// Add an environment detector. +$this->request->addDetector( + 'post', + ['env' => 'REQUEST_METHOD', 'value' => 'POST'] +); + +// Add a pattern value detector. +$this->request->addDetector( + 'iphone', + ['env' => 'HTTP_USER_AGENT', 'pattern' => '/iPhone/i'] +); + +// Add an option detector +$this->request->addDetector('internalIp', [ + 'env' => 'CLIENT_IP', + 'options' => ['192.168.0.101', '192.168.0.100'] +]); + + +// Add a header detector with value comparison +$this->request->addDetector('fancy', [ + 'env' => 'CLIENT_IP', + 'header' => ['X-Fancy' => 1] +]); + +// Add a header detector with callable comparison +$this->request->addDetector('fancy', [ + 'env' => 'CLIENT_IP', + 'header' => ['X-Fancy' => function ($value, $header) { + return in_array($value, ['1', '0', 'yes', 'no'], true); + }] +]); + +// Add a callback detector. Must be a valid callable. +$this->request->addDetector( + 'awesome', + function ($request) { + return $request->getParam('awesome'); + } +); + +// Add a detector that uses additional arguments. +$this->request->addDetector( + 'csv', + [ + 'accept' => ['text/csv'], + 'param' => '_ext', + 'value' => 'csv', + ] +); +``` + +There are several built-in detectors that you can use: + +- `is('get')` Check to see whether the current request is a GET. +- `is('put')` Check to see whether the current request is a PUT. +- `is('patch')` Check to see whether the current request is a PATCH. +- `is('post')` Check to see whether the current request is a POST. +- `is('delete')` Check to see whether the current request is a DELETE. +- `is('head')` Check to see whether the current request is HEAD. +- `is('options')` Check to see whether the current request is OPTIONS. +- `is('ajax')` Check to see whether the current request came with + X-Requested-With = XMLHttpRequest. +- `is('ssl')` Check to see whether the request is via SSL. +- `is('flash')` Check to see whether the request has a User-Agent of Flash. +- `is('json')` Check to see whether the request URL has 'json' extension or the + Accept header is set to 'application/json'. +- `is('xml')` Check to see whether the request URL has 'xml' extension or the Accept header is set to + 'application/xml' or 'text/xml'. + +`ServerRequest` also includes methods like +`Cake\Http\ServerRequest::domain()`, +`Cake\Http\ServerRequest::subdomains()` and +`Cake\Http\ServerRequest::host()` to make applications that use +subdomains simpler. + +### Session Data + +To access the session for a given request use the `getSession()` method or use the `session` attribute: + +``` php +$session = $this->request->getSession(); +$session = $this->request->getAttribute('session'); + +$data = $session->read('sessionKey'); +``` + +For more information, see the [Sessions](../development/sessions) documentation for how +to use the session object. + +### Host and Domain Name + +`method` Cake\\Http\\ServerRequest::**domain**($tldLength = 1): string + +Returns the domain name your application is running on: + +``` php +// Prints 'example.org' +echo $request->domain(); +``` + +`method` Cake\\Http\\ServerRequest::**subdomains**($tldLength = 1): array + +Returns the subdomains your application is running on as an array: + +``` php +// Returns ['my', 'dev'] for 'my.dev.example.org' +$subdomains = $request->subdomains(); +``` + +`method` Cake\\Http\\ServerRequest::**host**(): string|null + +Returns the host your application is on: + +``` php +// Prints 'my.dev.example.org' +echo $request->host(); +``` + +### Reading the HTTP Method + +`method` Cake\\Http\\ServerRequest::**getMethod**(): string + +Returns the HTTP method the request was made with: + +``` php +// Output POST +echo $request->getMethod(); +``` + +### Restricting Which HTTP method an Action Accepts + +`method` Cake\\Http\\ServerRequest::**allowMethod**($methods): bool + +Set allowed HTTP methods. If not matched, will throw +`MethodNotAllowedException`. The 405 response will include the required +`Allow` header with the passed methods: + +``` php +public function delete() +{ + // Only accept POST and DELETE requests + $this->request->allowMethod(['post', 'delete']); + ... +} +``` + +### Reading HTTP Headers + +Allows you to access any of the `HTTP_*` headers that were used +for the request. For example: + +``` php +// Get the header as a string +$userAgent = $this->request->getHeaderLine('User-Agent'); + +// Get an array of all values. +$acceptHeader = $this->request->getHeader('Accept'); + +// Check if a header exists +$hasAcceptHeader = $this->request->hasHeader('Accept'); +``` + +While some apache installs don't make the `Authorization` header accessible, +CakePHP will make it available through apache specific methods as required. + +`method` Cake\\Http\\ServerRequest::**referer**($local = true): string|null + +Returns the referring address for the request. + +`method` Cake\\Http\\ServerRequest::**clientIp**(): string + +Returns the current visitor's IP address. + +### Trusting Proxy Headers + +If your application is behind a load balancer or running on a cloud service, you +will often get the load balancer host, port and scheme in your requests. Often +load balancers will also send `HTTP-X-Forwarded-*` headers with the original +values. The forwarded headers will not be used by CakePHP out of the box. To +have the request object use these headers set the `trustProxy` property to +`true`: + +``` php +$this->request->trustProxy = true; + +// These methods will now use the proxied headers. +$port = $this->request->port(); +$host = $this->request->host(); +$scheme = $this->request->scheme(); +$clientIp = $this->request->clientIp(); +``` + +Once proxies are trusted the `clientIp()` method will use the *last* IP +address in the `X-Forwarded-For` header. If your application is behind +multiple proxies, you can use `setTrustedProxies()` to define the IP addresses +of proxies in your control: + +``` php +$request->setTrustedProxies(['127.1.1.1', '127.8.1.3']); +``` + +After proxies are trusted `clientIp()` will use the first IP address in the +`X-Forwarded-For` header providing it is the only value that isn't from a trusted +proxy. + +### Checking Accept Headers + +`method` Cake\\Http\\ServerRequest::**accepts**($type = null): array|bool + +Find out which content types the client accepts, or check whether it accepts a +particular type of content. + +Get all types: + +``` php +$accepts = $this->request->accepts(); +``` + +Check for a single type: + +``` php +$acceptsJson = $this->request->accepts('application/json'); +``` + +`method` Cake\\Http\\ServerRequest::**acceptLanguage**($language = null): array|bool + +Get all the languages accepted by the client, +or check whether a specific language is accepted. + +Get the list of accepted languages: + +``` php +$acceptsLanguages = $this->request->acceptLanguage(); +``` + +Check whether a specific language is accepted: + +``` php +$acceptsSpanish = $this->request->acceptLanguage('es-es'); +``` + + + +### Reading Cookies + +Request cookies can be read through a number of methods: + +``` php +// Get the cookie value, or null if the cookie is missing. +$rememberMe = $this->request->getCookie('remember_me'); + +// Read the value, or get the default of 0 +$rememberMe = $this->request->getCookie('remember_me', 0); + +// Get all cookies as an hash +$cookies = $this->request->getCookieParams(); + +// Get a CookieCollection instance +$cookies = $this->request->getCookieCollection() +``` + +See the `Cake\Http\Cookie\CookieCollection` documentation for how +to work with cookie collection. + +### Uploaded Files + +Requests expose the uploaded file data in `getData()` or +`getUploadedFiles()` as `UploadedFileInterface` objects: + +``` php +// Get a list of UploadedFile objects +$files = $request->getUploadedFiles(); + +// Read the file data. +$files[0]->getStream(); +$files[0]->getSize(); +$files[0]->getClientFileName(); + +// Move the file. +$files[0]->moveTo($targetPath); +``` + +### Manipulating URIs + +Requests contain a URI object, which contains methods for interacting with the +requested URI: + +``` php +// Get the URI +$uri = $request->getUri(); + +// Read data out of the URI. +$path = $uri->getPath(); +$query = $uri->getQuery(); +$host = $uri->getHost(); +``` + +
+ +\$this-\>response + +
+ +## Response + +`class` Cake\\Http\\**Response** + +`Cake\Http\Response` is the default response class in CakePHP. +It encapsulates a number of features and functionality for generating HTTP +responses in your application. It also assists in testing, as it can be +mocked/stubbed allowing you to inspect headers that will be sent. + +`Response` provides an interface to wrap the common response-related +tasks such as: + +- Sending headers for redirects. +- Sending content type headers. +- Sending any header. +- Sending the response body. + +### Dealing with Content Types + +`method` Cake\\Http\\Response::**withType**($contentType = null): static + +You can control the Content-Type of your application's responses with +`Cake\Http\Response::withType()`. If your application needs to deal +with content types that are not built into Response, you can map them with +`setTypeMap()` as well: + +``` php +// Add a vCard type +$this->response->setTypeMap('vcf', ['text/v-card']); + +// Set the response Content-Type to vcard. +$this->response = $this->response->withType('vcf'); +``` + +Usually, you'll want to map additional content types in your controller's +`~Controller::beforeFilter()` callback, so you can benefit from +automatic view switching provided by [Controller Viewclasses](../controllers#controller-viewclasses). + + + +### Sending Files + +`method` Cake\\Http\\Response::**withFile**(string $path, array $options = []): static + +There are times when you want to send files as responses for your requests. +You can accomplish that by using `Cake\Http\Response::withFile()`: + +``` php +public function sendFile($id) +{ + $file = $this->Attachments->getFile($id); + $response = $this->response->withFile($file['path']); + // Return the response to prevent controller from trying to render + // a view. + return $response; +} +``` + +As shown in the above example, you must pass the file path to the method. +CakePHP will send a proper content type header if it's a known file type listed +in CakeHttpResponse::\$\_mimeTypes. You can add new types prior to calling +`Cake\Http\Response::withFile()` by using the +`Cake\Http\Response::withType()` method. + +If you want, you can also force a file to be downloaded instead of displayed in +the browser by specifying the options: + +``` php +$response = $this->response->withFile( + $file['path'], + ['download' => true, 'name' => 'foo'] +); +``` + +The supported options are: + +name +The name allows you to specify an alternate file name to be sent to +the user. + +download +A boolean value indicating whether headers should be set to force +download. + +### Sending a String as File + +You can respond with a file that does not exist on the disk, such as a pdf or an +ics generated on the fly from a string: + +``` php +public function sendIcs() +{ + $icsString = $this->Calendars->generateIcs(); + $response = $this->response; + + // Inject string content into response body + $response = $response->withStringBody($icsString); + + $response = $response->withType('ics'); + + // Optionally force file download + $response = $response->withDownload('filename_for_download.ics'); + + // Return response object to prevent controller from trying to render + // a view. + return $response; +} +``` + +### Setting Headers + +`method` Cake\\Http\\Response::**withHeader**($header, $value) + +Setting headers is done with the `Cake\Http\Response::withHeader()` +method. Like all of the PSR-7 interface methods, this method returns a *new* +instance with the new header: + +``` php +// Add/replace a header +$response = $response->withHeader('X-Extra', 'My header'); + +// Set multiple headers +$response = $response->withHeader('X-Extra', 'My header') + ->withHeader('Location', 'http://example.com'); + +// Append a value to an existing header +$response = $response->withAddedHeader('Set-Cookie', 'remember_me=1'); +``` + +Headers are not sent when set. Instead, they are held until the response is +emitted by `Cake\Http\Server`. + +You can now use the convenience method +`Cake\Http\Response::withLocation()` to directly set or get the +redirect location header. + +### Setting the Body + +`method` Cake\\Http\\Response::**withStringBody**($string): static + +To set a string as the response body, do the following: + +``` php +// Set a string into the body +$response = $response->withStringBody('My Body'); + +// If you want a json response +$response = $response->withType('application/json') + ->withStringBody(json_encode(['Foo' => 'bar'])); +``` + +`method` Cake\\Http\\Response::**withBody**($body) + +To set the response body, use the `withBody()` method, which is provided by the +`Laminas\Diactoros\MessageTrait`: + +``` php +$response = $response->withBody($stream); +``` + +Be sure that `$stream` is a `Psr\Http\Message\StreamInterface` object. +See below on how to create a new stream. + +You can also stream responses from files using `Laminas\Diactoros\Stream` streams: + +``` php +// To stream from a file +use Laminas\Diactoros\Stream; + +$stream = new Stream('/path/to/file', 'rb'); +$response = $response->withBody($stream); +``` + +You can also stream responses from a callback using the `CallbackStream`. This +is useful when you have resources like images, CSV files or PDFs you need to +stream to the client: + +``` php +// Streaming from a callback +use Cake\Http\CallbackStream; + +// Create an image. +$img = imagecreate(100, 100); +// ... + +$stream = new CallbackStream(function () use ($img) { + imagepng($img); +}); +$response = $response->withBody($stream); +``` + +### Setting the Character Set + +`method` Cake\\Http\\Response::**withCharset**($charset): static + +Sets the charset that will be used in the response: + +``` php +$this->response = $this->response->withCharset('UTF-8'); +``` + +### Interacting with Browser Caching + +`method` Cake\\Http\\Response::**withDisabledCache**(): static + +You sometimes need to force browsers not to cache the results of a controller +action. `Cake\Http\Response::withDisabledCache()` is intended for just +that: + +``` php +public function index() +{ + // Disable caching + $this->response = $this->response->withDisabledCache(); +} +``` + +> [!WARNING] +> Disabling caching from SSL domains while trying to send +> files to Internet Explorer can result in errors. + +`method` Cake\\Http\\Response::**withCache**($since, $time = '+1 day'): static + +You can also tell clients that you want them to cache responses. By using +`Cake\Http\Response::withCache()`: + +``` php +public function index() +{ + // Enable caching + $this->response = $this->response->withCache('-1 minute', '+5 days'); +} +``` + +The above would tell clients to cache the resulting response for 5 days, +hopefully speeding up your visitors' experience. +The `withCache()` method sets the `Last-Modified` value to the first +argument. `Expires` header and the `max-age` directive are set based on the +second parameter. Cache-Control's `public` directive is set as well. + + + +### Fine Tuning HTTP Cache + +One of the best and easiest ways of speeding up your application is to use HTTP +cache. Under this caching model, you are only required to help clients decide if +they should use a cached copy of the response by setting a few headers such as +modified time and response entity tag. + +Rather than forcing you to code the logic for caching and for invalidating +(refreshing) it once the data has changed, HTTP uses two models, expiration and +validation, which usually are much simpler to use. + +Apart from using `Cake\Http\Response::withCache()`, you can also use +many other methods to fine-tune HTTP cache headers to take advantage of browser +or reverse proxy caching. + +#### The Cache Control Header + +`method` Cake\\Http\\Response::**withSharable**($public, $time = null): static + +Used under the expiration model, this header contains multiple indicators that +can change the way browsers or proxies use the cached content. A +`Cache-Control` header can look like this: + + Cache-Control: private, max-age=3600, must-revalidate + +`Response` class helps you set this header with some utility methods that will +produce a final valid `Cache-Control` header. The first is the +`withSharable()` method, which indicates whether a response is to be +considered sharable across different users or clients. This method actually +controls the `public` or `private` part of this header. Setting a response +as private indicates that all or part of it is intended for a single user. To +take advantage of shared caches, the control directive must be set as public. + +The second parameter of this method is used to specify a `max-age` for the +cache, which is the number of seconds after which the response is no longer +considered fresh: + +``` php +public function view() +{ + // ... + // Set the Cache-Control as public for 3600 seconds + $this->response = $this->response->withSharable(true, 3600); +} + +public function my_data() +{ + // ... + // Set the Cache-Control as private for 3600 seconds + $this->response = $this->response->withSharable(false, 3600); +} +``` + +`Response` exposes separate methods for setting each of the directives in +the `Cache-Control` header. + +#### The Expiration Header + +`method` Cake\\Http\\Response::**withExpires**($time): static + +You can set the `Expires` header to a date and time after which the response +is no longer considered fresh. This header can be set using the +`withExpires()` method: + +``` php +public function view() +{ + $this->response = $this->response->withExpires('+5 days'); +} +``` + +This method also accepts a `DateTime` instance or any string that can +be parsed by the `DateTime` class. + +#### The Etag Header + +`method` Cake\\Http\\Response::**withEtag**($tag, $weak = false): static + +Cache validation in HTTP is often used when content is constantly changing, and +asks the application to only generate the response contents if the cache is no +longer fresh. Under this model, the client continues to store pages in the +cache, but it asks the application every time +whether the resource has changed, instead of using it directly. +This is commonly used with static resources such as images and other assets. + +The `withEtag()` method (called entity tag) is a string +that uniquely identifies the requested resource, as a checksum does for a file, +in order to determine whether it matches a cached resource. + +To take advantage of this header, you must either call the +`isNotModified()` method manually or include the +[Checking HTTP Cache](../controllers/components/check-http-cache) in your controller: + +``` php +public function index() +{ + $articles = $this->Articles->find('all')->all(); + + // Simple checksum of the article contents. + // You should use a more efficient implementation + // in a real world application. + $checksum = md5(json_encode($articles)); + + $response = $this->response->withEtag($checksum); + if ($response->isNotModified($this->request)) { + return $response; + } + + $this->response = $response; + // ... +} +``` + +> [!NOTE] +> Most proxy users should probably consider using the Last Modified Header +> instead of Etags for performance and compatibility reasons. + +#### The Last Modified Header + +`method` Cake\\Http\\Response::**withModified**($time): static + +Also, under the HTTP cache validation model, you can set the `Last-Modified` +header to indicate the date and time at which the resource was modified for the +last time. Setting this header helps CakePHP tell caching clients whether the +response was modified or not based on their cache. + +To take advantage of this header, you must either call the +`isNotModified()` method manually or include the +[Checking HTTP Cache](../controllers/components/check-http-cache) in your controller: + +``` php +public function view() +{ + $article = $this->Articles->find()->first(); + $response = $this->response->withModified($article->modified); + if ($response->isNotModified($this->request)) { + return $response; + } + $this->response; + // ... +} +``` + +#### The Vary Header + +`method` Cake\\Http\\Response::**withVary**($header): static + +In some cases, you might want to serve different content using the same URL. +This is often the case if you have a multilingual page or respond with different +HTML depending on the browser. Under such circumstances you can use the `Vary` +header: + +``` php +$response = $this->response->withVary('User-Agent'); +$response = $this->response->withVary('Accept-Encoding', 'User-Agent'); +$response = $this->response->withVary('Accept-Language'); +``` + +#### Sending Not-Modified Responses + +`method` Cake\\Http\\Response::**isNotModified**(Request $request): bool + +Compares the cache headers for the request object with the cache header from the +response and determines whether it can still be considered fresh. If so, deletes +the response content, and sends the 304 Not Modified header: + +``` php +// In a controller action. +if ($this->response->isNotModified($this->request)) { + return $this->response; +} +``` + + + +### Setting Cookies + +Cookies can be added to response using either an array or a `Cake\Http\Cookie\Cookie` +object: + +``` php +use Cake\Http\Cookie\Cookie; +use DateTime; + +// Add a cookie +$this->response = $this->response->withCookie(Cookie::create( + 'remember_me', + 'yes', + // All keys are optional + [ + 'expires' => new DateTime('+1 year'), + 'path' => '', + 'domain' => '', + 'secure' => false, + 'httponly' => false, + 'samesite' => null // Or one of CookieInterface::SAMESITE_* constants + ] +)); +``` + +See the [Creating Cookies](#creating-cookies) section for how to use the cookie object. You +can use `withExpiredCookie()` to send an expired cookie in the response. This +will make the browser remove its local cookie: + +``` php +$this->response = $this->response->withExpiredCookie(new Cookie('remember_me')); +``` + + + +### Setting Cross Origin Request Headers (CORS) + +The `cors()` method returns a `CorsBuilder` instance which provides a fluent +interface for defining [HTTP Access Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) +related headers: + +``` php +$this->response = $this->response->cors($this->request) + ->allowOrigin(['*.cakephp.org']) + ->allowMethods(['GET', 'POST']) + ->allowHeaders(['X-CSRF-Token']) + ->allowCredentials() + ->exposeHeaders(['Link']) + ->maxAge(300) + ->build(); +``` + +CORS related headers will only be applied to the response if the following +criteria are met: + +1. The request has an `Origin` header. +2. The request's `Origin` value matches one of the allowed Origin values. + +#### CorsBuilder Methods + +`class` Cake\\Http\\**CorsBuilder** + +The `CorsBuilder` provides the following methods for configuring CORS: + +`method` Cake\\Http\\CorsBuilder::**allowOrigin**(array|string $domains) + +`method` Cake\\Http\\CorsBuilder::**allowMethods**(array $methods) + +`method` Cake\\Http\\CorsBuilder::**allowHeaders**(array $headers) + +`method` Cake\\Http\\CorsBuilder::**allowCredentials**() + +`method` Cake\\Http\\CorsBuilder::**exposeHeaders**(array $headers) + +`method` Cake\\Http\\CorsBuilder::**maxAge**(string|int $age) + +`method` Cake\\Http\\CorsBuilder::**build**(): ResponseInterface + +#### Practical CORS Examples + +Here are some common CORS configurations: + +**API accepting requests from a SPA frontend**: + +``` php +// In your controller +public function beforeFilter(EventInterface $event) +{ + parent::beforeFilter($event); + + if ($this->request->is('options')) { + // Handle preflight requests + $this->response = $this->response->cors($this->request) + ->allowOrigin(['https://app.example.com']) + ->allowMethods(['GET', 'POST', 'PUT', 'DELETE']) + ->allowHeaders(['Content-Type', 'Authorization']) + ->allowCredentials() + ->maxAge(86400) + ->build(); + + $event->setResult($this->response); + } +} + +public function index() +{ + // Apply CORS to regular requests + $this->response = $this->response->cors($this->request) + ->allowOrigin(['https://app.example.com']) + ->allowCredentials() + ->build(); + + // Your regular controller logic... +} +``` + +**Public API with relaxed CORS**: + +``` php +$this->response = $this->response->cors($this->request) + ->allowOrigin('*') + ->allowMethods(['GET']) + ->exposeHeaders(['X-Total-Count', 'X-Page']) + ->maxAge(3600) + ->build(); +``` + +#### Creating CORS Middleware + +For consistent CORS handling across your application, create a middleware: + +``` php +// src/Middleware/CorsMiddleware.php +namespace App\Middleware; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; + +class CorsMiddleware implements MiddlewareInterface +{ + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface { + // Handle preflight requests + if ($request->getMethod() === 'OPTIONS') { + $response = new \Cake\Http\Response(); + $response = $response->cors($request) + ->allowOrigin(['*.myapp.com']) + ->allowMethods(['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']) + ->allowHeaders(['Content-Type', 'Authorization']) + ->allowCredentials() + ->maxAge(3600) + ->build(); + + return $response; + } + + $response = $handler->handle($request); + + // Add CORS headers to regular requests + return $response->cors($request) + ->allowOrigin(['*.myapp.com']) + ->allowCredentials() + ->build(); + } +} +``` + +Then add it to your application middleware stack in `src/Application.php`: + +``` php +public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue +{ + $middlewareQueue + // Add CORS middleware early in the stack + ->add(new \App\Middleware\CorsMiddleware()) + // ... other middleware + ->add(new ErrorHandlerMiddleware(Configure::read('Error'))) + ->add(new AssetMiddleware([ + 'cacheTime' => Configure::read('Asset.cacheTime'), + ])) + ->add(new RoutingMiddleware($this)); + + return $middlewareQueue; +} +``` + +### Running logic after the Response has been sent + +In fastcgi based environments you can listen to the `Server.terminate` event +to run logic **after** the response has been sent to the client. The +`terminate` event will be passed a `request` and `response`. The +`request` is fetched from the applications' DI container, or from +`Router::getRequest()` if the DI container does not have a request registered. + +> [!WARNING] +> In non fastcgi environments the `Server.terminate` event is fired before +> the response is sent. + +::: info Added in version 5.1.0 +::: + +## Common Mistakes with Immutable Responses + +Response objects offer a number of methods that treat +responses as immutable objects. Immutable objects help prevent difficult to +track accidental side-effects, and reduce mistakes caused by method calls caused +by refactoring that change ordering. While they offer a number of benefits, +immutable objects can take some getting used to. Any method that starts with +`with` operates on the response in an immutable fashion, and will **always** +return a **new** instance. Forgetting to retain the modified instance is the most +frequent mistake people make when working with immutable objects: + +``` php +$this->response->withHeader('X-CakePHP', 'yes!'); +``` + +In the above code, the response will be lacking the `X-CakePHP` header, as the +return value of the `withHeader()` method was not retained. To correct the +above code you would write: + +``` php +$this->response = $this->response->withHeader('X-CakePHP', 'yes!'); +``` + +## Cookie Collections + +`class` Cake\\Http\\Cookie\\**CookieCollection** + +`CookieCollection` objects are accessible from the request and response objects. +They let you interact with groups of cookies using immutable patterns, which +allow the immutability of the request and response to be preserved. + + + +### Creating Cookies + +`class` Cake\\Http\\Cookie\\**Cookie** + +`Cookie` objects can be defined through constructor objects, or by using the +fluent interface that follows immutable patterns: + +``` php +use Cake\Http\Cookie\Cookie; + +// All arguments in the constructor +$cookie = new Cookie( + 'remember_me', // name + 1, // value + new DateTime('+1 year'), // expiration time, if applicable + '/', // path, if applicable + 'example.com', // domain, if applicable + false, // secure only? + true // http only ? +); + +// Using the builder methods +$cookie = (new Cookie('remember_me')) + ->withValue('1') + ->withExpiry(new DateTime('+1 year')) + ->withPath('/') + ->withDomain('example.com') + ->withSecure(false) + ->withHttpOnly(true); +``` + +Once you have created a cookie, you can add it to a new or existing +`CookieCollection`: + +``` php +use Cake\Http\Cookie\CookieCollection; + +// Create a new collection +$cookies = new CookieCollection([$cookie]); + +// Add to an existing collection +$cookies = $cookies->add($cookie); + +// Remove a cookie by name +$cookies = $cookies->remove('remember_me'); +``` + +> [!NOTE] +> Remember that collections are immutable and adding cookies into, or removing +> cookies from a collection, creates a *new* collection object. + +Cookie objects can be added to responses: + +``` php +// Add one cookie +$response = $this->response->withCookie($cookie); + +// Replace the entire cookie collection +$response = $this->response->withCookieCollection($cookies); +``` + +Cookies set to responses can be encrypted using the +[Encrypted Cookie Middleware](../controllers/middleware#encrypted-cookie-middleware). + + + +### Reading Cookies + +Once you have a `CookieCollection` instance, you can access the cookies it +contains: + +``` php +// Check if a cookie exists +$cookies->has('remember_me'); + +// Get the number of cookies in the collection +count($cookies); + +// Get a cookie instance. Will throw an error if the cookie is not found +$cookie = $cookies->get('remember_me'); + +// Get a cookie or null +$cookie = $cookies->remember_me; + +// Check if a cookie exists +$exists = isset($cookies->remember_me) +``` + +Once you have a `Cookie` object you can interact with it's state and modify +it. Keep in mind that cookies are immutable, so you'll need to update the +collection if you modify a cookie: + +``` php +// Get the value +$value = $cookie->getValue() + +// Access data inside a JSON value +$id = $cookie->read('User.id'); + +// Check state +$cookie->isHttpOnly(); +$cookie->isSecure(); +``` diff --git a/docs/en/core-libraries/app.md b/docs/en/core-libraries/app.md new file mode 100644 index 0000000000..8066e164e5 --- /dev/null +++ b/docs/en/core-libraries/app.md @@ -0,0 +1,133 @@ +# App Class + +`class` Cake\\Core\\**App** + +The App class is responsible for resource location and path management. + +## Finding Classes + +### App::className() + +`static` Cake\\Core\\App::**className**($name, $type = '', $suffix = ''): string|null + +This method is used to resolve class names throughout CakePHP. It resolves +the short form names CakePHP uses and returns the fully resolved class name: + +``` php +// Resolve a short class name with the namespace + suffix. +App::className('Flash', 'Controller/Component', 'Component'); +// Returns Cake\Controller\Component\FlashComponent + +// Resolve a plugin name. +App::className('DebugKit.Toolbar', 'Controller/Component', 'Component'); +// Returns DebugKit\Controller\Component\ToolbarComponent + +// Names with \ in them will be returned unaltered. +App::className('App\Cache\ComboCache'); +// Returns App\Cache\ComboCache +``` + +When resolving classes, the `App` namespace will be tried, and if the +class does not exist the `Cake` namespace will be attempted. If both +class names do not exist, `false` will be returned. + +## Finding Paths to Resources + +### App::path() + +`static` Cake\\Core\\App::**path**(string $package, ?string $plugin = null): array + +The method returns paths set using `App.paths` app config: + +``` php +// Get the templates path set using ``App.paths.templates`` app config. +App::path('templates'); +``` + +The same way you can retrieve paths for `locales` and `plugins`. + +## Finding Paths to Namespaces + +### App::classPath() + +`static` Cake\\Core\\App::**classPath**(string $package, ?string $plugin = null): array + +Used to get locations for paths based on conventions: + +``` php +// Get the path to Controller/ in your application +App::classPath('Controller'); +``` + +This can be done for all namespaces that are part of your application. + +`App::classPath()` will only return the default path, and will not be able to +provide any information about additional paths the autoloader is configured +for. + +### App::core() + +`static` Cake\\Core\\App::**core**(string $package): array + +Used for finding the path to a package inside CakePHP: + +``` php +// Get the path to Cache engines. +App::core('Cache/Engine'); +``` + +## Locating Themes + +Since themes are plugins, you can use the methods above to get the path to +a theme. + +## Loading Vendor Files + +Ideally vendor files should be autoloaded with `Composer`, if you have vendor +files that cannot be autoloaded or installed with Composer you will need to use +`require` to load them. + +If you cannot install a library with Composer, it is best to install each library in +a directory following Composer's convention of `vendor/$author/$package`. +If you had a library called AcmeLib, you could install it into +`vendor/Acme/AcmeLib`. Assuming it did not use PSR-0 compatible classnames +you could autoload the classes within it using `classmap` in your +application's `composer.json`: + +``` json +"autoload": { + "psr-4": { + "App\\": "src/", + "App\\Test\\": "tests/" + }, + "classmap": [ + "vendor/Acme/AcmeLib" + ] +} +``` + +If your vendor library does not use classes, and instead provides functions, you +can configure Composer to load these files at the beginning of each request +using the `files` autoloading strategy: + +``` json +"autoload": { + "psr-4": { + "App\\": "src/", + "App\\Test\\": "tests/" + }, + "files": [ + "vendor/Acme/AcmeLib/functions.php" + ] +} +``` + +After configuring the vendor libraries you will need to regenerate your +application's autoloader using: + +``` bash +$ php composer.phar dump-autoload +``` + +If you happen to not be using Composer in your application, you will need to +manually load all vendor libraries yourself. diff --git a/docs/en/core-libraries/caching.md b/docs/en/core-libraries/caching.md new file mode 100644 index 0000000000..20a47b816b --- /dev/null +++ b/docs/en/core-libraries/caching.md @@ -0,0 +1,729 @@ +# Caching + +`class` Cake\\Cache\\**Cache** + +Caching can be used to make reading from expensive or slow resources faster, by +maintaining a second copy of the required data in a faster or closer storage +system. For example, you can store the results of expensive queries, or remote +webservice access that doesn't frequently change in a cache. Once in the cache, +reading data from the cache is much cheaper than accessing the remote resource. + +Caching in CakePHP is facilitated by the `Cache` class. +This class provides a static interface and uniform API to +interact with various Caching implementations. CakePHP +provides several cache engines, and provides a simple interface if you need to +build your own backend. The built-in caching engines are: + +- `File` File cache is a simple cache that uses local files. It + is the slowest cache engine, and doesn't provide as many features for + atomic operations. However, since disk storage is often quite cheap, + storing large objects, or elements that are infrequently written + work well in files. +- `Memcached` Uses the [Memcached](https://php.net/memcached) + extension. +- `Redis` Uses the [phpredis](https://github.com/phpredis/phpredis) + extension. Redis provides a fast and persistent cache system similar to + Memcached, also provides atomic operations. +- `Apcu` APCu cache uses the PHP [APCu](https://php.net/apcu) extension. + This extension uses shared memory on the webserver to store objects. + This makes it very fast, and able to provide atomic read/write features. +- `Array` Stores all data in an array. This engine does not provide + persistent storage and is intended for use in application test suites. +- `Null` The null engine doesn't actually store anything and fails all read + operations. + +Regardless of the CacheEngine you choose to use, your application interacts with +`Cake\Cache\Cache`. + + + +## Configuring Cache Engines + +### Cache::setConfig() + +`static` Cake\\Cache\\Cache::**setConfig**($key, $config = null): void + +Your application can configure any number of 'engines' during its bootstrap +process. Cache engine configurations are defined in **config/app.php**. + +For optimal performance CakePHP requires two cache engines to be defined. + +- `_cake_core_` is used for storing file maps, and parsed results of + [Internationalization & Localization](../core-libraries/internationalization-and-localization) files. +- `_cake_model_`, is used to store schema descriptions for your applications + models. + +Using multiple engine configurations also lets you incrementally change the +storage as needed. For example in your **config/app.php** you could put the +following: + +``` php +// ... +'Cache' => [ + 'short' => [ + 'className' => 'File', + 'duration' => '+1 hours', + 'path' => CACHE, + 'prefix' => 'cake_short_', + ], + // Using a fully namespaced name. + 'long' => [ + 'className' => 'Cake\Cache\Engine\FileEngine', + 'duration' => '+1 week', + 'probability' => 100, + 'path' => CACHE . 'long' . DS, + ], +] +// ... +``` + +Configuration options can also be provided as a `DSN` string. This is +useful when working with environment variables or `PaaS` providers: + +``` php +Cache::setConfig('short', [ + 'url' => 'memcached://user:password@cache-host/?timeout=3600&prefix=myapp_', +]); +``` + +When using a DSN string you can define any additional parameters/options as +query string arguments. + +You can also configure Cache engines at runtime: + +``` php +// Using a short name +Cache::setConfig('short', [ + 'className' => 'File', + 'duration' => '+1 hours', + 'path' => CACHE, + 'prefix' => 'cake_short_' +]); + +// Using a fully namespaced name. +Cache::setConfig('long', [ + 'className' => 'Cake\Cache\Engine\FileEngine', + 'duration' => '+1 week', + 'probability' => 100, + 'path' => CACHE . 'long' . DS, +]); + +// Using a constructed object. +$object = new FileEngine($config); +Cache::setConfig('other', $object); +``` + +The name of these engine configurations ('short' and 'long') are used as the `$config` +parameter for `Cake\Cache\Cache::write()` and +`Cake\Cache\Cache::read()`. When configuring cache engines you can +refer to the class name using the following syntaxes: + +``` php +// Short name (in App\ or Cake namespaces) +Cache::setConfig('long', ['className' => 'File']); + +// Plugin short name +Cache::setConfig('long', ['className' => 'MyPlugin.SuperCache']); + +// Full namespace +Cache::setConfig('long', ['className' => 'Cake\Cache\Engine\FileEngine']); + +// An object implementing CacheEngineInterface +Cache::setConfig('long', ['className' => $myCache]); +``` + +> [!NOTE] +> When using the FileEngine you might need to use the `mask` option to +> ensure cache files are made with the correct permissions. + +### Engine Options + +Each engine accepts the following options: + +- `duration` Specify how long items in this cache configuration last. + Specified as a `strtotime()` compatible expression. +- `groups` List of groups or 'tags' associated to every key stored in this + config. Useful when you need to delete a subset of data from a cache. +- `prefix` Prepended to all entries. Good for when you need to share + a keyspace with either another cache config or another application. +- `probability` Probability of hitting a cache gc cleanup. Setting to 0 will disable + `Cache::gc()` from ever being called automatically. + +### FileEngine Options + +FileEngine uses the following engine specific options: + +- `isWindows` Automatically populated with whether the host is windows or not +- `lock` Should files be locked before writing to them? +- `mask` The mask used for created files +- `path` Path to where cachefiles should be saved. Defaults to system's temp dir. + + + +### RedisEngine Options + +RedisEngine uses the following engine specific options: + +- `port` The port your Redis server is running on. +- `host` The host your Redis server is running on. +- `database` The database number to use for connection. +- `password` Redis server password. +- `persistent` Should a persistent connection be made to Redis. +- `timeout` Connection timeout for Redis. +- `unix_socket` Path to a unix socket for Redis. +- `tls` Connect to redis over TLS. +- `ssl_key` The ssl private key used for TLS connections. +- `ssl_ca` The ssl certificate authority file for TLS connections. +- `ssl_cert` The ssl certificate used for TLS connections. +- `cluster` Array of cluster server addresses for Redis Cluster support. + +::: info Added in version 5.1.0 +TLS connections were added in 5.1 +::: + +::: info Added in version 5.3.0 +Redis Cluster support was added in 5.3 +::: + +#### Redis Cluster Configuration + +To use Redis Cluster, configure the `cluster` option with an array of server addresses: + +``` php +Cache::setConfig('redis_cluster', [ + 'className' => 'Redis', + 'duration' => '+1 hours', + 'prefix' => 'cake_redis_', + 'cluster' => [ + '127.0.0.1:7000', + '127.0.0.1:7001', + '127.0.0.1:7002', + ] +]); +``` + +When using Redis Cluster, the `host` and `port` options are ignored. The engine will +automatically handle key distribution and failover across the cluster nodes. + +### MemcacheEngine Options + +- `compress` Whether to compress data. +- `username` Login to access the Memcache server. +- `password` Password to access the Memcache server. +- `persistent` The name of the persistent connection. All configurations using + the same persistent value will share a single underlying connection. +- `serialize` The serializer engine used to serialize data. Available engines are php, + igbinary and json. Beside php, the memcached extension must be compiled with the + appropriate serializer support. +- `servers` String or array of memcached servers. If an array MemcacheEngine will use + them as a pool. +- `duration` Be aware that any duration greater than 30 days will be treated as real + Unix time value rather than an offset from current time. +- `options` Additional options for the memcached client. Should be an array of option =\> value. + Use the `\Memcached::OPT_*` constants as keys. + + + +### Configuring Cache Fallbacks + +In the event that an engine is not available, such as the `FileEngine` trying +to write to an unwritable folder or the `RedisEngine` failing to connect to +Redis, the engine will fall back to the noop `NullEngine` and trigger a loggable +error. This prevents the application from throwing an uncaught exception due to +cache failure. + +You can configure Cache configurations to fall back to a specified config using +the `fallback` configuration key: + +``` php +Cache::setConfig('redis', [ + 'className' => 'Redis', + 'duration' => '+1 hours', + 'prefix' => 'cake_redis_', + 'host' => '127.0.0.1', + 'port' => 6379, + 'fallback' => 'default', +]); +``` + +If initializing the `RedisEngine` instance fails, the `redis` cache configuration +would fall back to using the `default` cache configuration. If initializing the +engine for the `default` cache configuration *also* fails, in this scenario the +engine would fall back once again to the `NullEngine` and prevent the application +from throwing an uncaught exception. + +You can turn off cache fallbacks with `false`: + +``` php +Cache::setConfig('redis', [ + 'className' => 'Redis', + 'duration' => '+1 hours', + 'prefix' => 'cake_redis_', + 'host' => '127.0.0.1', + 'port' => 6379, + 'fallback' => false +]); +``` + +When there is no fallback cache failures will be raised as exceptions. + +### Cache::drop() + +`static` Cake\\Cache\\Cache::**drop**($key): bool + +Once a configuration is created you cannot change it. Instead you should drop +the configuration and re-create it using `Cake\Cache\Cache::drop()` and +`Cake\Cache\Cache::setConfig()`. Dropping a cache engine will remove +the config and destroy the adapter if it was constructed. + +## Writing to a Cache + +### Cache::write() + +`static` Cake\\Cache\\Cache::**write**($key, $value, $config = 'default'): bool + +`Cache::write()` will write a \$value to the Cache. You can read or +delete this value later by referring to it by `$key`. You may +specify an optional configuration to store the cache in as well. If +no `$config` is specified, default will be used. `Cache::write()` +can store any type of object and is ideal for storing results of +model finds: + +``` php +$posts = Cache::read('posts'); +if ($posts === null) { + $posts = $someService->getAllPosts(); + Cache::write('posts', $posts); +} +``` + +Using `Cache::write()` and `Cache::read()` to reduce the number +of trips made to the database to fetch posts. + +> [!NOTE] +> If you plan to cache the result of queries made with the CakePHP ORM, +> it is better to use the built-in cache capabilities of the Query object +> as described in the [Caching Query Results](../orm/query-builder#caching-query-results) section + +### Cache::writeMany() + +`static` Cake\\Cache\\Cache::**writeMany**($data, $config = 'default'): bool + +You may find yourself needing to write multiple cache keys at once. While you +can use multiple calls to `write()`, `writeMany()` allows CakePHP to use +more efficient storage APIs where available. For example using `writeMany()` +save multiple network connections when using Memcached: + +``` php +$result = Cache::writeMany([ + 'article-' . $slug => $article, + 'article-' . $slug . '-comments' => $comments +]); + +// $result will contain +['article-first-post' => true, 'article-first-post-comments' => true] +``` + +### Cache::add() + +`static` Cake\\Cache\\Cache::**add**(string $key, mixed $value, string $config = 'default'): bool + +Using `Cache::add()` will let you atomically set a key to a value if the key +does not already exist in the cache. If the key already exists in the cache +backend or the write fails, `add()` will return `false`: + +``` php +// Set a key to act as a lock +$result = Cache::add($lockKey, true); +if (!$result) { + return; +} +// Do an action where there can only be one process active at a time. + +// Remove the lock key. +Cache::delete($lockKey); +``` + +> [!WARNING] +> File based caching does not support atomic writes. + +### Cache::remember() + +`static` Cake\\Cache\\Cache::**remember**($key, $callable, $config = 'default'): mixed + +Cache helps with read-through caching. If the named cache key exists, +it will be returned. If the key does not exist, the callable will be invoked +and the results stored in the cache at the provided key. + +For example, you often want to cache remote service call results. You could use +`remember()` to make this simple: + +``` php +class IssueService +{ + public function allIssues($repo) + { + return Cache::remember($repo . '-issues', function () use ($repo) { + return $this->fetchAll($repo); + }); + } +} +``` + +## Reading From a Cache + +### Cache::read() + +`static` Cake\\Cache\\Cache::**read**($key, $config = 'default'): mixed + +`Cache::read()` is used to read the cached value stored under +`$key` from the `$config`. If `$config` is null the default +config will be used. `Cache::read()` will return the cached value +if it is a valid cache or `null` if the cache has expired or +doesn't exist. Use strict comparison operators `===` or `!==` +to check the success of the `Cache::read()` operation. + +For example: + +``` php +$cloud = Cache::read('cloud'); +if ($cloud !== null) { + return $cloud; +} + +// Generate cloud data +// ... + +// Store data in cache +Cache::write('cloud', $cloud); + +return $cloud; +``` + +Or if you are using another cache configuration called `short`, you can +specify it in `Cache::read()` and `Cache::write()` calls as below: + +``` php +// Read key "cloud", but from short configuration instead of default +$cloud = Cache::read('cloud', 'short'); +if ($cloud === null) { + // Generate cloud data + // ... + + // Store data in cache, using short cache configuration instead of default + Cache::write('cloud', $cloud, 'short'); +} + +return $cloud; +``` + +### Cache::readMany() + +`static` Cake\\Cache\\Cache::**readMany**($keys, $config = 'default'): iterable + +After you've written multiple keys at once, you'll probably want to read them as +well. While you could use multiple calls to `read()`, `readMany()` allows +CakePHP to use more efficient storage APIs where available. For example using +`readMany()` save multiple network connections when using Memcached: + +``` php +$result = Cache::readMany([ + 'article-' . $slug, + 'article-' . $slug . '-comments' +]); +// $result will contain +['article-first-post' => '...', 'article-first-post-comments' => '...'] +``` + +## Deleting From a Cache + +### Cache::delete() + +`static` Cake\\Cache\\Cache::**delete**($key, $config = 'default'): bool + +`Cache::delete()` will allow you to completely remove a cached +object from the store: + +``` php +// Remove a key +Cache::delete('my_key'); +``` + +As of 4.4.0, the `RedisEngine` also provides a `deleteAsync()` method +which uses the `UNLINK` operation to remove cache keys: + +``` php +Cache::pool('redis')->deleteAsync('my_key'); +``` + +### Cache::deleteMany() + +`static` Cake\\Cache\\Cache::**deleteMany**($keys, $config = 'default'): bool + +After you've written multiple keys at once, you may want to delete them. While +you could use multiple calls to `delete()`, `deleteMany()` allows CakePHP to use +more efficient storage APIs where available. For example using `deleteMany()` +save multiple network connections when using Memcached: + +``` php +$result = Cache::deleteMany([ + 'article-' . $slug, + 'article-' . $slug . '-comments' +]); +// $result will contain +['article-first-post' => true, 'article-first-post-comments' => true] +``` + +## Clearing Cached Data + +### Cache::clear() + +`static` Cake\\Cache\\Cache::**clear**($config = 'default'): bool + +Destroy all cached values for a cache configuration. In engines like: Apcu, +Memcached, the cache configuration's prefix is used to remove +cache entries. Make sure that different cache configurations have different +prefixes: + +``` php +// Will clear all keys. +Cache::clear(); +``` + +As of 4.4.0, the `RedisEngine` also provides a `clearBlocking()` method +which uses the `UNLINK` operation to remove cache keys: + +``` php +Cache::pool('redis')->clearBlocking(); +``` + +> [!NOTE] +> Because APCu uses isolated caches for webserver and CLI they +> have to be cleared separately (CLI cannot clear webserver and vice versa). + +## Using Cache to Store Counters + +### Cache::increment() + +`static` Cake\\Cache\\Cache::**increment**($key, $offset = 1, $config = 'default'): int|false + +### Cache::decrement() + +`static` Cake\\Cache\\Cache::**decrement**($key, $offset = 1, $config = 'default'): int|false + +Counters in your application are good candidates for storage in a cache. As an +example, a simple countdown for remaining 'slots' in a contest could be stored +in Cache. The Cache class exposes atomic ways to increment/decrement counter +values. Atomic operations are important for these values as it +reduces the risk of contention, and ability for two users to simultaneously +lower the value by one, resulting in an incorrect value. + +After setting an integer value you can manipulate it using `increment()` and +`decrement()`: + +``` php +Cache::write('initial_count', 10); + +// Later on +Cache::decrement('initial_count'); + +// Or +Cache::increment('initial_count'); +``` + +> [!NOTE] +> Incrementing and decrementing do not work with FileEngine. You should use +> APCu, Redis or Memcached instead. + +## Using Cache to Store Common Query Results + +You can greatly improve the performance of your application by putting results +that infrequently change, or that are subject to heavy reads into the cache. +A perfect example of this are the results from +`Cake\ORM\Table::find()`. The Query object allows you to cache +results using the `cache()` method. See the [Caching Query Results](../orm/query-builder#caching-query-results) section +for more information. + + + +## Using Groups + +Sometimes you will want to mark multiple cache entries to belong to certain +group or namespace. This is a common requirement for mass-invalidating keys +whenever some information changes that is shared among all entries in the same +group. This is possible by declaring the groups in cache configuration: + +``` php +Cache::setConfig('site_home', [ + 'className' => 'Redis', + 'duration' => '+999 days', + 'groups' => ['comment', 'article'], +]); +``` + +### Cache::clearGroup() + +`method` Cake\\Cache\\Cache::**clearGroup**($group, $config = 'default'): bool + +Let's say you want to store the HTML generated for your homepage in cache, but +would also want to automatically invalidate this cache every time a comment or +post is added to your database. By adding the groups `comment` and `article`, +we have effectively tagged any key stored into this cache configuration with +both group names. + +For instance, whenever a new post is added, we could tell the Cache engine to +remove all entries associated to the `article` group: + +``` php +// src/Model/Table/ArticlesTable.php +public function afterSave($event, $entity, $options = []) +{ + if ($entity->isNew()) { + Cache::clearGroup('article', 'site_home'); + } +} +``` + +### Cache::groupConfigs() + +`static` Cake\\Cache\\Cache::**groupConfigs**($group = null): array + +`groupConfigs()` can be used to retrieve mapping between group and +configurations, i.e.: having the same group: + +``` php +// src/Model/Table/ArticlesTable.php + +/** + * A variation of previous example that clears all Cache configurations + * having the same group + */ +public function afterSave($event, $entity, $options = []) +{ + if ($entity->isNew()) { + $configs = Cache::groupConfigs('article'); + foreach ($configs['article'] as $config) { + Cache::clearGroup('article', $config); + } + } +} +``` + +Groups are shared across all cache configs using the same engine and same +prefix. If you are using groups and want to take advantage of group deletion, +choose a common prefix for all your configs. + +## Globally Enable or Disable Cache + +### Cache::disable() + +`static` Cake\\Cache\\Cache::**disable**(): void + +You may need to disable all Cache read & writes when trying to figure out cache +expiration related issues. You can do this using `enable()` and +`disable()`: + +``` php +// Disable all cache reads, and cache writes. +Cache::disable(); +``` + +Once disabled, all reads and writes will return `null`. + +### Cache::enable() + +`static` Cake\\Cache\\Cache::**enable**(): void + +Once disabled, you can use `enable()` to re-enable caching: + +``` php +// Re-enable all cache reads, and cache writes. +Cache::enable(); +``` + +### Cache::enabled() + +`static` Cake\\Cache\\Cache::**enabled**(): bool + +If you need to check on the state of Cache, you can use `enabled()`. + +## Creating a Cache Engine + +You can provide custom `Cache` engines in `App\Cache\Engine` as well +as in plugins using `$plugin\Cache\Engine`. Cache engines must be in a cache +directory. If you had a cache engine named `MyCustomCacheEngine` +it would be placed in either **src/Cache/Engine/MyCustomCacheEngine.php**. +Or in **plugins/MyPlugin/src/Cache/Engine/MyCustomCacheEngine.php** as +part of a plugin. Cache configs from plugins need to use the plugin +dot syntax: + +``` php +Cache::setConfig('custom', [ + 'className' => 'MyPlugin.MyCustomCache', + // ... +]); +``` + +Custom Cache engines must extend `Cake\Cache\CacheEngine` which +defines a number of abstract methods as well as provides a few initialization +methods. + +The required API for a CacheEngine is + +`class` Cake\\Cache\\**CacheEngine** + +`method` Cake\\Cache\\CacheEngine::**write**($key, $value) + +`method` Cake\\Cache\\CacheEngine::**read**($key) + +`method` Cake\\Cache\\CacheEngine::**delete**($key): bool + +`method` Cake\\Cache\\CacheEngine::**clear**($check): bool + +`method` Cake\\Cache\\CacheEngine::**clearGroup**($group): bool + +`method` Cake\\Cache\\CacheEngine::**decrement**($key, $offset = 1): int|false + +`method` Cake\\Cache\\CacheEngine::**increment**($key, $offset = 1): int|false + + + +## Cache Events + +::: info Added in version 5.3.0 +::: + +You can add event listeners to the following events: + +- `\Cake\Cache\Event\CacheBeforeGetEvent` +- `\Cake\Cache\Event\CacheAfterGetEvent` +- `\Cake\Cache\Event\CacheBeforeSetEvent` +- `\Cake\Cache\Event\CacheAfterSetEvent` +- `\Cake\Cache\Event\CacheBeforeAddEvent` +- `\Cake\Cache\Event\CacheAfterAddEvent` +- `\Cake\Cache\Event\CacheBeforeDecrementEvent` +- `\Cake\Cache\Event\CacheAfterDecrementEvent` +- `\Cake\Cache\Event\CacheBeforeDeleteEvent` +- `\Cake\Cache\Event\CacheAfterDeleteEvent` +- `\Cake\Cache\Event\CacheBeforeIncrementEvent` +- `\Cake\Cache\Event\CacheAfterIncrementEvent` +- `\Cake\Cache\Event\CacheClearedEvent` +- `\Cake\Cache\Event\CacheGroupClearEvent` + +an example listener in your `src/Application.php` or plugin class would be: + +``` php +public function events(EventManagerInterface $eventManager): EventManagerInterface +{ + $eventManager->on(CacheAfterGetEvent::NAME, function (CacheAfterGetEvent $event): void { + $key = $event->getKey(); + $value = $event->getValue(); + $success = $event->getResult(); + }); + + return $eventManager; +} +``` + +Different events have different context, so please check the methods inside the custom event class +if you are looking for certain data. diff --git a/docs/en/core-libraries/collections.md b/docs/en/core-libraries/collections.md new file mode 100644 index 0000000000..646c1dfdbd --- /dev/null +++ b/docs/en/core-libraries/collections.md @@ -0,0 +1,1428 @@ +# Collections + +`class` Cake\\Collection\\**Collection** + +The collection classes provide a set of tools to manipulate arrays or +`Traversable` objects. If you have ever used underscore.js, +you have an idea of what you can expect from the collection classes. + +Collection instances are immutable; modifying a collection will instead generate +a new collection. This makes working with collection objects more predictable as +operations are side-effect free. + +## Quick Example + +Collections can be created using an array or `Traversable` object. You'll also +interact with collections every time you interact with the ORM in CakePHP. +A simple use of a Collection would be: + +``` php +use Cake\Collection\Collection; + +$items = ['a' => 1, 'b' => 2, 'c' => 3]; +$collection = new Collection($items); + +// Create a new collection containing elements +// with a value greater than one. +$overOne = $collection->filter(function ($value, $key, $iterator) { + return $value > 1; +}); +``` + +You can also use the `collection()` helper function instead of `new Collection()`: + +``` php +$items = ['a' => 1, 'b' => 2, 'c' => 3]; + +// These both make a Collection instance. +$collectionA = new Collection($items); +$collectionB = collection($items); +``` + +The benefit of the helper method is that it is easier to chain off of than +`(new Collection($items))`. + +The `Cake\Collection\CollectionTrait` allows you to integrate +collection-like features into any `Traversable` object you have in your +application as well. + +## List of Methods + +| | | | +|-----------------|--------------|--------------| +| `any` | `append` | `appendItem` | +| `avg` | `buffered` | `chunk` | +| `chunkWithKeys` | `combine` | `compile` | +| `contains` | `countBy` | `each` | +| `every` | `extract` | `filter` | +| `first` | `firstMatch` | `groupBy` | +| `indexBy` | `insert` | `isEmpty` | +| `last` | `listNested` | `map` | +| `match` | `max` | `median` | +| `min` | `nest` | `prepend` | +| `prependItem` | `reduce` | `reject` | +| `sample` | `shuffle` | `skip` | +| `some` | `sortBy` | `stopWhen` | +| `sumOf` | `take` | `through` | +| `transpose` | `unfold` | `zip` | + +## Iterating + +### each() + +`method` Cake\\Collection\\Collection::**each**($callback) + +Collections can be iterated and/or transformed into new collections with the +`each()` and `map()` methods. The `each()` method will not create a new +collection, but will allow you to modify any objects within the collection: + +``` php +$collection = new Collection($items); +$collection = $collection->each(function ($value, $key) { + echo "Element $key: $value"; +}); +``` + +The return of `each()` will be the collection object. Each will iterate the +collection immediately applying the callback to each value in the collection. + +### map() + +`method` Cake\\Collection\\Collection::**map**($callback): CollectionInterface + +The `map()` method will create a new collection based on the output of the +callback being applied to each object in the original collection: + +``` php +$items = ['a' => 1, 'b' => 2, 'c' => 3]; +$collection = new Collection($items); + +$new = $collection->map(function ($value, $key) { + return $value * 2; +}); + +// $result contains [2, 4, 6]; +$result = $new->toList(); + +// $result contains ['a' => 2, 'b' => 4, 'c' => 6]; +$result = $new->toArray(); +``` + +The `map()` method will create a new iterator which lazily creates +the resulting items when iterated. + +### extract() + +`method` Cake\\Collection\\Collection::**extract**($path): CollectionInterface + +One of the most common uses for a `map()` function is to extract a single +column from a collection. If you are looking to build a list of elements +containing the values for a particular property, you can use the `extract()` +method: + +``` php +$collection = new Collection($people); +$names = $collection->extract('name'); + +// $result contains ['mark', 'jose', 'barbara']; +$result = $names->toList(); +``` + +As with many other functions in the collection class, you are allowed to specify +a dot-separated path for extracting columns. This example will return +a collection containing the author names from a list of articles: + +``` php +$collection = new Collection($articles); +$names = $collection->extract('author.name'); + +// $result contains ['Maria', 'Stacy', 'Larry']; +$result = $names->toList(); +``` + +Finally, if the property you are looking after cannot be expressed as a path, +you can use a callback function to return it: + +``` php +$collection = new Collection($articles); +$names = $collection->extract(function ($article) { + return $article->author->name . ', ' . $article->author->last_name; +}); +``` + +Often, the properties you need to extract a common key present in multiple +arrays or objects that are deeply nested inside other structures. For those +cases you can use the `{*}` matcher in the path key. This matcher is often +helpful when matching HasMany and BelongsToMany association data: + +``` php +$data = [ + [ + 'name' => 'James', + 'phone_numbers' => [ + ['number' => 'number-1'], + ['number' => 'number-2'], + ['number' => 'number-3'], + ], + ], + [ + 'name' => 'James', + 'phone_numbers' => [ + ['number' => 'number-4'], + ['number' => 'number-5'], + ], + ], +]; + +$numbers = (new Collection($data))->extract('phone_numbers.{*}.number'); +$result = $numbers->toList(); +// $result contains ['number-1', 'number-2', 'number-3', 'number-4', 'number-5'] +``` + +This last example uses `toList()` unlike other examples, which is important +when we're getting results with possibly duplicate keys. By using `toList()` +we'll be guaranteed to get all values even if there are duplicate keys. + +Unlike `Cake\Utility\Hash::extract()` this method only supports the +`{*}` wildcard. All other wildcard and attributes matchers are not supported. + +### combine() + +`method` Cake\\Collection\\Collection::**combine**($keyPath, $valuePath, $groupPath = null): CollectionInterface + +Collections allow you to create a new collection made from keys and values in +an existing collection. Both the key and value paths can be specified with +dot notation paths: + +``` php +$items = [ + ['id' => 1, 'name' => 'foo', 'parent' => 'a'], + ['id' => 2, 'name' => 'bar', 'parent' => 'b'], + ['id' => 3, 'name' => 'baz', 'parent' => 'a'], +]; +$combined = (new Collection($items))->combine('id', 'name'); +$result = $combined->toArray(); + +// $result contains +[ + 1 => 'foo', + 2 => 'bar', + 3 => 'baz', +]; +``` + +You can also optionally use a `groupPath` to group results based on a path: + +``` php +$combined = (new Collection($items))->combine('id', 'name', 'parent'); +$result = $combined->toArray(); + +// $result contains +[ + 'a' => [1 => 'foo', 3 => 'baz'], + 'b' => [2 => 'bar'] +]; +``` + +Finally you can use *closures* to build keys/values/groups paths dynamically, +for example when working with entities and dates (converted to `I18n\DateTime` +instances by the ORM) you may want to group results by date: + +``` php +$combined = (new Collection($entities))->combine( + 'id', + function ($entity) { return $entity; }, + function ($entity) { return $entity->date->toDateString(); } +); + $result = $combined->toArray(); + +// $result contains +[ + 'date string like 2015-05-01' => ['entity1->id' => entity1, 'entity2->id' => entity2, ..., 'entityN->id' => entityN] + 'date string like 2015-06-01' => ['entity1->id' => entity1, 'entity2->id' => entity2, ..., 'entityN->id' => entityN] +] +``` + +### stopWhen() + +`method` Cake\\Collection\\Collection::**stopWhen**(callable $c): CollectionInterface + +You can stop the iteration at any point using the `stopWhen()` method. Calling +it in a collection will create a new one that will stop yielding results if the +passed callable returns true for one of the elements: + +``` php +$items = [10, 20, 50, 1, 2]; +$collection = new Collection($items); + +$new = $collection->stopWhen(function ($value, $key) { + // Stop on the first value bigger than 30 + return $value > 30; +}); + +// $result contains [10, 20]; +$result = $new->toList(); +``` + +### unfold() + +`method` Cake\\Collection\\Collection::**unfold**(callable $callback): CollectionInterface + +Sometimes the internal items of a collection will contain arrays or iterators +with more items. If you wish to flatten the internal structure to iterate once +over all elements you can use the `unfold()` method. It will create a new +collection that will yield every single element nested in the collection: + +``` php +$items = [[1, 2, 3], [4, 5]]; +$collection = new Collection($items); +$new = $collection->unfold(); + +// $result contains [1, 2, 3, 4, 5]; +$result = $new->toList(); +``` + +When passing a callable to `unfold()` you can control what elements will be +unfolded from each item in the original collection. This is useful for returning +data from paginated services: + +``` php +$pages = [1, 2, 3, 4]; +$collection = new Collection($pages); +$items = $collection->unfold(function ($page, $key) { + // An imaginary web service that returns a page of results + return MyService::fetchPage($page)->toList(); +}); + +$allPagesItems = $items->toList(); +``` + +You can use the `yield` keyword inside `unfold()` to return as +many elements for each item in the collection as you may need: + +``` php +$oddNumbers = [1, 3, 5, 7]; +$collection = new Collection($oddNumbers); +$new = $collection->unfold(function ($oddNumber) { + yield $oddNumber; + yield $oddNumber + 1; +}); + +// $result contains [1, 2, 3, 4, 5, 6, 7, 8]; +$result = $new->toList(); +``` + +### chunk() + +`method` Cake\\Collection\\Collection::**chunk**($chunkSize): CollectionInterface + +When dealing with large amounts of items in a collection, it may make sense to +process the elements in batches instead of one by one. For splitting +a collection into multiple arrays of a certain size, you can use the `chunk()` +function: + +``` php +$items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; +$collection = new Collection($items); +$chunked = $collection->chunk(2); +$chunked->toList(); // [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]] +``` + +The `chunk` function is particularly useful when doing batch processing, for +example with a database result: + +``` php +$collection = new Collection($articles); +$collection->map(function ($article) { + // Change a property in the article + $article->property = 'changed'; + }) + ->chunk(20) + ->each(function ($batch) { + myBulkSave($batch); // This function will be called for each batch + }); +``` + +### chunkWithKeys() + +`method` Cake\\Collection\\Collection::**chunkWithKeys**($chunkSize): CollectionInterface + +Much like `chunk()`, `chunkWithKeys()` allows you to slice up +a collection into smaller batches but with keys preserved. This is useful when +chunking associative arrays: + +``` php +$collection = new Collection([ + 'a' => 1, + 'b' => 2, + 'c' => 3, + 'd' => [4, 5] +]); +$chunked = $collection->chunkWithKeys(2); +$result = $chunked->toList(); + +// $result contains +[ + ['a' => 1, 'b' => 2], + ['c' => 3, 'd' => [4, 5]] +] +``` + +## Filtering + +### filter() + +`method` Cake\\Collection\\Collection::**filter**($callback): CollectionInterface + +Collections allow you to filter and create new collections based on +the result of callback functions. You can use `filter()` to create a new +collection of elements matching a criteria callback: + +``` php +$collection = new Collection($people); +$ladies = $collection->filter(function ($person, $key) { + return $person->gender === 'female'; +}); +$guys = $collection->filter(function ($person, $key) { + return $person->gender === 'male'; +}); +``` + +### reject() + +`method` Cake\\Collection\\Collection::**reject**(callable $c): CollectionInterface + +The inverse of `filter()` is `reject()`. This method does a negative filter, +removing elements that match the filter function: + +``` php +$collection = new Collection($people); +$ladies = $collection->reject(function ($person, $key) { + return $person->gender === 'male'; +}); +``` + +### every() + +`method` Cake\\Collection\\Collection::**every**($callback): bool + +You can do truth tests with filter functions. To see if every element in +a collection matches a test you can use `every()`: + +``` php +$collection = new Collection($people); +$allYoungPeople = $collection->every(function ($person) { + return $person->age < 21; +}); +``` + +### any() + +`method` Cake\\Collection\\Collection::**any**($callback): bool + +### some() + +`method` Cake\\Collection\\Collection::**some**($callback): bool + +You can see if the collection contains at least one element matching a filter +function using the `any()` method: + +``` php +$collection = new Collection($people); +$hasYoungPeople = $collection->any(function ($person) { + return $person->age < 21; +}); +``` + +> [!NOTE] +> The `some()` method is an alias of `any()`. + +### match() + +`method` Cake\\Collection\\Collection::**match**($conditions): CollectionInterface + +If you need to extract a new collection containing only the elements that +contain a given set of properties, you should use the `match()` method: + +``` php +$collection = new Collection($comments); +$commentsFromMark = $collection->match(['user.name' => 'Mark']); +``` + +### firstMatch() + +`method` Cake\\Collection\\Collection::**firstMatch**($conditions): mixed + +The property name can be a dot-separated path. You can traverse into nested +entities and match the values they contain. When you only need the first +matching element from a collection, you can use `firstMatch()`: + +``` php +$collection = new Collection($comments); +$comment = $collection->firstMatch([ + 'user.name' => 'Mark', + 'active' => true +]); +``` + +As you can see from the above, both `match()` and `firstMatch()` allow you +to provide multiple conditions to match on. In addition, the conditions can be +for different paths, allowing you to express complex conditions to match +against. + +## Aggregation + +### reduce() + +`method` Cake\\Collection\\Collection::**reduce**($callback, $initial): mixed + +The counterpart of a `map()` operation is usually a `reduce`. This +function will help you build a single result out of all the elements in a +collection: + +``` php +$totalPrice = $collection->reduce(function ($accumulated, $orderLine) { + return $accumulated + $orderLine->price; +}, 0); +``` + +In the above example, `$totalPrice` will be the sum of all single prices +contained in the collection. Note the second argument for the `reduce()` +function takes the initial value for the reduce operation you are +performing: + +``` php +$allTags = $collection->reduce(function ($accumulated, $article) { + return array_merge($accumulated, $article->tags); +}, []); +``` + +### min() + +`method` Cake\\Collection\\Collection::**min**(string|callable $callback, $type = SORT_NUMERIC): mixed + +To extract the minimum value for a collection based on a property, just use the +`min()` function. This will return the full element from the collection and +not just the smallest value found: + +``` php +$collection = new Collection($people); +$youngest = $collection->min('age'); + +echo $youngest->name; +``` + +You are also able to express the property to compare by providing a path or a +callback function: + +``` php +$collection = new Collection($people); +$personYoungestChild = $collection->min(function ($person) { + return $person->child->age; +}); + +$personWithYoungestDad = $collection->min('dad.age'); +``` + +### max() + +`method` Cake\\Collection\\Collection::**max**(string|callable $callback, $type = SORT_NUMERIC): mixed + +The same can be applied to the `max()` function, which will return a single +element from the collection having the highest property value: + +``` php +$collection = new Collection($people); +$oldest = $collection->max('age'); + +$personOldestChild = $collection->max(function ($person) { + return $person->child->age; +}); + +$personWithOldestDad = $collection->max('dad.age'); +``` + +### sumOf() + +`method` Cake\\Collection\\Collection::**sumOf**($path = null): float|int + +Finally, the `sumOf()` method will return the sum of a property of all +elements: + +``` php +$collection = new Collection($people); +$sumOfAges = $collection->sumOf('age'); + +$sumOfChildrenAges = $collection->sumOf(function ($person) { + return $person->child->age; +}); + +$sumOfDadAges = $collection->sumOf('dad.age'); +``` + +### avg() + +`method` Cake\\Collection\\Collection::**avg**($path = null): float|int|null + +Calculate the average value of the elements in the collection. Optionally +provide a matcher path, or function to extract values to generate the average +for: + +``` php +$items = [ + ['invoice' => ['total' => 100]], + ['invoice' => ['total' => 200]], +]; + +// $average contains 150 +$average = (new Collection($items))->avg('invoice.total'); +``` + +### median() + +`method` Cake\\Collection\\Collection::**median**($path = null): float|int|null + +Calculate the median value of a set of elements. Optionally provide a matcher +path, or function to extract values to generate the median for: + +``` php +$items = [ + ['invoice' => ['total' => 400]], + ['invoice' => ['total' => 500]], + ['invoice' => ['total' => 100]], + ['invoice' => ['total' => 333]], + ['invoice' => ['total' => 200]], +]; + +// $median contains 333 +$median = (new Collection($items))->median('invoice.total'); +``` + +### Grouping and Counting + +### groupBy() + +`method` Cake\\Collection\\Collection::**groupBy**($callback): CollectionInterface + +Collection values can be grouped by different keys in a new collection when they +share the same value for a property: + +``` php +$students = [ + ['name' => 'Mark', 'grade' => 9], + ['name' => 'Andrew', 'grade' => 10], + ['name' => 'Stacy', 'grade' => 10], + ['name' => 'Barbara', 'grade' => 9] +]; +$collection = new Collection($students); +$studentsByGrade = $collection->groupBy('grade'); +$result = $studentsByGrade->toArray(); + +// $result contains +[ + 10 => [ + ['name' => 'Andrew', 'grade' => 10], + ['name' => 'Stacy', 'grade' => 10] + ], + 9 => [ + ['name' => 'Mark', 'grade' => 9], + ['name' => 'Barbara', 'grade' => 9] + ] +] +``` + +As usual, it is possible to provide either a dot-separated path for nested +properties or your own callback function to generate the groups dynamically: + +``` php +$commentsByUserId = $comments->groupBy('user.id'); + +$classResults = $students->groupBy(function ($student) { + return $student->grade > 6 ? 'approved' : 'denied'; +}); +``` + +### countBy() + +`method` Cake\\Collection\\Collection::**countBy**($callback): CollectionInterface + +If you only wish to know the number of occurrences per group, you can do so by +using the `countBy()` method. It takes the same arguments as `groupBy` so it +should be already familiar to you: + +``` php +$classResults = $students->countBy(function ($student) { + return $student->grade > 6 ? 'approved' : 'denied'; +}); + +// Result could look like this when converted to array: +['approved' => 70, 'denied' => 20] +``` + +### indexBy() + +`method` Cake\\Collection\\Collection::**indexBy**($callback): CollectionInterface + +There will be certain cases where you know an element is unique for the property +you want to group by. If you wish a single result per group, you can use the +function `indexBy()`: + +``` php +$usersById = $users->indexBy('id'); + +// When converted to array result could look like +[ + 1 => 'markstory', + 3 => 'jose_zap', + 4 => 'jrbasso' +] +``` + +As with the `groupBy()` function you can also use a property path or +a callback: + +``` php +$articlesByAuthorId = $articles->indexBy('author.id'); + +$filesByHash = $files->indexBy(function ($file) { + return md5($file); +}); +``` + +### zip() + +`method` Cake\\Collection\\Collection::**zip**($items): CollectionInterface + +The elements of different collections can be grouped together using the +`zip()` method. It will return a new collection containing an array grouping +the elements from each collection that are placed at the same position: + +``` php +$odds = new Collection([1, 3, 5]); +$pairs = new Collection([2, 4, 6]); +$combined = $odds->zip($pairs)->toList(); // [[1, 2], [3, 4], [5, 6]] +``` + +You can also zip multiple collections at once: + +``` php +$years = new Collection([2013, 2014, 2015, 2016]); +$salaries = [1000, 1500, 2000, 2300]; +$increments = [0, 500, 500, 300]; + +$rows = $years->zip($salaries, $increments); +$result = $rows->toList(); + +// $result contains +[ + [2013, 1000, 0], + [2014, 1500, 500], + [2015, 2000, 500], + [2016, 2300, 300] +] +``` + +As you can already see, the `zip()` method is very useful for transposing +multidimensional arrays: + +``` php +$data = [ + 2014 => ['jan' => 100, 'feb' => 200], + 2015 => ['jan' => 300, 'feb' => 500], + 2016 => ['jan' => 400, 'feb' => 600], +]; + +// Getting jan and feb data together + +$firstYear = new Collection(array_shift($data)); +$result = $firstYear->zip($data[0], $data[1])->toList(); + +// Or $firstYear->zip(...$data) in PHP >= 5.6 + +// $result contains +[ + [100, 300, 400], + [200, 500, 600] +] +``` + +## Sorting + +### sortBy() + +`method` Cake\\Collection\\Collection::**sortBy**($callback, $order = SORT_DESC, $sort = SORT_NUMERIC): CollectionInterface + +Collection values can be sorted in ascending or descending order based on +a column or custom function. To create a new sorted collection out of the values +of another one, you can use `sortBy`: + +``` php +$collection = new Collection($people); +$sorted = $collection->sortBy('age'); +``` + +As seen above, you can sort by passing the name of a column or property that +is present in the collection values. You are also able to specify a property +path instead using the dot notation. The next example will sort articles by +their author's name: + +``` php +$collection = new Collection($articles); +$sorted = $collection->sortBy('author.name'); +``` + +The `sortBy()` method is flexible enough to let you specify an extractor +function that will let you dynamically select the value to use for comparing two +different values in the collection: + +``` php +$collection = new Collection($articles); +$sorted = $collection->sortBy(function ($article) { + return $article->author->name . '-' . $article->title; +}); +``` + +In order to specify in which direction the collection should be sorted, you need +to provide either `SORT_ASC` or `SORT_DESC` as the second parameter for +sorting in ascending or descending direction respectively. By default, +collections are sorted in descending direction: + +``` php +$collection = new Collection($people); +$sorted = $collection->sortBy('age', SORT_ASC); +``` + +Sometimes you will need to specify which type of data you are trying to compare +so that you get consistent results. For this purpose, you should supply a third +argument in the `sortBy()` function with one of the following constants: + +- **SORT_NUMERIC**: For comparing numbers +- **SORT_STRING**: For comparing string values +- **SORT_NATURAL**: For sorting string containing numbers and you'd like those + numbers to be order in a natural way. For example: showing "10" after "2". +- **SORT_LOCALE_STRING**: For comparing strings based on the current locale. + +By default, `SORT_NUMERIC` is used: + +``` php +$collection = new Collection($articles); +$sorted = $collection->sortBy('title', SORT_ASC, SORT_NATURAL); +``` + +> [!WARNING] +> It is often expensive to iterate sorted collections more than once. If you +> plan to do so, consider converting the collection to an array or simply use +> the `compile()` method on it. + +## Working with Tree Data + +### nest() + +`method` Cake\\Collection\\Collection::**nest**($idPath, $parentPath, $nestingKey = 'children'): CollectionInterface + +Not all data is meant to be represented in a linear way. Collections make it +easier to construct and flatten hierarchical or nested structures. Creating +a nested structure where children are grouped by a parent identifier property +can be done with the `nest()` method. + +Two parameters are required for this function. The first one is the property +representing the item identifier. The second parameter is the name of the +property representing the identifier for the parent item: + +``` php +$collection = new Collection([ + ['id' => 1, 'parent_id' => null, 'name' => 'Birds'], + ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds'], + ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'], + ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'], + ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish'], + ['id' => 6, 'parent_id' => null, 'name' => 'Fish'], +]); +$nested = $collection->nest('id', 'parent_id'); +$result = $nested->toList(); + +// $result contains +[ + [ + 'id' => 1, + 'parent_id' => null, + 'name' => 'Birds', + 'children' => [ + ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds', 'children' => []], + ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle', 'children' => []], + ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull', 'children' => []], + ], + ], + [ + 'id' => 6, + 'parent_id' => null, + 'name' => 'Fish', + 'children' => [ + ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish', 'children' => []], + ], + ], +]; +``` + +Children elements are nested inside the `children` property inside each of the +items in the collection. This type of data representation is helpful for +rendering menus or traversing elements up to certain level in the tree. + +### listNested() + +`method` Cake\\Collection\\Collection::**listNested**($order = 'desc', $nestingKey = 'children'): CollectionInterface + +The inverse of `nest()` is `listNested()`. This method allows you to flatten +a tree structure back into a linear structure. It takes two parameters; the +first one is the traversing mode (asc, desc or leaves), and the second one is +the name of the property containing the children for each element in the +collection. + +Taking the input the nested collection built in the previous example, we can +flatten it: + +``` php +$result = $nested->listNested()->toList(); + +// $result contains +[ + ['id' => 1, 'parent_id' => null, 'name' => 'Birds', 'children' => [...]], + ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds'], + ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'], + ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'], + ['id' => 6, 'parent_id' => null, 'name' => 'Fish', 'children' => [...]], + ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish'] +] +``` + +By default, the tree is traversed from the root to the leaves. You can also +instruct it to only return the leaf elements in the tree: + +``` php +$result = $nested->listNested('leaves')->toList(); + +// $result contains +[ + ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds', 'children' => [], ], + ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle', 'children' => [], ], + ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull', 'children' => [], ], + ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish', 'children' => [], ], +] +``` + +Once you have converted a tree into a nested list, you can use the `printer()` +method to configure how the list output should be formatted: + +``` php +$result = $nested->listNested()->printer('name', 'id', '--')->toArray(); + +// $result contains +[ + 1 => 'Birds', + 2 => '--Land Birds', + 3 => '--Eagle', + 4 => '--Seagull', + 6 => 'Fish', + 5 => '--Clown Fish', +] +``` + +The `printer()` method also lets you use a callback to generate the keys and +or values: + +``` php +$nested->listNested()->printer( + function ($el) { + return $el->name; + }, + function ($el) { + return $el->id; + } +); +``` + +## Other Methods + +### isEmpty() + +`method` Cake\\Collection\\Collection::**isEmpty**(): bool + +Allows you to see if a collection contains any elements: + +``` php +$collection = new Collection([]); +// Returns true +$collection->isEmpty(); + +$collection = new Collection([1]); +// Returns false +$collection->isEmpty(); +``` + +### contains() + +`method` Cake\\Collection\\Collection::**contains**($value): bool + +Collections allow you to quickly check if they contain one particular +value: by using the `contains()` method: + +``` php +$items = ['a' => 1, 'b' => 2, 'c' => 3]; +$collection = new Collection($items); +$hasThree = $collection->contains(3); +``` + +Comparisons are performed using the `===` operator. If you wish to do looser +comparison types you can use the `some()` method. + +### shuffle() + +`method` Cake\\Collection\\Collection::**shuffle**(): CollectionInterface + +Sometimes you may wish to show a collection of values in a random order. In +order to create a new collection that will return each value in a randomized +position, use the `shuffle`: + +``` php +$collection = new Collection(['a' => 1, 'b' => 2, 'c' => 3]); + +// This could return [2, 3, 1] +$collection->shuffle()->toList(); +``` + +### transpose() + +`method` Cake\\Collection\\Collection::**transpose**(): CollectionInterface + +When you transpose a collection, you get a new collection containing a row made +of the each of the original columns: + +``` php +$items = [ + ['Products', '2012', '2013', '2014'], + ['Product A', '200', '100', '50'], + ['Product B', '300', '200', '100'], + ['Product C', '400', '300', '200'], +]; +$transpose = (new Collection($items))->transpose(); +$result = $transpose->toList(); + +// $result contains +[ + ['Products', 'Product A', 'Product B', 'Product C'], + ['2012', '200', '300', '400'], + ['2013', '100', '200', '300'], + ['2014', '50', '100', '200'], +] +``` + +### Withdrawing Elements + +### sample() + +`method` Cake\\Collection\\Collection::**sample**($length = 10): CollectionInterface + +Shuffling a collection is often useful when doing quick statistical analysis. +Another common operation when doing this sort of task is withdrawing a few +random values out of a collection so that more tests can be performed on those. +For example, if you wanted to select 5 random users to which you'd like to apply +some A/B tests to, you can use the `sample()` function: + +``` php +$collection = new Collection($people); + +// Withdraw maximum 20 random users from this collection +$testSubjects = $collection->sample(20); +``` + +`sample()` will take at most the number of values you specify in the first +argument. If there are not enough elements in the collection to satisfy the +sample, the full collection in a random order is returned. + +### take() + +`method` Cake\\Collection\\Collection::**take**($length, $offset): CollectionInterface + +Whenever you want to take a slice of a collection use the `take()` function, +it will create a new collection with at most the number of values you specify in +the first argument, starting from the position passed in the second argument: + +``` php +$topFive = $collection->sortBy('age')->take(5); + +// Take 5 people from the collection starting from position 4 +$nextTopFive = $collection->sortBy('age')->take(5, 4); +``` + +Positions are zero-based, therefore the first position number is `0`. + +### skip() + +`method` Cake\\Collection\\Collection::**skip**($length): CollectionInterface + +While the second argument of `take()` can help you skip some elements before +getting them from the collection, you can also use `skip()` for the same +purpose as a way to take the rest of the elements after a certain position: + +``` php +$collection = new Collection([1, 2, 3, 4]); +$allExceptFirstTwo = $collection->skip(2)->toList(); // [3, 4] +``` + +### first() + +`method` Cake\\Collection\\Collection::**first**(): mixed + +One of the most common uses of `take()` is getting the first element in the +collection. A shortcut method for achieving the same goal is using the +`first()` method: + +``` php +$collection = new Collection([5, 4, 3, 2]); +$collection->first(); // Returns 5 +``` + +### last() + +`method` Cake\\Collection\\Collection::**last**(): mixed + +Similarly, you can get the last element of a collection using the `last()` +method: + +``` php +$collection = new Collection([5, 4, 3, 2]); +$collection->last(); // Returns 2 +``` + +### Expanding Collections + +### append() + +`method` Cake\\Collection\\Collection::**append**(array|Traversable $items): CollectionInterface + +You can compose multiple collections into a single one. This enables you to +gather data from various sources, concatenate it, and apply other collection +functions to it very smoothly. The `append()` method will return a new +collection containing the values from both sources: + +``` php +$cakephpTweets = new Collection($tweets); +$myTimeline = $cakephpTweets->append($phpTweets); + +// Tweets containing `cakefest` from both sources +$myTimeline->filter(function ($tweet) { + return strpos($tweet, 'cakefest'); +}); +``` + +### appendItem() + +`method` Cake\\Collection\\Collection::**appendItem**($value, $key): CollectionInterface + +Allows you to append an item with an optional key to the collection. If you +specify a key that already exists in the collection, the value will not be +overwritten: + +``` php +$cakephpTweets = new Collection($tweets); +$myTimeline = $cakephpTweets->appendItem($newTweet, 99); +``` + +### prepend() + +`method` Cake\\Collection\\Collection::**prepend**($items): CollectionInterface + +The `prepend()` method will return a new collection containing the values from +both sources: + +``` php +$cakephpTweets = new Collection($tweets); +$myTimeline = $cakephpTweets->prepend($phpTweets); +``` + +### prependItem() + +`method` Cake\\Collection\\Collection::**prependItem**($value, $key): CollectionInterface + +Allows you to prepend an item with an optional key to the collection. If you +specify a key that already exists in the collection, the value will not be +overwritten: + +``` php +$cakephpTweets = new Collection($tweets); +$myTimeline = $cakephpTweets->prependItem($newTweet, 99); +``` + +> [!WARNING] +> When appending from different sources, you can expect some keys from both +> collections to be the same. For example, when appending two simple arrays. +> This can present a problem when converting a collection to an array using +> `toArray()`. If you do not want values from one collection to override +> others in the previous one based on their key, make sure that you call +> `toList()` in order to drop the keys and preserve all values. + +### Modifiying Elements + +### insert() + +`method` Cake\\Collection\\Collection::**insert**($path, $items): CollectionInterface + +At times, you may have two separate sets of data that you would like to insert +the elements of one set into each of the elements of the other set. This is +a very common case when you fetch data from a data source that does not support +data-merging or joins natively. + +Collections offer an `insert()` method that will allow you to insert each of +the elements in one collection into a property inside each of the elements of +another collection: + +``` php +$users = [ + ['username' => 'mark'], + ['username' => 'juan'], + ['username' => 'jose'] +]; + +$languages = [ + ['PHP', 'Python', 'Ruby'], + ['Bash', 'PHP', 'Javascript'], + ['Javascript', 'Prolog'] +]; + +$merged = (new Collection($users))->insert('skills', $languages); +$result = $merged->toArray(); + +// $result contains +[ + ['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']], + ['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']], + ['username' => 'jose', 'skills' => ['Javascript', 'Prolog']] +]; +``` + +The first parameter for the `insert()` method is a dot-separated path of +properties to follow so that the elements can be inserted at that position. The +second argument is anything that can be converted to a collection object. + +Please observe that elements are inserted by the position they are found, thus, +the first element of the second collection is merged into the first +element of the first collection. + +If there are not enough elements in the second collection to insert into the +first one, then the target property will not be present: + +``` php +$languages = [ + ['PHP', 'Python', 'Ruby'], + ['Bash', 'PHP', 'Javascript'] +]; + +$merged = (new Collection($users))->insert('skills', $languages); +$result = $merged->toArray(); + +// $result contains +[ + ['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']], + ['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']], + ['username' => 'jose'] +]; +``` + +The `insert()` method can operate array elements or objects implementing the +`ArrayAccess` interface. + +### Making Collection Methods Reusable + +Using closures for collection methods is great when the work to be done is small +and focused, but it can get messy very quickly. This becomes more obvious when +a lot of different methods need to be called or when the length of the closure +methods is more than just a few lines. + +There are also cases when the logic used for the collection methods can be +reused in multiple parts of your application. It is recommended that you +consider extracting complex collection logic to separate classes. For example, +imagine a lengthy closure like this one: + +``` php +$collection + ->map(function ($row, $key) { + if (!empty($row['items'])) { + $row['total'] = collection($row['items'])->sumOf('price'); + } + + if (!empty($row['total'])) { + $row['tax_amount'] = $row['total'] * 0.25; + } + + // More code here... + + return $modifiedRow; + }); +``` + +This can be refactored by creating another class: + +``` php +class TotalOrderCalculator +{ + public function __invoke($row, $key) + { + if (!empty($row['items'])) { + $row['total'] = collection($row['items'])->sumOf('price'); + } + + if (!empty($row['total'])) { + $row['tax_amount'] = $row['total'] * 0.25; + } + + // More code here... + + return $modifiedRow; + } +} + +// Use the logic in your map() call +$collection->map(new TotalOrderCalculator) +``` + +### through() + +`method` Cake\\Collection\\Collection::**through**($callback): CollectionInterface + +Sometimes a chain of collection method calls can become reusable in other parts +of your application, but only if they are called in that specific order. In +those cases you can use `through()` in combination with a class implementing +`__invoke` to distribute your handy data processing calls: + +``` php +$collection + ->map(new ShippingCostCalculator) + ->map(new TotalOrderCalculator) + ->map(new GiftCardPriceReducer) + ->buffered() + ... +``` + +The above method calls can be extracted into a new class so they don't need to +be repeated every time: + +``` php +class FinalCheckOutRowProcessor +{ + public function __invoke($collection) + { + return $collection + ->map(new ShippingCostCalculator) + ->map(new TotalOrderCalculator) + ->map(new GiftCardPriceReducer) + ->buffered() + ... + } +} + +// Now you can use the through() method to call all methods at once +$collection->through(new FinalCheckOutRowProcessor); +``` + +### Optimizing Collections + +### buffered() + +`method` Cake\\Collection\\Collection::**buffered**(): CollectionInterface + +Collections often perform most operations that you create using its functions in +a lazy way. This means that even though you can call a function, it does not +mean it is executed right away. This is true for a great deal of functions in +this class. Lazy evaluation allows you to save resources in situations +where you don't use all the values in a collection. You might not use all the +values when iteration stops early, or when an exception/failure case is reached +early. + +Additionally, lazy evaluation helps speed up some operations. Consider the +following example: + +``` php +$collection = new Collection($oneMillionItems); +$collection = $collection->map(function ($item) { + return $item * 2; +}); +$itemsToShow = $collection->take(30); +``` + +Had the collections not been lazy, we would have executed one million operations, +even though we only wanted to show 30 elements out of it. Instead, our map +operation was only applied to the 30 elements we used. We can also +derive benefits from this lazy evaluation for smaller collections when we +do more than one operation on them. For example: calling `map()` twice and +then `filter()`. + +Lazy evaluation comes with its downside too. You could be doing the same +operations more than once if you optimize a collection prematurely. Consider +this example: + +``` php +$ages = $collection->extract('age'); + +$youngerThan30 = $ages->filter(function ($item) { + return $item < 30; +}); + +$olderThan30 = $ages->filter(function ($item) { + return $item > 30; +}); +``` + +If we iterate both `youngerThan30` and `olderThan30`, the collection would +unfortunately execute the `extract()` operation twice. This is because +collections are immutable and the lazy-extracting operation would be done for +both filters. + +Luckily we can overcome this issue with a single function. If you plan to reuse +the values from certain operations more than once, you can compile the results +into another collection using the `buffered()` function: + +``` php +$ages = $collection->extract('age')->buffered(); +$youngerThan30 = ... +$olderThan30 = ... +``` + +Now, when both collections are iterated, they will only call the +extracting operation once. + +### Making Collections Rewindable + +The `buffered()` method is also useful for converting non-rewindable iterators +into collections that can be iterated more than once: + +``` php +public function results() +{ + ... + foreach ($transientElements as $e) { + yield $e; + } +} +$rewindable = (new Collection(results()))->buffered(); +``` + +### Cloning Collections + +### compile() + +`method` Cake\\Collection\\Collection::**compile**($preserveKeys = true): CollectionInterface + +Sometimes you need to get a clone of the elements from another +collection. This is useful when you need to iterate the same set from different +places at the same time. In order to clone a collection out of another use the +`compile()` method: + +``` php +$ages = $collection->extract('age')->compile(); + +foreach ($ages as $age) { + foreach ($collection as $element) { + echo h($element->name) . ' - ' . $age; + } +} +``` diff --git a/docs/en/core-libraries/email.md b/docs/en/core-libraries/email.md new file mode 100644 index 0000000000..24dc83edce --- /dev/null +++ b/docs/en/core-libraries/email.md @@ -0,0 +1,716 @@ +# Mailer + +`class` Cake\\Mailer\\**Mailer**(string|array|null $profile = null) + +`Mailer` is a convenience class for sending email. With this class you can send +email from any place inside of your application. + +## Basic Usage + +First of all, you should ensure the class is loaded: + +``` php +use Cake\Mailer\Mailer; +``` + +After you've loaded `Mailer`, you can send an email with the following: + +``` php +$mailer = new Mailer('default'); +$mailer->setFrom(['me@example.com' => 'My Site']) + ->setTo('you@example.com') + ->setSubject('About') + ->deliver('My message'); +``` + +Since `Mailer`'s setter methods return the instance of the class, you are able +to set its properties with method chaining. + +`Mailer` has several methods for defining recipients - `setTo()`, `setCc()`, +`setBcc()`, `addTo()`, `addCc()` and `addBcc()`. The main difference being +that the first three will overwrite what was already set and the latter will just +add more recipients to their respective field: + +``` php +$mailer = new Mailer(); +$mailer->setTo('to@example.com', 'To Example'); +$mailer->addTo('to2@example.com', 'To2 Example'); +// The email's To recipients are: to@example.com and to2@example.com +$mailer->setTo('test@example.com', 'ToTest Example'); +// The email's To recipient is: test@example.com +``` + +### Choosing the Sender + +When sending email on behalf of other people, it's often a good idea to define the +original sender using the Sender header. You can do so using `setSender()`: + +``` php +$mailer = new Mailer(); +$mailer->setSender('app@example.com', 'MyApp emailer'); +``` + +> [!NOTE] +> It's also a good idea to set the envelope sender when sending mail on another +> person's behalf. This prevents them from getting any messages about +> deliverability. + + + +## Configuration + +Mailer profiles and email transport settings are defined in your application's +configuration files. The `Email` and `EmailTransport` keys define mailer +profiles and email transport configurations respectively. During application +bootstrap configuration settings are passed from `Configure` into the +`Mailer` and `TransportFactory` classes using `setConfig()`. By defining +profiles and transports, you can keep your application code free of +configuration data, and avoid duplication that makes maintenance and deployment +more difficult. + +To load a predefined configuration, you can use the `setProfile()` method or +pass it to the constructor of `Mailer`: + +``` php +$mailer = new Mailer(); +$mailer->setProfile('default'); + +// Or in constructor +$mailer = new Mailer('default'); +``` + +Instead of passing a string which matches a preset configuration name, you can +also just load an array of options: + +``` php +$mailer = new Mailer(); +$mailer->setProfile(['from' => 'me@example.org', 'transport' => 'my_custom']); + +// Or in constructor +$mailer = new Mailer(['from' => 'me@example.org', 'transport' => 'my_custom']); +``` + + + +### Configuration Profiles + +Defining delivery profiles allows you to consolidate common email settings into +re-usable profiles. Your application can have as many profiles as necessary. The +following configuration keys are used: + +- `'from'`: Mailer or array of sender. See `Mailer::setFrom()`. +- `'sender'`: Mailer or array of real sender. See `Mailer::setSender()`. +- `'to'`: Mailer or array of destination. See `Mailer::setTo()`. +- `'cc'`: Mailer or array of carbon copy. See `Mailer::setCc()`. +- `'bcc'`: Mailer or array of blind carbon copy. See `Mailer::setBcc()`. +- `'replyTo'`: Mailer or array to reply the e-mail. See `Mailer::setReplyTo()`. +- `'readReceipt'`: Mailer address or an array of addresses to receive the + receipt of read. See `Mailer::setReadReceipt()`. +- `'returnPath'`: Mailer address or an array of addresses to return if have + some error. See `Mailer::setReturnPath()`. +- `'messageId'`: Message ID of e-mail. See `Mailer::setMessageId()`. +- `'subject'`: Subject of the message. See `Mailer::setSubject()`. +- `'message'`: Content of message. Do not set this field if you are using rendered content. +- `'priority'`: Priority of the email as numeric value (usually from 1 to 5 with 1 being the highest). +- `'headers'`: Headers to be included. See `Mailer::setHeaders()`. +- `'viewRenderer'`: If you are using rendered content, set the view classname. + See `ViewBuilder::setClassName()`. +- `'template'`: If you are using rendered content, set the template name. See + `ViewBuilder::setTemplate()`. +- `'theme'`: Theme used when rendering template. See `ViewBuilder::setTheme()`. +- `'layout'`: If you are using rendered content, set the layout to render. See + `ViewBuilder::setTemplate()`. +- `'autoLayout'`: If you want to render a template without layout, set this field to + `false`. See `ViewBuilder::disableAutoLayout()`. +- `'viewVars'`: If you are using rendered content, set the array with + variables to be used in the view. See `Mailer::setViewVars()`. +- `'attachments'`: List of files to attach. See `Mailer::setAttachments()`. +- `'emailFormat'`: Format of email (html, text or both). See `Mailer::setEmailFormat()`. +- `'transport'`: Transport configuration name. See [Email Transport](#email-transport). +- `'log'`: Log level to log the email headers and message. `true` will use + LOG_DEBUG. See [Logging Levels](../core-libraries/logging#logging-levels). Note that logs will be emitted under the scope named `email`. + See also [Logging Scopes](../core-libraries/logging#logging-scopes). +- `'helpers'`: Array of helpers used in the email template. + `ViewBuilder::setHelpers()`/`ViewBuilder::addHelpers()`. + +> [!NOTE] +> The values of above keys using Mailer or array, like from, to, cc, etc will be passed +> as first parameter of corresponding methods. The equivalent for: +> `$mailer->setFrom('my@example.com', 'My Site')` +> would be defined as `'from' => ['my@example.com' => 'My Site']` in your config + +## Setting Headers + +In `Mailer` you are free to set whatever headers you want. Do not forget to +put the `X-` prefix for your custom headers. + +See `Mailer::setHeaders()` and `Mailer::addHeaders()` + +## Sending Templated Emails + +Emails are often much more than just a simple text message. In order +to facilitate that, CakePHP provides a way to send emails using CakePHP's +[view layer](../views). + +The templates for emails reside in a special folder `templates/email/` of your +application. Mailer views can also use layouts and elements just like normal views: + +``` php +$mailer = new Mailer(); +$mailer + ->setEmailFormat('html') + ->setTo('bob@example.com') + ->setFrom('app@domain.com') + ->viewBuilder() + ->setTemplate('welcome') + ->setLayout('fancy'); + +$mailer->deliver(); +``` + +The above would use **templates/email/html/welcome.php** for the view +and **templates/layout/email/html/fancy.php** for the layout. You can +send multipart templated email messages as well: + +``` php +$mailer = new Mailer(); +$mailer + ->setEmailFormat('both') + ->setTo('bob@example.com') + ->setFrom('app@domain.com') + ->viewBuilder() + ->setTemplate('welcome') + ->setLayout('fancy'); + +$mailer->deliver(); +``` + +This would use the following template files: + +- **templates/email/text/welcome.php** +- **templates/layout/email/text/fancy.php** +- **templates/email/html/welcome.php** +- **templates/layout/email/html/fancy.php** + +When sending templated emails you have the option of sending either +`text`, `html` or `both`. + +You can set all view related config using the view builder instance got by +`Mailer::viewBuilder()` similar to how you do the same in controller. + +You can set view variables with `Mailer::setViewVars()`: + +``` php +$mailer = new Mailer('templated'); +$mailer->setViewVars(['value' => 12345]); +``` + +Or you can use the view builder methods `ViewBuilder::setVar()` and +`ViewBuilder::setVars()`. + +In your email templates you can use these with: + +``` html +

Here is your value:

+``` + +You can use helpers in emails as well, much like you can in normal template files. +By default only the `HtmlHelper` is loaded. You can load additional +helpers using the `ViewBuilder::addHelpers()` method: + +``` php +$mailer->viewBuilder()->addHelpers(['Html', 'Custom', 'Text']); +``` + +When adding helpers be sure to include 'Html' or it will be removed from the +helpers loaded in your email template. + +> [!NOTE] +> In versions prior to 4.3.0, you will need to use `setHelpers()` instead. + +If you want to send email using templates in a plugin you can use the familiar +`plugin syntax` to do so: + +``` php +$mailer = new Mailer(); +$mailer->viewBuilder()->setTemplate('Blog.new_comment'); +``` + +The above would use template and layout from the Blog plugin as an example. + +In some cases, you might need to override the default template provided by plugins. +You can do this using themes: + +``` php +$mailer->viewBuilder() + ->setTemplate('Blog.new_comment') + ->setLayout('Blog.auto_message') + ->setTheme('TestTheme'); +``` + +This allows you to override the `new_comment` template in your theme without +modifying the Blog plugin. The template file needs to be created in the +following path: +**templates/plugin/TestTheme/plugin/Blog/email/text/new_comment.php**. + +## Sending Attachments + +### Mailer::setAttachments() + +`method` Cake\\Mailer\\Mailer::**setAttachments**($attachments) + +You can attach files to email messages as well. There are a few +different formats depending on what kind of files you have, and how +you want the filenames to appear in the recipient's mail client: + +1. Array: `$mailer->setAttachments(['/full/file/path/file.png'])` will + attach this file with the name file.png.. + +2. Array with key: + `$mailer->setAttachments(['photo.png' => '/full/some_hash.png'])` will + attach some_hash.png with the name photo.png. The recipient will see + photo.png, not some_hash.png. + +3. Nested arrays: + + ``` php + $mailer->setAttachments([ + 'photo.png' => [ + 'file' => '/full/some_hash.png', + 'mimetype' => 'image/png', + 'contentId' => 'my-unique-id', + ], + ]); + ``` + + The above will attach the file with different mimetype and with custom + Content ID (when set the content ID the attachment is transformed to inline). + The mimetype and contentId are optional in this form. + + 3.1. When you are using the `contentId`, you can use the file in the HTML + body like ``. + + 3.2. You can use the `contentDisposition` option to disable the + `Content-Disposition` header for an attachment. This is useful when + sending ical invites to clients using outlook. + + 3.3 Instead of the `file` option you can provide the file contents as + a string using the `data` option. This allows you to attach files without + needing file paths to them. + +### Mailer::addAttachment() + +`method` Cake\\Mailer\\Mailer::**addAttachment**(\\Psr\\Http\\Message\\UploadedFileInterface|string $path, ?string $name, ?string $mimetype, ?string $contentId, ?bool $contentDisposition) + +You can also add attachments using the `addAttachment()` method. + +> \$mailer-\>addAttachment('/full/file/path/file.png'); + +### Relaxing Address Validation Rules + +### Mailer::setEmailPattern() + +`method` Cake\\Mailer\\Mailer::**setEmailPattern**($pattern) + +If you are having validation issues when sending to non-compliant addresses, you +can relax the pattern used to validate email addresses. This is sometimes +necessary when dealing with some ISPs: + +``` php +$mailer = new Mailer('default'); + +// Relax the email pattern, so you can send +// to non-conformant addresses. +$mailer->setEmailPattern($newPattern); +``` + +## Sending Emails from CLI + +When sending emails within a CLI script (Shells, Tasks, ...) you should manually +set the domain name for Mailer to use. It will serve as the host name for the +message id (since there is no host name in a CLI environment): + +``` php +$mailer->setDomain('www.example.org'); +// Results in message ids like ```` (valid) +// Instead of ``` (invalid) +``` + +A valid message id can help to prevent emails ending up in spam folders. + +## Creating Reusable Emails + +Until now we have seen how to directly use the the `Mailer` class to create and +send one emails. But main feature of mailer is to allow creating reusable emails +throughout your application. They can also be used to contain multiple email +configurations in one location. This helps keep your code DRYer and keeps email +configuration noise out of other areas in your application. + +In this example we will be creating a `Mailer` that contains user-related +emails. To create our `UserMailer`, create the file +**src/Mailer/UserMailer.php**. The contents of the file should look like the +following: + +``` php +namespace App\Mailer; + +use Cake\Mailer\Mailer; + +class UserMailer extends Mailer +{ + public function welcome($user) + { + $this + ->setTo($user->email) + ->setSubject(sprintf('Welcome %s', $user->name)) + ->viewBuilder() + ->setTemplate('welcome_mail'); // By default template with same name as method name is used. + } + + public function resetPassword($user) + { + $this + ->setTo($user->email) + ->setSubject('Reset password') + ->setViewVars(['token' => $user->token]); + } +} +``` + +In our example we have created two methods, one for sending a welcome email, and +another for sending a password reset email. Each of these methods expect a user +`Entity` and utilizes its properties for configuring each email. + +We are now able to use our `UserMailer` to send out our user-related emails +from anywhere in our application. For example, if we wanted to send our welcome +email we could do the following: + +``` php +namespace App\Controller; + +use Cake\Mailer\MailerAwareTrait; + +class UsersController extends AppController +{ + use MailerAwareTrait; + + public function register() + { + $user = $this->Users->newEmptyEntity(); + if ($this->request->is('post')) { + $user = $this->Users->patchEntity($user, $this->request->getData()) + if ($this->Users->save($user)) { + $this->getMailer('User')->send('welcome', [$user]); + } + } + $this->set('user', $user); + } +} +``` + +If we wanted to completely separate sending a user their welcome email from our +application's code, we can have our `UserMailer` subscribe to the +`Model.afterSave` event. By subscribing to an event, we can keep our +application's user-related classes completely free of email-related logic and +instructions. For example, we could add the following to our `UserMailer`: + +``` php +public function implementedEvents(): array +{ + return [ + 'Model.afterSave' => 'onRegistration', + ]; +} + +public function onRegistration(EventInterface $event, EntityInterface $entity, ArrayObject $options): void +{ + if ($entity->isNew()) { + $this->send('welcome', [$entity]); + } +} +``` + +You can now register the mailer as an event listener and the +`onRegistration()` method will be invoked every time the `Model.afterSave` +event is fired: + +``` php +// attach to Users event manager +$this->Users->getEventManager()->on($this->getMailer('User')); +``` + +> [!NOTE] +> For information on how to register event listener objects, +> please refer to the [Registering Event Listeners](../core-libraries/events#registering-event-listeners) documentation. + + + +## Configuring Transports + +Email messages are delivered by transports. Different transports allow you to +send messages via PHP's `mail()` function, SMTP servers, or not at all which +is useful for debugging. Configuring transports allows you to keep configuration +data out of your application code and makes deployment simpler as you can simply +change the configuration data. An example transport configuration looks like: + +``` text +// In config/app.php +'EmailTransport' => [ + // Sample Mail configuration + 'default' => [ + 'className' => 'Mail', + ], + // Sample SMTP configuration + 'gmail' => [ + 'host' => 'smtp.gmail.com', + 'port' => 587, + 'username' => 'my@gmail.com', + 'password' => 'secret', + 'className' => 'Smtp', + 'tls' => true, + ], +], +``` + +Transports can also be configured at runtime using +`TransportFactory::setConfig()`: + +``` php +use Cake\Mailer\TransportFactory; + +// Define an SMTP transport +TransportFactory::setConfig('gmail', [ + 'host' => 'ssl://smtp.gmail.com', + 'port' => 465, + 'username' => 'my@gmail.com', + 'password' => 'secret', + 'className' => 'Smtp' +]); +``` + +You can configure SSL SMTP servers, like Gmail. To do so, put the `ssl://` +prefix in the host and configure the port value accordingly. You can also +enable TLS SMTP using the `tls` option: + +``` php +use Cake\Mailer\TransportFactory; + +TransportFactory::setConfig('gmail', [ + 'host' => 'smtp.gmail.com', + 'port' => 587, + 'username' => 'my@gmail.com', + 'password' => 'secret', + 'className' => 'Smtp', + 'tls' => true +]); +``` + +The above configuration would enable TLS communication for email messages. + +To configure your mailer to use a specific transport you can use +`Cake\Mailer\Mailer::setTransport()` method or have the transport +in your configuration: + +``` php +// Use a named transport already configured using TransportFactory::setConfig() +$mailer->setTransport('gmail'); + +// Use a constructed object. +$mailer->setTransport(new \Cake\Mailer\Transport\DebugTransport()); +``` + +> [!WARNING] +> You will need to have access for less secure apps enabled in your Google +> account for this to work: +> [Allowing less secure apps to access your +> account](https://support.google.com/accounts/answer/6010255). + +> [!NOTE] +>   [Gmail SMTP settings](https://support.google.com/a/answer/176600?hl=en). + +> [!NOTE] +> To use SSL + SMTP, you will need to have the SSL configured in your PHP +> install. + +Configuration options can also be provided as a `DSN` string. This is +useful when working with environment variables or `PaaS` providers: + +``` css +TransportFactory::setConfig('default', [ + 'url' => 'smtp://my@gmail.com:secret@smtp.gmail.com:587?tls=true', +]); +``` + +When using a DSN string you can define any additional parameters/options as +query string arguments. + +### Mailer::drop() + +`static` Cake\\Mailer\\Mailer::**drop**($key) + +Once configured, transports cannot be modified. In order to modify a transport +you must first drop it and then reconfigure it. + +### Creating Custom Transports + +You are able to create your custom transports for situations such as send email using services +like SendGrid, MailGun or Postmark. To create your transport, first create the file +**src/Mailer/Transport/ExampleTransport.php** (where Example is the name of your +transport). To start, your file should look like: + +``` php +namespace App\Mailer\Transport; + +use Cake\Mailer\AbstractTransport; +use Cake\Mailer\Message; + +class ExampleTransport extends AbstractTransport +{ + public function send(Message $message): array + { + // Do something. + } +} +``` + +You must implement the method `send(Message $message)` with your custom logic. + +## Sending emails without using Mailer + +The `Mailer` is a higher level abstraction class which acts as a bridge between +the `Cake\Mailer\Message`, `Cake\Mailer\Renderer` and `Cake\Mailer\AbstractTransport` +classes to configure emails with a fluent interface. + +If you want you can use these classes directly with the `Mailer` too. + +For example: + +``` php +$render = new \Cake\Mailer\Renderer(); +$render->viewBuilder() + ->setTemplate('custom') + ->setLayout('sparkly'); + +$message = new \Cake\Mailer\Message(); +$message + ->setFrom('admin@cakephp.org') + ->setTo('user@foo.com') + ->setBody($render->render()); + +$transport = new \Cake\Mailer\Transport\MailTransport(); +$result = $transport->send($message); +``` + +You can even skip using the `Renderer` and set the message body directly +using `Message::setBodyText()` and `Message::setBodyHtml()` methods. + + + +## Testing Mailers + +To test mailers, add `Cake\TestSuite\EmailTrait` to your test case. +The `EmailTrait` uses PHPUnit hooks to replace your application's email transports +with a proxy that intercepts email messages and allows you to do assertions +on the mail that would be delivered. + +Add the trait to your test case to start testing emails, and load routes if your +emails need to generate URLs: + +``` php +namespace App\Test\TestCase\Mailer; + +use App\Mailer\WelcomeMailer; +use App\Model\Entity\User; + +use Cake\TestSuite\EmailTrait; +use Cake\TestSuite\TestCase; + +class WelcomeMailerTestCase extends TestCase +{ + use EmailTrait; + + public function setUp(): void + { + parent::setUp(); + $this->loadRoutes(); + } +} +``` + +Let's assume we have a mailer that delivers welcome emails when a new user +registers. We want to check that the subject and body contain the user's name: + +``` php +// in our WelcomeMailerTestCase class. +public function testName() +{ + $user = new User([ + 'name' => 'Alice Alittea', + 'email' => 'alice@example.org', + ]); + $mailer = new WelcomeMailer(); + $mailer->send('welcome', [$user]); + + $this->assertMailSentTo($user->email); + $this->assertMailContainsText('Hi ' . $user->name); + $this->assertMailContainsText('Welcome to CakePHP!'); +} +``` + +### Assertion methods + +The `Cake\TestSuite\EmailTrait` trait provides the following assertions: + +``` php +// Asserts an expected number of emails were sent +$this->assertMailCount($count); + +// Asserts that no emails were sent +$this->assertNoMailSent(); + +// Asserts an email was sent to an address +$this->assertMailSentTo($address); + +// Asserts an email was sent from an address +$this->assertMailSentFrom($emailAddress); +$this->assertMailSentFrom([$emailAddress => $displayName]); + +// Asserts an email contains expected contents +$this->assertMailContains($contents); + +// Asserts an email contains expected html contents +$this->assertMailContainsHtml($contents); + +// Asserts an email contains expected text contents +$this->assertMailContainsText($contents); + +// Asserts an email contains the expected value within an Message getter (for example, "subject") +$this->assertMailSentWith($expected, $parameter); + +// Asserts an email at a specific index was sent to an address +$this->assertMailSentToAt($at, $address); + +// Asserts an email at a specific index was sent from an address +$this->assertMailSentFromAt($at, $address); + +// Asserts an email at a specific index contains expected contents +$this->assertMailContainsAt($at, $contents); + +// Asserts an email at a specific index contains expected html contents +$this->assertMailContainsHtmlAt($at, $contents); + +// Asserts an email at a specific index contains expected text contents +$this->assertMailContainsTextAt($at, $contents); + +// Asserts an email contains an attachment +$this->assertMailContainsAttachment('test.png'); + +// Asserts an email at a specific index contains the expected value within an Message getter (for example, "cc") +$this->assertMailSentWithAt($at, $expected, $parameter); + +// Asserts an email contains a substring in the subject. +$this->assertMailSubjectContains('Free Offer'); + +// Asserts an email at the specific index contains a substring in the subject. +$this->assertMailSubjectContainsAt(1, 'Free Offer'); +``` diff --git a/docs/en/core-libraries/events.md b/docs/en/core-libraries/events.md new file mode 100644 index 0000000000..6c1b953553 --- /dev/null +++ b/docs/en/core-libraries/events.md @@ -0,0 +1,667 @@ +# Events System + +Creating maintainable applications is both a science and an art. It is +well-known that a key for having good quality code is making your objects +loosely coupled and strongly cohesive at the same time. Cohesion means that +all methods and properties for a class are strongly related to the class +itself and it is not trying to do the job other objects should be doing, +while loosely coupling is the measure of how little a class is "wired" +to external objects, and how much that class is depending on them. + +There are certain cases where you need to cleanly communicate with other parts +of an application, without having to hard code dependencies, thus losing +cohesion and increasing class coupling. Using the Observer pattern, which allows +objects to notify other objects and anonymous listeners about changes is +a useful pattern to achieve this goal. + +Listeners in the observer pattern can subscribe to events and choose to act upon +them if they are relevant. If you have used JavaScript, there is a good chance +that you are already familiar with event driven programming. + +CakePHP emulates several aspects of how events are triggered and managed in +popular JavaScript libraries such as jQuery. In the CakePHP implementation, an +event object is dispatched to all listeners. The event object holds information +about the event, and provides the ability to stop event propagation at any +point. Listeners can register themselves or can delegate this task to other +objects and have the chance to alter the state and the event itself for the rest +of the callbacks. + +The event subsystem is at the heart of Model, Behavior, Controller, View and +Helper callbacks. If you've ever used any of them, you are already somewhat +familiar with events in CakePHP. + +## Example Event Usage + +Let's suppose you are building a Cart plugin, and you'd like to focus on just +handling order logic. You don't really want to include shipping logic, emailing +the user or decrementing the item from the stock, but these are important tasks +to the people using your plugin. If you were not using events, you may try to +implement this by attaching behaviors to models, or adding components to your +controllers. Doing so represents a challenge most of the time, since you +would have to come up with the code for externally loading those behaviors or +attaching hooks to your plugin controllers. + +Instead, you can use events to allow you to cleanly separate the concerns of +your code and allow additional concerns to hook into your plugin using events. +For example, in your Cart plugin you have an Orders model that deals with +creating orders. You'd like to notify the rest of the application that an order +has been created. To keep your Orders model clean you could use events: + +``` php +// Cart/Model/Table/OrdersTable.php +namespace Cart\Model\Table; + +use Cake\Event\Event; +use Cake\ORM\Table; + +class OrdersTable extends Table +{ + public function place($order) + { + if ($this->save($order)) { + $this->Cart->remove($order); + $event = new Event('Order.afterPlace', $this, [ + 'order' => $order + ]); + $this->getEventManager()->dispatch($event); + + return true; + } + + return false; + } +} +``` + +The above code allows you to notify the other parts of the application +that an order has been created. You can then do tasks like send email +notifications, update stock, log relevant statistics and other tasks in separate +objects that focus on those concerns. + +## Accessing Event Managers + +In CakePHP events are triggered against event managers. Event managers are +available in every Table, View and Controller using `getEventManager()`: + +``` php +$events = $this->getEventManager(); +``` + +Each model has a separate event manager, while the View and Controller +share one. This allows model events to be self contained, and allow components +or controllers to act upon events created in the view if necessary. + +### Global Event Manager + +In addition to instance level event managers, CakePHP provides a global event +manager that allows you to listen to any event fired in an application. This is +useful when attaching listeners to a specific instance might be cumbersome or +difficult. The global manager is a singleton instance of +`Cake\Event\EventManager`. Listeners attached to the global +dispatcher will be fired before instance listeners at the same priority. You can +access the global manager using a static method: + +``` php +// In any configuration file or piece of code that executes before the event +use Cake\Event\EventManager; + +EventManager::instance()->on( + 'Order.afterPlace', + $aCallback +); +``` + +One important thing you should consider is that there are events that will be +triggered having the same name but different subjects, so checking it in the +event object is usually required in any function that gets attached globally in +order to prevent some bugs. Remember that with the flexibility of using the +global manager, some additional complexity is incurred. + +`Cake\Event\EventManager::dispatch()` method accepts the event +object as an argument and notifies all listener and callbacks passing this +object along. The listeners will handle all the extra logic around the +`afterPlace` event, you can log the time, send emails, update user statistics +possibly in separate objects and even delegating it to offline tasks if you have +the need. + + + +### Tracking Events + +To keep a list of events that are fired on a particular `EventManager`, you +can enable event tracking. To do so, simply attach an +`Cake\Event\EventList` to the manager: + +``` php +EventManager::instance()->setEventList(new EventList()); +``` + +After firing an event on the manager, you can retrieve it from the event list: + +``` php +$eventsFired = EventManager::instance()->getEventList(); +$firstEvent = $eventsFired[0]; +``` + +Tracking can be disabled by removing the event list or calling +`Cake\Event\EventManager::trackEvents(false)`. + +## Core Events + +There are a number of core events within the framework which your application +can listen to. Each layer of CakePHP emits events that you can use in your +application. + +- [ORM/Model events](../orm/table-objects#table-callbacks) +- [Controller events](../controllers#controller-life-cycle) +- [View events](../views#view-events) + +### `Server.terminate` + +The `Server.terminate` event is triggered after the response has been sent to the +client. This event is useful for performing tasks that should be done after the +response has been sent, such as logging or sending emails. + +You can listen to this event using an event manager instance: + +``` php +use Cake\Event\EventManager; + +EventManager::instance()->on('Server.terminate', function ($event) { + // Perform tasks that should be done after the response has been + // sent to the client. +}); +``` + +Or using the `events` hook in your Application/Plugin class: + +``` php +use Cake\Event\EventManagerInterface; + +public function events(EventManagerInterface $eventManager): EventManagerInterface +{ + $eventManager->on('Server.terminate', function ($event) { + // Perform tasks that should be done after the response has been + // sent to the client. + }); + + return $eventManager; +} +``` + +> [!TIP] +> This is called even if an exception is thrown during the request, e.g. on 404 pages. + +> [!NOTE] +> The `Server.terminate` event only works for PHP-FPM implementations which +> support the `fastcgi_finish_request` function. + +### `Command.beforeExecute` & `Command.afterExecute` + +::: info Added in version 5.0.0 +The `Command.beforeExecute` & `Command.afterExecute` events were added in 5.0 +::: + +The `Command.beforeExecute` & `Command.afterExecute` events are triggered before and after +a command has been executed. Both events contain the `args` which are passed to the command +and the `afterExecute` event also contains the `exitCode` which is returned by the command. + +You can listen to this event using an event manager instance: + +``` php +use Cake\Event\EventManager; + +EventManager::instance()->on('Command.beforeExecute', function ($event, $args) { + $command = $event->getSubject(); + // Do stuff here +}); + +EventManager::instance()->on('Command.afterExecute', function ($event, $args, $result) { + $command = $event->getSubject(); + // Do stuff here +}); +``` + +Or using the `events` hook in your Application/Plugin class: + +``` php +use Cake\Event\EventManagerInterface; + +public function events(EventManagerInterface $eventManager): EventManagerInterface +{ + $eventManager->on('Command.beforeExecute', function ($event, $args) { + $command = $event->getSubject(); + // Do stuff here + }); + + $eventManager->on('Command.afterExecute', function ($event, $args, $result) { + $command = $event->getSubject(); + // Do stuff here + }); + + return $eventManager; +} +``` + + + +## Registering Listeners + +Listeners are the preferred way to register callbacks for an event. This is done +by implementing the `Cake\Event\EventListenerInterface` interface +in any class you wish to register some callbacks. Classes implementing it need +to provide the `implementedEvents()` method. This method must return an +associative array with all event names that the class will handle. + +To continue our previous example, let's imagine we have a UserStatistic class +responsible for calculating a user's purchasing history, and compiling into +global site statistics. This is a great place to use a listener class. Doing so +allows you to concentrate the statistics logic in one place and react to events +as necessary. Our `UserStatistics` listener might start out like: + +``` php +namespace App\Event; + +use Cake\Event\EventListenerInterface; + +class UserStatistic implements EventListenerInterface +{ + public function implementedEvents(): array + { + return [ + // Custom event names let you design your application events + // as required. + 'Order.afterPlace' => 'updateBuyStatistic', + ]; + } + + public function updateBuyStatistic($event) + { + // Code to update statistics + } +} + +// From your controller, attach the UserStatistic object to the Order's event manager +$statistics = new UserStatistic(); +$this->Orders->getEventManager()->on($statistics); +``` + +As you can see in the above code, the `on()` function will accept instances +of the `EventListener` interface. Internally, the event manager will use +`implementedEvents()` to attach the correct callbacks. + +::: info Added in version 5.1.0 +The `events` hook was added to the `BaseApplication` as well as the `BasePlugin` class +::: + +As of CakePHP 5.1 it is recommended to register event listeners by adding them via the `events` hook in your application or plugin class: + +``` php +namespace App; + +use App\Event\UserStatistic; +use Cake\Event\EventManagerInterface; +use Cake\Http\BaseApplication; + +class Application extends BaseApplication +{ + // The rest of your Application class + + public function events(EventManagerInterface $eventManager): EventManagerInterface + { + $statistics = new UserStatistic(); + $eventManager->on($statistics); + + return $eventManager; + } +} +``` + +### Registering Anonymous Listeners + +While event listener objects are generally a better way to implement listeners, +you can also bind any `callable` as an event listener. For example if we +wanted to put any orders into the log files, we could use a simple anonymous +function to do so: + +``` php +use Cake\Log\Log; + +// From within a controller, or during application bootstrap. +$this->Orders->getEventManager()->on('Order.afterPlace', function ($event) { + Log::write( + 'info', + 'A new order was placed with id: ' . $event->getSubject()->id + ); +}); +``` + +In addition to anonymous functions you can use any other callable type that PHP +supports: + +``` php +$events = [ + 'email-sending' => 'EmailSender::sendBuyEmail', + 'inventory' => [$this->InventoryManager, 'decrement'], +]; +foreach ($events as $callable) { + $eventManager->on('Order.afterPlace', $callable); +} +``` + +When working with plugins that don't trigger specific events, you can leverage +event listeners on the default events. Lets take an example 'UserFeedback' +plugin which handles feedback forms from users. From your application you would +like to know when a Feedback record has been saved and ultimately act on it. You +can listen to the global `Model.afterSave` event. However, you can take +a more direct approach and only listen to the event you really need: + +``` php +// You can create the following before the +// save operation, ie. config/bootstrap.php +use Cake\Datasource\FactoryLocator; +// If sending emails +use Cake\Mailer\Email; + +FactoryLocator::get('Table')->get('ThirdPartyPlugin.Feedbacks') + ->getEventManager() + ->on('Model.afterSave', function($event, $entity) + { + // For example we can send an email to the admin + $email = new Email('default'); + $email->setFrom(['info@yoursite.com' => 'Your Site']) + ->setTo('admin@yoursite.com') + ->setSubject('New Feedback - Your Site') + ->send('Body of message'); + }); +``` + +You can use this same approach to bind listener objects. + +### Interacting with Existing Listeners + +Assuming several event listeners have been registered the presence or absence +of a particular event pattern can be used as the basis of some action.: + +``` php +// Attach listeners to EventManager. +$this->getEventManager()->on('User.Registration', [$this, 'userRegistration']); +$this->getEventManager()->on('User.Verification', [$this, 'userVerification']); +$this->getEventManager()->on('User.Authorization', [$this, 'userAuthorization']); + +// Somewhere else in your application. +$events = $this->getEventManager()->matchingListeners('Verification'); +if (!empty($events)) { + // Perform logic related to presence of 'Verification' event listener. + // For example removing the listener if present. + $this->getEventManager()->off('User.Verification'); +} else { + // Perform logic related to absence of 'Verification' event listener +} +``` + +> [!NOTE] +> The pattern passed to the `matchingListeners` method is case sensitive. + + + +### Establishing Priorities + +In some cases you might want to control the order that listeners are invoked. +For instance, if we go back to our user statistics example. It would be ideal if +this listener was called at the end of the stack. By calling it at the end of +the listener stack, we can ensure that the event was not cancelled, and that no +other listeners raised exceptions. We can also get the final state of the +objects in the case that other listeners have modified the subject or event +object. + +Priorities are defined as an integer when adding a listener. The higher the +number, the later the method will be fired. The default priority for all +listeners is `10`. If you need your method to be run earlier, using any value +below this default will work. On the other hand if you desire to run the +callback after the others, using a number above `10` will do. + +If two callbacks happen to have the same priority value, they will be executed +with a the order they were attached. You set priorities using the `on()` +method for callbacks, and declaring it in the `implementedEvents()` function +for event listeners: + +``` php +// Setting priority for a callback +$callback = [$this, 'doSomething']; +$this->getEventManager()->on( + 'Order.afterPlace', + ['priority' => 2], + $callback +); + +// Setting priority for a listener +class UserStatistic implements EventListenerInterface +{ + public function implementedEvents(): array + { + return [ + 'Order.afterPlace' => [ + 'callable' => 'updateBuyStatistic', + 'priority' => 100 + ], + ]; + } +} +``` + +As you see, the main difference for `EventListener` objects is that you need +to use an array for specifying the callable method and the priority preference. +The `callable` key is a special array entry that the manager will read to know +what function in the class it should be calling. + +### Getting Event Data as Function Parameters + +When events have data provided in their constructor, the provided data is +converted into arguments for the listeners. An example from the View layer is +the afterRender callback: + +``` php +$this->getEventManager() + ->dispatch(new Event('View.afterRender', $this, ['view' => $viewFileName])); +``` + +The listeners of the `View.afterRender` callback should have the following +signature: + +``` javascript +function (EventInterface $event, $viewFileName) +``` + +Each value provided to the Event constructor will be converted into function +parameters in the order they appear in the data array. If you use an associative +array, the result of `array_values` will determine the function argument +order. + +> [!NOTE] +> Unlike in 2.x, converting event data to listener arguments is the default +> behavior and cannot be disabled. + +## Dispatching Events + +Once you have obtained an instance of an event manager you can dispatch events +using `Cake\Event\EventManager::dispatch()`. This method takes an +instance of the `Cake\Event\Event` class. Let's look at dispatching +an event: + +``` php +// An event listener has to be instantiated before dispatching an event. +// Create a new event and dispatch it. +$event = new Event('Order.afterPlace', $this, [ + 'order' => $order +]); +$this->getEventManager()->dispatch($event); +``` + +`Cake\Event\Event` accepts 3 arguments in its constructor. The +first one is the event name, you should try to keep this name as unique as +possible, while making it readable. We suggest a convention as follows: +`Layer.eventName` for general events happening at a layer level (for example, +`Controller.startup`, `View.beforeRender`) and `Layer.Class.eventName` for +events happening in specific classes on a layer, for example +`Model.User.afterRegister` or `Controller.Courses.invalidAccess`. + +The second argument is the `subject`, meaning the object associated to the +event, usually when it is the same class triggering events about itself, using +`$this` will be the most common case. Although a Component could trigger +controller events too. The subject class is important because listeners will get +immediate access to the object properties and have the chance to inspect or +change them on the fly. + +Finally, the third argument is any additional event data. This can be any data +you consider useful to pass around so listeners can act upon it. While this can +be an argument of any type, we recommend passing an associative array. + +The `Cake\Event\EventManager::dispatch()` method accepts an event +object as an argument and notifies all subscribed listeners. + + + +### Stopping Events + +Much like DOM events, you may want to stop an event to prevent additional +listeners from being notified. You can see this in action during model callbacks +(for example, beforeSave) in which it is possible to stop the saving operation if +the code detects it cannot proceed any further. + +In order to stop events you can either return `false` in your callbacks or +call the `stopPropagation()` method on the event object: + +``` php +public function doSomething($event) +{ + // ... + return false; // Stops the event +} + +public function updateBuyStatistic($event) +{ + // ... + $event->stopPropagation(); +} +``` + +Stopping an event will prevent any additional callbacks from being called. +Additionally the code triggering the event may behave differently based on the +event being stopped or not. Generally it does not make sense to stop 'after' +events, but stopping 'before' events is often used to prevent the entire +operation from occurring. + +To check if an event was stopped, you call the `isStopped()` method in the +event object: + +``` php +public function place($order) +{ + $event = new Event('Order.beforePlace', $this, ['order' => $order]); + $this->getEventManager()->dispatch($event); + if ($event->isStopped()) { + return false; + } + if ($this->Orders->save($order)) { + // ... + } + // ... +} +``` + +In the previous example the order would not get saved if the event is stopped +during the `beforePlace` process. + +### Getting Event Results + +Every time a callback returns a non-null non-false value, it gets stored in the +`$result` property of the event object. This is useful when you want to allow +callbacks to modify the event execution. Let's take again our `beforePlace` +example and let callbacks modify the `$order` data. + +Event results can be altered either using the event object result property +directly or returning the value in the callback itself: + +``` php +// A listener callback +public function doSomething($event) +{ + // ... + $alteredData = $event->getData('order') + $moreData; + + return $alteredData; +} + +// Another listener callback +public function doSomethingElse($event) +{ + // ... + $event->setResult(['order' => $alteredData] + $this->result()); +} + +// Using the event result +public function place($order) +{ + $event = new Event('Order.beforePlace', $this, ['order' => $order]); + $this->getEventManager()->dispatch($event); + if (!empty($event->getResult()['order'])) { + $order = $event->getResult()['order']; + } + if ($this->Orders->save($order)) { + // ... + } + // ... +} +``` + +It is possible to alter any event object property and have the new data passed +to the next callback. In most of the cases, providing objects as event data or +result and directly altering the object is the best solution as the reference is +kept the same and modifications are shared across all callback calls. + +### Removing Callbacks and Listeners + +If for any reason you want to remove any callback from the event manager just +call the `Cake\Event\EventManager::off()` method using as +arguments the first two parameters you used for attaching it: + +``` php +// Attaching a function +$this->getEventManager()->on('My.event', [$this, 'doSomething']); + +// Detaching the function +$this->getEventManager()->off('My.event', [$this, 'doSomething']); + +// Attaching an anonymous function. +$myFunction = function ($event) { ... }; +$this->getEventManager()->on('My.event', $myFunction); + +// Detaching the anonymous function +$this->getEventManager()->off('My.event', $myFunction); + +// Adding a EventListener +$listener = new MyEventLister(); +$this->getEventManager()->on($listener); + +// Detaching a single event key from a listener +$this->getEventManager()->off('My.event', $listener); + +// Detaching all callbacks implemented by a listener +$this->getEventManager()->off($listener); +``` + +Events are a great way of separating concerns in your application and make +classes both cohesive and decoupled from each other. Events can be utilized to +de-couple application code and make extensible plugins. + +Keep in mind that with great power comes great responsibility. Using too many +events can make debugging harder and require additional integration testing. + +## Additional Reading + +- [Behaviors](../orm/behaviors) +- [Command Objects](../console-commands/commands) +- [Components](../controllers/components) +- [Helpers](../views/helpers) +- [Testing Events](../development/testing#testing-events) diff --git a/docs/en/core-libraries/form.md b/docs/en/core-libraries/form.md new file mode 100644 index 0000000000..7c1e740fbc --- /dev/null +++ b/docs/en/core-libraries/form.md @@ -0,0 +1,243 @@ +# Modelless Forms + +`class` Cake\\Form\\**Form** + +Most of the time you will have forms backed by [ORM entities](../orm/entities) +and [ORM tables](../orm/table-objects) or other persistent stores, +but there are times when you'll need to validate user input and then perform an +action if the data is valid. The most common example of this is a contact form. + +## Creating a Form + +Generally when using the Form class you'll want to use a subclass to define your +form. This makes testing easier, and lets you re-use your form. Forms are put +into **src/Form** and usually have `Form` as a class suffix. For example, +a simple contact form would look like: + +``` php +// in src/Form/ContactForm.php +namespace App\Form; + +use Cake\Form\Form; +use Cake\Form\Schema; +use Cake\Validation\Validator; + +class ContactForm extends Form +{ + protected function _buildSchema(Schema $schema): Schema + { + return $schema->addField('name', 'string') + ->addField('email', ['type' => 'string']) + ->addField('body', ['type' => 'text']); + } + + public function validationDefault(Validator $validator): Validator + { + $validator->minLength('name', 10) + ->email('email'); + + return $validator; + } + + // Prior to 5.3.0 This should be + // `protected function _execute(array $data): bool` + protected function process(array $data): bool + { + // Send an email. + return true; + } +} +``` + +In the above example we see the 3 hook methods that forms provide: + +- `_buildSchema` is used to define the schema data that is used by FormHelper + to create an HTML form. You can define field type, length, and precision. +- `validationDefault` Gets a `Cake\Validation\Validator` instance + that you can attach validators to. +- `process` lets you define the behavior you want to happen when + `execute()` is called and the data is valid. + +You can always define additional public methods as you need as well. + +::: info Changed in version 5.3.0 +The `_execute` method was deprecated, and replaced by `process`. +::: + +## Processing Request Data + +Once you've defined your form, you can use it in your controller to process +and validate request data: + +``` php +// In a controller +namespace App\Controller; + +use App\Controller\AppController; +use App\Form\ContactForm; + +class ContactController extends AppController +{ + public function index() + { + $contact = new ContactForm(); + if ($this->request->is('post')) { + if ($contact->execute($this->request->getData())) { + $this->Flash->success('We will get back to you soon.'); + } else { + $this->Flash->error('There was a problem submitting your form.'); + } + } + $this->set('contact', $contact); + } +} +``` + +In the above example, we use the `execute()` method to run our form's +`process()` method only when the data is valid, and set flash messages +accordingly. If we want to use a non-default validation set we can use the +`validate` option: + +``` php +if ($contact->execute($this->request->getData(), 'update')) { + // Handle form success. +} +``` + +This option can also be set to `false` to disable validation. + +We could have also used the `validate()` method to only validate +the request data: + +``` php +$isValid = $form->validate($this->request->getData()); + +// You can also use other validation sets. The following +// would use the rules defined by `validationUpdate()` +$isValid = $form->validate($this->request->getData(), 'update'); +``` + +## Setting Form Values + +You can set default values for modelless forms using the `setData()` method. +Values set with this method will overwrite existing data in the form object: + +``` php +// In a controller +namespace App\Controller; + +use App\Controller\AppController; +use App\Form\ContactForm; + +class ContactController extends AppController +{ + public function index() + { + $contact = new ContactForm(); + if ($this->request->is('post')) { + if ($contact->execute($this->request->getData())) { + $this->Flash->success('We will get back to you soon.'); + } else { + $this->Flash->error('There was a problem submitting your form.'); + } + } + + if ($this->request->is('get')) { + $contact->setData([ + 'name' => 'John Doe', + 'email' => 'john.doe@example.com' + ]); + } + + $this->set('contact', $contact); + } +} +``` + +Values should only be defined if the request method is GET, otherwise +you will overwrite your previous POST Data which might have validation errors +that need corrections. You can use `set()` to add or replace individual fields +or a subset of fields: + +``` php +// Set one field. +$contact->set('name', 'John Doe'); + +// Set multiple fields; +$contact->set([ + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', +]); +``` + +## Getting Form Errors + +Once a form has been validated you can retrieve the errors from it: + +``` php +$errors = $form->getErrors(); +/* $errors contains +[ + 'name' => ['length' => 'Name must be at least two characters long'], + 'email' => ['format' => 'A valid email address is required'], +] +*/ + +$error = $form->getError('email'); +/* $error contains +[ + 'format' => 'A valid email address is required', +] +*/ +``` + +## Invalidating Individual Form Fields from Controller + +It is possible to invalidate individual fields from the controller without the +use of the Validator class. The most common use case for this is when the +validation is done on a remote server. In such case, you must manually +invalidate the fields accordingly to the feedback from the remote server: + +``` php +// in src/Form/ContactForm.php +public function setErrors($errors) +{ + $this->_errors = $errors; +} +``` + +According to how the validator class would have returned the errors, `$errors` +must be in this format: + +``` php +['fieldName' => ['validatorName' => 'The error message to display']] +``` + +Now you will be able to invalidate form fields by setting the fieldName, then +set the error messages: + +``` php +// In a controller +$contact = new ContactForm(); +$contact->setErrors(['email' => ['_required' => 'Your email is required']]); +``` + +Proceed to Creating HTML with FormHelper to see the results. + +## Creating HTML with FormHelper + +Once you've created a Form class, you'll likely want to create an HTML form for +it. FormHelper understands Form objects just like ORM entities: + +``` php +echo $this->Form->create($contact); +echo $this->Form->control('name'); +echo $this->Form->control('email'); +echo $this->Form->control('body'); +echo $this->Form->button('Submit'); +echo $this->Form->end(); +``` + +The above would create an HTML form for the `ContactForm` we defined earlier. +HTML forms created with FormHelper will use the defined schema and validator to +determine field types, maxlengths, and validation errors. diff --git a/docs/en/core-libraries/global-constants-and-functions.md b/docs/en/core-libraries/global-constants-and-functions.md new file mode 100644 index 0000000000..ae9eefb398 --- /dev/null +++ b/docs/en/core-libraries/global-constants-and-functions.md @@ -0,0 +1,264 @@ +# Constants & Functions + +While most of your day-to-day work in CakePHP will be utilizing core classes and +methods, CakePHP features a number of global convenience functions that may come +in handy. Many of these functions are for use with CakePHP classes (loading +model or component classes), but many others make working with arrays or +strings a little easier. + +We'll also cover some of the constants available in CakePHP applications. Using +these constants will help make upgrades more smooth, but are also convenient +ways to point to certain files or directories in your CakePHP application. + +## Global Functions + +Here are CakePHP's globally available functions. Most of them are just +convenience wrappers for other CakePHP functionality, such as debugging and +translating content. By default only namespaced functions are autoloaded, +however you can optionally load global aliases by adding: + +``` text +require CAKE . 'functions.php'; +``` + +To your application's `config/bootstrap.php`. Doing this will load global +aliases for *all* functions listed below. + +### __() + +`function` **__(string $string_id, [$formatArgs])** + +This function handles localization in CakePHP applications. The +`$string_id` identifies the ID for a translation. You can supply +additional arguments to replace placeholders in your string: + +``` text +__('You have {0} unread messages', $number); +``` + +You can also provide a name-indexed array of replacements: + +``` text +__('You have {unread} unread messages', ['unread' => $number]); +``` + +> [!NOTE] +> Check out the +> [Internationalization & Localization](../core-libraries/internationalization-and-localization) section for +> more information. + +### __d() + +`function` **__d(string $domain, string $msg, mixed $args = null)** + +Allows you to override the current domain for a single message lookup. + +Useful when internationalizing a plugin: +`echo __d('plugin_name', 'This is my plugin');` + +> [!NOTE] +> Make sure to use the underscored version of the plugin name here as domain. + +### __dn() + +`function` **__dn(string $domain, string $singular, string $plural, integer $count, mixed $args = null)** + +Allows you to override the current domain for a single plural message +lookup. Returns correct plural form of message identified by `$singular` +and `$plural` for count `$count` from domain `$domain`. + +### __dx() + +`function` **__dx(string $domain, string $context, string $msg, mixed $args = null)** + +Allows you to override the current domain for a single message lookup. It +also allows you to specify a context. + +The context is a unique identifier for the translations string that makes it +unique within the same domain. + +### __dxn() + +`function` **__dxn(string $domain, string $context, string $singular, string $plural, integer $count, mixed $args = null)** + +Allows you to override the current domain for a single plural message +lookup. It also allows you to specify a context. Returns correct plural +form of message identified by `$singular` and `$plural` for count +`$count` from domain `$domain`. Some languages have more than one form +for plural messages dependent on the count. + +The context is a unique identifier for the translations string that makes it +unique within the same domain. + +### __n() + +`function` **__n(string $singular, string $plural, integer $count, mixed $args = null)** + +Returns correct plural form of message identified by `$singular` and +`$plural` for count `$count`. Some languages have more than one form for +plural messages dependent on the count. + +### __x() + +`function` **__x(string $context, string $msg, mixed $args = null)** + +The context is a unique identifier for the translations string that makes it +unique within the same domain. + +### __xn() + +`function` **__xn(string $context, string $singular, string $plural, integer $count, mixed $args = null)** + +Returns correct plural form of message identified by `$singular` and +`$plural` for count `$count` from domain `$domain`. It also allows you +to specify a context. Some languages have more than one form for plural +messages dependent on the count. + +The context is a unique identifier for the translations string that makes it +unique within the same domain. + +### collection() + +`function` **collection(mixed $items)** + +Convenience wrapper for instantiating a new `Cake\Collection\Collection` +object, wrapping the passed argument. The `$items` parameter takes either +a `Traversable` object or an array. + +### debug() + +`function` **debug(mixed $var, boolean $showHtml = null, $showFrom = true)** + +If the core `$debug` variable is `true`, `$var` is printed out. +If `$showHTML` is `true` or left as `null`, the data is rendered to be +browser-friendly. If `$showFrom` is not set to `false`, the debug output +will start with the line from which it was called. Also see +[Debugging](../development/debugging) + +### dd() + +`function` **dd(mixed $var, boolean $showHtml = null)** + +It behaves like `debug()`, but execution is also halted. +If the core `$debug` variable is `true`, `$var` is printed. +If `$showHTML` is `true` or left as `null`, the data is rendered to be +browser-friendly. Also see [Debugging](../development/debugging) + +### pr() + +`function` **pr(mixed $var)** + +Convenience wrapper for `print_r()`, with the addition of +wrapping `
` tags around the output.
+
+### pj()
+
+`function` **pj(mixed $var)**
+
+JSON pretty print convenience function, with the addition of
+wrapping `
` tags around the output.
+
+It is meant for debugging the JSON representation of objects and arrays.
+
+### env()
+
+`function` **env(string $key, string $default = null)**
+
+Gets an environment variable from available sources. Used as a backup if
+`$_SERVER` or `$_ENV` are disabled.
+
+This function also emulates `PHP_SELF` and `DOCUMENT_ROOT` on
+unsupporting servers. In fact, it's a good idea to always use `env()`
+instead of `$_SERVER` or `getenv()` (especially if you plan to
+distribute the code), since it's a full emulation wrapper.
+
+### h()
+
+`function` **h(string $text, boolean $double = true, string $charset = null)**
+
+Convenience wrapper for `htmlspecialchars()`.
+
+### pluginSplit()
+
+`function` **pluginSplit(string $name, boolean $dotAppend = false, string $plugin = null)**
+
+Splits a dot syntax plugin name into its plugin and class name. If `$name`
+does not have a dot, then index 0 will be `null`.
+
+Commonly used like `list($plugin, $name) = pluginSplit('Users.User');`
+
+### namespaceSplit()
+
+`function` **namespaceSplit(string $class)**
+
+Split the namespace from the classname.
+
+Commonly used like `list($namespace, $className) = namespaceSplit('Cake\Core\App');`
+
+## Core Definition Constants
+
+Most of the following constants refer to paths in your application.
+
+`constant` Cake\\Core\\**APP**
+
+Absolute path to your application directory, including a trailing slash.
+
+`constant` Cake\\Core\\**APP_DIR**
+
+Equals `app` or the name of your application directory.
+
+`constant` Cake\\Core\\**CACHE**
+
+Path to the cache files directory. It can be shared between hosts in a
+multi-server setup.
+
+`constant` Cake\\Core\\**CAKE**
+
+Path to the cake directory.
+
+`constant` Cake\\Core\\**CAKE_CORE_INCLUDE_PATH**
+
+Path to the root lib directory.
+
+`constant` Cake\\Core\\**CONFIG**
+
+Path to the config directory.
+
+`constant` Cake\\Core\\**CORE_PATH**
+
+Path to the CakePHP directory with ending directory slash.
+
+`constant` Cake\\Core\\**DS**
+
+Short for PHP's `DIRECTORY_SEPARATOR`, which is `/` on Linux and `\`
+on Windows.
+
+`constant` Cake\\Core\\**LOGS**
+
+Path to the logs directory.
+
+`constant` Cake\\Core\\**RESOURCES**
+
+Path to the resources directory.
+
+`constant` Cake\\Core\\**ROOT**
+
+Path to the root directory.
+
+`constant` Cake\\Core\\**TESTS**
+
+Path to the tests directory.
+
+`constant` Cake\\Core\\**TMP**
+
+Path to the temporary files directory.
+
+`constant` Cake\\Core\\**WWW_ROOT**
+
+Full path to the webroot.
+
+## Timing Definition Constants
+
+`constant` Cake\\Core\\**TIME_START**
+
+Unix timestamp in microseconds as a float from when the application started.
diff --git a/docs/en/core-libraries/hash.md b/docs/en/core-libraries/hash.md
new file mode 100644
index 0000000000..ef0cc57f83
--- /dev/null
+++ b/docs/en/core-libraries/hash.md
@@ -0,0 +1,951 @@
+# Hash
+
+`class` Cake\\Utility\\**Hash**
+
+Array management, if done right, can be a very powerful and useful
+tool for building smarter, more optimized code. CakePHP offers a
+very useful set of static utilities in the Hash class that allow you
+to do just that.
+
+CakePHP's Hash class can be called from any model or controller in
+the same way Inflector is called. Example: `Hash::combine()`.
+
+
+
+## Hash Path Syntax
+
+The path syntax described below is used by all the methods in `Hash`. Not all
+parts of the path syntax are available in all methods. A path expression is
+made of any number of tokens. Tokens are composed of two groups. Expressions,
+are used to traverse the array data, while matchers are used to qualify
+elements. You apply matchers to expression elements.
+
+### Expression Types
+
+| Expression | Definition |
+|----|----|
+| `{n}` | Represents a numeric key. Will match any string or numeric key. |
+| `{s}` | Represents a string. Will match any string value including numeric string values. |
+| `{*}` | Matches any value. |
+| `Foo` | Matches keys with the exact same value. |
+
+All expression elements are supported by all methods. In addition to expression
+elements, you can use attribute matching with certain methods. They are `extract()`,
+`combine()`, `format()`, `check()`, `map()`, `reduce()`,
+`apply()`, `sort()`, `insert()`, `remove()` and `nest()`.
+
+### Attribute Matching Types
+
+| Matcher | Definition |
+|----|----|
+| `[id]` | Match elements with a given array key. |
+| `[id=2]` | Match elements with id equal to 2. |
+| `[id!=2]` | Match elements with id not equal to 2. |
+| `[id>2]` | Match elements with id greater than 2. |
+| `[id>=2]` | Match elements with id greater than or equal to 2. |
+| `[id<2]` | Match elements with id less than 2 |
+| `[id<=2]` | Match elements with id less than or equal to 2. |
+| `[text=/.../]` | Match elements that have values matching the regular expression inside `...`. |
+
+### Hash::get()
+
+`static` Cake\\Utility\\Hash::**get**(array|ArrayAccess $data, $path, $default = null): mixed
+
+`get()` is a simplified version of `extract()`, it only supports direct
+path expressions. Paths with `{n}`, `{s}`, `{*}` or matchers are not
+supported. Use `get()` when you want exactly one value out of an array. If
+a matching path is not found the default value will be returned.
+
+### Hash::extract()
+
+`static` Cake\\Utility\\Hash::**extract**(array|ArrayAccess $data, $path): ArrayAccess|array
+
+`Hash::extract()` supports all expression, and matcher components of
+[Hash Path Syntax](#hash-path-syntax). You can use extract to retrieve data from arrays
+or object implementing `ArrayAccess` interface, along arbitrary paths
+quickly without having to loop through the data structures. Instead you
+use path expressions to qualify which elements you want returned :
+
+``` php
+// Common Usage:
+$users = [
+    ['id' => 1, 'name' => 'mark'],
+    ['id' => 2, 'name' => 'jane'],
+    ['id' => 3, 'name' => 'sally'],
+    ['id' => 4, 'name' => 'jose'],
+];
+$results = Hash::extract($users, '{n}.id');
+// $results equals:
+// [1,2,3,4];
+```
+
+`static` Hash::**insert**(array $data, $path, $values = null): ArrayAccess|array
+
+Inserts `$values` into an array as defined by `$path`:
+
+``` php
+$a = [
+    'pages' => ['name' => 'page']
+];
+$result = Hash::insert($a, 'files', ['name' => 'files']);
+// $result now looks like:
+[
+    [pages] => [
+        [name] => page
+    ]
+    [files] => [
+        [name] => files
+    ]
+]
+```
+
+  You can use paths using `{n}`, `{s}` and `{*}` to insert data into multiple
+points:
+
+``` php
+$users = Hash::insert($users, '{n}.new', 'value');
+```
+
+Attribute matchers work with `insert()` as well:
+
+``` php
+$data = [
+    0 => ['up' => true, 'Item' => ['id' => 1, 'title' => 'first']],
+    1 => ['Item' => ['id' => 2, 'title' => 'second']],
+    2 => ['Item' => ['id' => 3, 'title' => 'third']],
+    3 => ['up' => true, 'Item' => ['id' => 4, 'title' => 'fourth']],
+    4 => ['Item' => ['id' => 5, 'title' => 'fifth']],
+];
+$result = Hash::insert($data, '{n}[up].Item[id=4].new', 9);
+/* $result now looks like:
+    [
+        ['up' => true, 'Item' => ['id' => 1, 'title' => 'first']],
+        ['Item' => ['id' => 2, 'title' => 'second']],
+        ['Item' => ['id' => 3, 'title' => 'third']],
+        ['up' => true, 'Item' => ['id' => 4, 'title' => 'fourth', 'new' => 9]],
+        ['Item' => ['id' => 5, 'title' => 'fifth']],
+    ]
+*/
+```
+
+### Hash::remove()
+
+`static` Cake\\Utility\\Hash::**remove**(array $data, $path): ArrayAccess|array
+
+Removes all elements from an array that match `$path`:
+
+``` php
+$a = [
+    'pages' => ['name' => 'page'],
+    'files' => ['name' => 'files']
+];
+$result = Hash::remove($a, 'files');
+/* $result now looks like:
+    [
+        [pages] => [
+            [name] => page
+        ]
+
+    ]
+*/
+```
+
+Using `{n}`, `{s}` and `{*}` will allow you to remove multiple values at once.
+You can also use attribute matchers with `remove()`:
+
+``` php
+$data = [
+    0 => ['clear' => true, 'Item' => ['id' => 1, 'title' => 'first']],
+    1 => ['Item' => ['id' => 2, 'title' => 'second']],
+    2 => ['Item' => ['id' => 3, 'title' => 'third']],
+    3 => ['clear' => true, 'Item' => ['id' => 4, 'title' => 'fourth']],
+    4 => ['Item' => ['id' => 5, 'title' => 'fifth']],
+];
+$result = Hash::remove($data, '{n}[clear].Item[id=4]');
+/* $result now looks like:
+    [
+        ['clear' => true, 'Item' => ['id' => 1, 'title' => 'first']],
+        ['Item' => ['id' => 2, 'title' => 'second']],
+        ['Item' => ['id' => 3, 'title' => 'third']],
+        ['clear' => true],
+        ['Item' => ['id' => 5, 'title' => 'fifth']],
+    ]
+*/
+```
+
+### Hash::combine()
+
+`static` Cake\\Utility\\Hash::**combine**(array $data, $keyPath, $valuePath = null, $groupPath = null): array
+
+Creates an associative array using a `$keyPath` as the path to build its keys,
+and optionally `$valuePath` as path to get the values. If `$valuePath` is not
+specified, or doesn't match anything, values will be initialized to null.
+You can optionally group the values by what is obtained when following the
+path specified in `$groupPath`:
+
+``` php
+$a = [
+    [
+        'User' => [
+            'id' => 2,
+            'group_id' => 1,
+            'Data' => [
+                'user' => 'mariano.iglesias',
+                'name' => 'Mariano Iglesias'
+            ]
+        ]
+    ],
+    [
+        'User' => [
+            'id' => 14,
+            'group_id' => 2,
+            'Data' => [
+                'user' => 'phpnut',
+                'name' => 'Larry E. Masters'
+            ]
+        ]
+    ],
+];
+
+$result = Hash::combine($a, '{n}.User.id');
+/* $result now looks like:
+    [
+        [2] =>
+        [14] =>
+    ]
+*/
+
+$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.user');
+/* $result now looks like:
+    [
+        [2] => 'mariano.iglesias'
+        [14] => 'phpnut'
+    ]
+*/
+
+$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data');
+/* $result now looks like:
+    [
+        [2] => [
+                [user] => mariano.iglesias
+                [name] => Mariano Iglesias
+        ]
+        [14] => [
+                [user] => phpnut
+                [name] => Larry E. Masters
+        ]
+    ]
+*/
+
+$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.name');
+/* $result now looks like:
+    [
+        [2] => Mariano Iglesias
+        [14] => Larry E. Masters
+    ]
+*/
+
+$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data', '{n}.User.group_id');
+/* $result now looks like:
+    [
+        [1] => [
+                [2] => [
+                        [user] => mariano.iglesias
+                        [name] => Mariano Iglesias
+                ]
+        ]
+        [2] => [
+                [14] => [
+                        [user] => phpnut
+                        [name] => Larry E. Masters
+                ]
+        ]
+    ]
+*/
+
+$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.name', '{n}.User.group_id');
+/* $result now looks like:
+    [
+        [1] => [
+                [2] => Mariano Iglesias
+        ]
+        [2] => [
+                [14] => Larry E. Masters
+        ]
+    ]
+*/
+
+// As of 3.9.0 $keyPath can be null
+$result = Hash::combine($a, null, '{n}.User.Data.name');
+/* $result now looks like:
+    [
+        [0] => Mariano Iglesias
+        [1] => Larry E. Masters
+    ]
+*/
+```
+
+You can provide arrays for both `$keyPath` and `$valuePath`. If you do this,
+the first value will be used as a format string, for values extracted by the
+other paths:
+
+``` php
+$result = Hash::combine(
+    $a,
+    '{n}.User.id',
+    ['%s: %s', '{n}.User.Data.user', '{n}.User.Data.name'],
+    '{n}.User.group_id'
+);
+/* $result now looks like:
+    [
+        [1] => [
+                [2] => mariano.iglesias: Mariano Iglesias
+        ]
+        [2] => [
+                [14] => phpnut: Larry E. Masters
+        ]
+    ]
+*/
+
+$result = Hash::combine(
+    $a,
+    ['%s: %s', '{n}.User.Data.user', '{n}.User.Data.name'],
+    '{n}.User.id'
+);
+/* $result now looks like:
+    [
+        [mariano.iglesias: Mariano Iglesias] => 2
+        [phpnut: Larry E. Masters] => 14
+    ]
+*/
+```
+
+### Hash::format()
+
+`static` Cake\\Utility\\Hash::**format**(array $data, array $paths, $format): array|null
+
+Returns a series of values extracted from an array, formatted with a
+format string:
+
+``` php
+$data = [
+    [
+        'Person' => [
+            'first_name' => 'Nate',
+            'last_name' => 'Abele',
+            'city' => 'Boston',
+            'state' => 'MA',
+            'something' => '42'
+        ]
+    ],
+    [
+        'Person' => [
+            'first_name' => 'Larry',
+            'last_name' => 'Masters',
+            'city' => 'Boondock',
+            'state' => 'TN',
+            'something' => '{0}'
+        ]
+    ],
+    [
+        'Person' => [
+            'first_name' => 'Garrett',
+            'last_name' => 'Woodworth',
+            'city' => 'Venice Beach',
+            'state' => 'CA',
+            'something' => '{1}'
+        ]
+    ]
+];
+
+$res = Hash::format($data, ['{n}.Person.first_name', '{n}.Person.something'], '%2$d, %1$s');
+/*
+[
+    [0] => 42, Nate
+    [1] => 0, Larry
+    [2] => 0, Garrett
+]
+*/
+
+$res = Hash::format($data, ['{n}.Person.first_name', '{n}.Person.something'], '%1$s, %2$d');
+/*
+[
+    [0] => Nate, 42
+    [1] => Larry, 0
+    [2] => Garrett, 0
+]
+*/
+```
+
+### Hash::contains()
+
+`static` Cake\\Utility\\Hash::**contains**(array $data, array $needle): bool
+
+Determines if one Hash or array contains the exact keys and values
+of another:
+
+``` php
+$a = [
+    0 => ['name' => 'main'],
+    1 => ['name' => 'about']
+];
+$b = [
+    0 => ['name' => 'main'],
+    1 => ['name' => 'about'],
+    2 => ['name' => 'contact'],
+    'a' => 'b',
+];
+
+$result = Hash::contains($a, $a);
+// true
+$result = Hash::contains($a, $b);
+// false
+$result = Hash::contains($b, $a);
+// true
+```
+
+### Hash::check()
+
+`static` Cake\\Utility\\Hash::**check**(array $data, string $path = null): bool
+
+Checks if a particular path is set in an array:
+
+``` php
+$set = [
+    'My Index 1' => ['First' => 'The first item']
+];
+$result = Hash::check($set, 'My Index 1.First');
+// $result == true
+
+$result = Hash::check($set, 'My Index 1');
+// $result == true
+
+$set = [
+    'My Index 1' => [
+        'First' => [
+            'Second' => [
+                'Third' => [
+                    'Fourth' => 'Heavy. Nesting.'
+                ]
+            ]
+        ]
+    ]
+];
+$result = Hash::check($set, 'My Index 1.First.Second');
+// $result == true
+
+$result = Hash::check($set, 'My Index 1.First.Second.Third');
+// $result == true
+
+$result = Hash::check($set, 'My Index 1.First.Second.Third.Fourth');
+// $result == true
+
+$result = Hash::check($set, 'My Index 1.First.Seconds.Third.Fourth');
+// $result == false
+```
+
+### Hash::filter()
+
+`static` Cake\\Utility\\Hash::**filter**(array $data, $callback = ['Hash', 'filter']): array
+
+Filters empty elements out of array, excluding '0'. You can also supply a
+custom `$callback` to filter the array elements. The callback should
+return `false` to remove elements from the resulting array:
+
+``` php
+$data = [
+    '0',
+    false,
+    true,
+    0,
+    ['one thing', 'I can tell you', 'is you got to be', false]
+];
+$res = Hash::filter($data);
+
+/* $res now looks like:
+    [
+        [0] => 0
+        [2] => true
+        [3] => 0
+        [4] => [
+                [0] => one thing
+                [1] => I can tell you
+                [2] => is you got to be
+        ]
+    ]
+*/
+```
+
+### Hash::flatten()
+
+`static` Cake\\Utility\\Hash::**flatten**(array $data, string $separator = '.'): array
+
+Collapses a multi-dimensional array into a single dimension:
+
+``` php
+$arr = [
+    [
+        'Post' => ['id' => '1', 'title' => 'First Post'],
+        'Author' => ['id' => '1', 'user' => 'Kyle'],
+    ],
+    [
+        'Post' => ['id' => '2', 'title' => 'Second Post'],
+        'Author' => ['id' => '3', 'user' => 'Crystal'],
+    ],
+];
+$res = Hash::flatten($arr);
+/* $res now looks like:
+    [
+        [0.Post.id] => 1
+        [0.Post.title] => First Post
+        [0.Author.id] => 1
+        [0.Author.user] => Kyle
+        [1.Post.id] => 2
+        [1.Post.title] => Second Post
+        [1.Author.id] => 3
+        [1.Author.user] => Crystal
+    ]
+*/
+```
+
+### Hash::expand()
+
+`static` Cake\\Utility\\Hash::**expand**(array $data, string $separator = '.'): array
+
+Expands an array that was previously flattened with
+`Hash::flatten()`:
+
+``` php
+$data = [
+    '0.Post.id' => 1,
+    '0.Post.title' => First Post,
+    '0.Author.id' => 1,
+    '0.Author.user' => Kyle,
+    '1.Post.id' => 2,
+    '1.Post.title' => Second Post,
+    '1.Author.id' => 3,
+    '1.Author.user' => Crystal,
+];
+$res = Hash::expand($data);
+/* $res now looks like:
+[
+    [
+        'Post' => ['id' => '1', 'title' => 'First Post'],
+        'Author' => ['id' => '1', 'user' => 'Kyle'],
+    ],
+    [
+        'Post' => ['id' => '2', 'title' => 'Second Post'],
+        'Author' => ['id' => '3', 'user' => 'Crystal'],
+    ],
+];
+*/
+```
+
+### Hash::merge()
+
+`static` Cake\\Utility\\Hash::**merge**(array $data, array $merge[, array $n]): array
+
+This function can be thought of as a hybrid between PHP's
+`array_merge` and `array_merge_recursive`. The difference to the two
+is that if an array key contains another array then the function
+behaves recursive (unlike `array_merge`) but does not do if for keys
+containing strings (unlike `array_merge_recursive`).
+
+> [!NOTE]
+> This function will work with an unlimited amount of arguments and
+> typecasts non-array parameters into arrays.
+
+``` php
+$array = [
+    [
+        'id' => '48c2570e-dfa8-4c32-a35e-0d71cbdd56cb',
+        'name' => 'mysql raleigh-workshop-08 < 2008-09-05.sql ',
+        'description' => 'Importing an sql dump',
+    ],
+    [
+        'id' => '48c257a8-cf7c-4af2-ac2f-114ecbdd56cb',
+        'name' => 'pbpaste | grep -i Unpaid | pbcopy',
+        'description' => 'Remove all lines that say "Unpaid".',
+    ]
+];
+$arrayB = 4;
+$arrayC = [0 => "test array", "cats" => "dogs", "people" => 1267];
+$arrayD = ["cats" => "felines", "dog" => "angry"];
+$res = Hash::merge($array, $arrayB, $arrayC, $arrayD);
+
+/* $res now looks like:
+[
+    [0] => [
+            [id] => 48c2570e-dfa8-4c32-a35e-0d71cbdd56cb
+            [name] => mysql raleigh-workshop-08 < 2008-09-05.sql
+            [description] => Importing an sql dump
+    ]
+    [1] => [
+            [id] => 48c257a8-cf7c-4af2-ac2f-114ecbdd56cb
+            [name] => pbpaste | grep -i Unpaid | pbcopy
+            [description] => Remove all lines that say "Unpaid".
+    ]
+    [2] => 4
+    [3] => test array
+    [cats] => felines
+    [people] => 1267
+    [dog] => angry
+]
+*/
+```
+
+### Hash::numeric()
+
+`static` Cake\\Utility\\Hash::**numeric**(array $data): bool
+
+Checks to see if all the values in the array are numeric:
+
+``` php
+$data = ['one'];
+$res = Hash::numeric(array_keys($data));
+// $res is true
+
+$data = [1 => 'one'];
+$res = Hash::numeric($data);
+// $res is false
+```
+
+### Hash::dimensions()
+
+`static` Cake\\Utility\\Hash::**dimensions**(array $data): int
+
+Counts the dimensions of an array. This method will only
+consider the dimension of the first element in the array:
+
+``` php
+$data = ['one', '2', 'three'];
+$result = Hash::dimensions($data);
+// $result == 1
+
+$data = ['1' => '1.1', '2', '3'];
+$result = Hash::dimensions($data);
+// $result == 1
+
+$data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => '3.1.1']];
+$result = Hash::dimensions($data);
+// $result == 2
+
+$data = ['1' => '1.1', '2', '3' => ['3.1' => '3.1.1']];
+$result = Hash::dimensions($data);
+// $result == 1
+
+$data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => ['3.1.1' => '3.1.1.1']]];
+$result = Hash::dimensions($data);
+// $result == 2
+```
+
+### Hash::maxDimensions()
+
+`static` Cake\\Utility\\Hash::**maxDimensions**(array $data): int
+
+Similar to `~Hash::dimensions()`, however this method returns,
+the deepest number of dimensions of any element in the array:
+
+``` php
+$data = ['1' => '1.1', '2', '3' => ['3.1' => '3.1.1']];
+$result = Hash::maxDimensions($data);
+// $result == 2
+
+$data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => ['3.1.1' => '3.1.1.1']]];
+$result = Hash::maxDimensions($data);
+// $result == 3
+```
+
+### Hash::map()
+
+`static` Cake\\Utility\\Hash::**map**(array $data, $path, $function): array
+
+Creates a new array, by extracting `$path`, and mapping `$function`
+across the results. You can use both expression and matching elements with
+this method:
+
+``` php
+// Call the noop function $this->noop() on every element of $data
+$result = Hash::map($data, "{n}", [$this, 'noop']);
+
+public function noop(array $array)
+{
+    // Do stuff to array and return the result
+    return $array;
+}
+```
+
+### Hash::reduce()
+
+`static` Cake\\Utility\\Hash::**reduce**(array $data, $path, $function): mixed
+
+Creates a single value, by extracting `$path`, and reducing the extracted
+results with `$function`. You can use both expression and matching elements
+with this method.
+
+### Hash::apply()
+
+`static` Cake\\Utility\\Hash::**apply**(array $data, $path, $function): mixed
+
+Apply a callback to a set of extracted values using `$function`. The function
+will get the extracted values as the first argument:
+
+``` php
+$data = [
+    ['date' => '01-01-2016', 'booked' => true],
+    ['date' => '01-01-2016', 'booked' => false],
+    ['date' => '02-01-2016', 'booked' => true]
+];
+$result = Hash::apply($data, '{n}[booked=true].date', 'array_count_values');
+/* $result now looks like:
+    [
+        '01-01-2016' => 1,
+        '02-01-2016' => 1,
+    ]
+*/
+```
+
+### Hash::sort()
+
+`static` Cake\\Utility\\Hash::**sort**(array $data, $path, $dir, $type = 'regular'): array
+
+Sorts an array by any value, determined by a [Hash Path Syntax](#hash-path-syntax)
+Only expression elements are supported by this method:
+
+``` php
+$a = [
+    0 => ['Person' => ['name' => 'Jeff']],
+    1 => ['Shirt' => ['color' => 'black']]
+];
+$result = Hash::sort($a, '{n}.Person.name', 'asc');
+/* $result now looks like:
+    [
+        [0] => [
+                [Shirt] => [
+                        [color] => black
+                ]
+        ]
+        [1] => [
+                [Person] => [
+                        [name] => Jeff
+                ]
+        ]
+    ]
+*/
+```
+
+`$dir` can be either `asc` or `desc`. `$type`
+can be one of the following values:
+
+- `regular` for regular sorting.
+- `numeric` for sorting values as their numeric equivalents.
+- `string` for sorting values as their string value.
+- `natural` for sorting values in a human friendly way. Will
+  sort `foo10` below `foo2` as an example.
+
+### Hash::diff()
+
+`static` Cake\\Utility\\Hash::**diff**(array $data, array $compare): array
+
+Computes the difference between two arrays:
+
+``` php
+$a = [
+    0 => ['name' => 'main'],
+    1 => ['name' => 'about']
+];
+$b = [
+    0 => ['name' => 'main'],
+    1 => ['name' => 'about'],
+    2 => ['name' => 'contact']
+];
+
+$result = Hash::diff($a, $b);
+/* $result now looks like:
+    [
+        [2] => [
+                [name] => contact
+        ]
+    ]
+*/
+```
+
+### Hash::mergeDiff()
+
+`static` Cake\\Utility\\Hash::**mergeDiff**(array $data, array $compare): array
+
+This function merges two arrays and pushes the differences in
+data to the bottom of the resultant array.
+
+**Example 1**
+:
+
+``` php
+$array1 = ['ModelOne' => ['id' => 1001, 'field_one' => 'a1.m1.f1', 'field_two' => 'a1.m1.f2']];
+$array2 = ['ModelOne' => ['id' => 1003, 'field_one' => 'a3.m1.f1', 'field_two' => 'a3.m1.f2', 'field_three' => 'a3.m1.f3']];
+$res = Hash::mergeDiff($array1, $array2);
+
+/* $res now looks like:
+    [
+        [ModelOne] => [
+                [id] => 1001
+                [field_one] => a1.m1.f1
+                [field_two] => a1.m1.f2
+                [field_three] => a3.m1.f3
+            ]
+    ]
+*/
+```
+
+**Example 2**
+:
+
+``` php
+$array1 = ["a" => "b", 1 => 20938, "c" => "string"];
+$array2 = ["b" => "b", 3 => 238, "c" => "string", ["extra_field"]];
+$res = Hash::mergeDiff($array1, $array2);
+/* $res now looks like:
+    [
+        [a] => b
+        [1] => 20938
+        [c] => string
+        [b] => b
+        [3] => 238
+        [4] => [
+                [0] => extra_field
+        ]
+    ]
+*/
+```
+
+### Hash::normalize()
+
+`static` Cake\\Utility\\Hash::**normalize**(array $data, $assoc = true, $default = null): array
+
+Normalizes an array. If `$assoc` is `true`, the resulting array will be
+normalized to be an associative array. Numeric keys with values, will be
+converted to string keys with `$default` values. Normalizing an array,
+makes using the results with `Hash::merge()` easier:
+
+``` php
+$a = ['Tree', 'CounterCache',
+    'Upload' => [
+        'folder' => 'products',
+        'fields' => ['image_1_id', 'image_2_id']
+    ]
+];
+$result = Hash::normalize($a);
+/* $result now looks like:
+    [
+        [Tree] => null
+        [CounterCache] => null
+        [Upload] => [
+                [folder] => products
+                [fields] => [
+                        [0] => image_1_id
+                        [1] => image_2_id
+                ]
+        ]
+    ]
+*/
+
+$b = [
+    'Cacheable' => ['enabled' => false],
+    'Limit',
+    'Bindable',
+    'Validator',
+    'Transactional',
+];
+$result = Hash::normalize($b);
+/* $result now looks like:
+    [
+        [Cacheable] => [
+                [enabled] => false
+        ]
+
+        [Limit] => null
+        [Bindable] => null
+        [Validator] => null
+        [Transactional] => null
+    ]
+*/
+```
+
+::: info Changed in version 4.5.0
+The `$default` parameter was added.
+:::
+
+### Hash::nest()
+
+`static` Cake\\Utility\\Hash::**nest**(array $data, array $options = []): array
+
+Takes a flat array set, and creates a nested, or threaded data structure.
+
+**Options:**
+
+- `children` The key name to use in the result set for children. Defaults
+  to 'children'.
+- `idPath` The path to a key that identifies each entry. Should be
+  compatible with `Hash::extract()`. Defaults to `{n}.$alias.id`
+- `parentPath` The path to a key that identifies the parent of each entry.
+  Should be compatible with `Hash::extract()`. Defaults to `{n}.$alias.parent_id`
+- `root` The id of the desired top-most result.
+
+For example, if you had the following array of data:
+
+``` php
+$data = [
+    ['ThreadPost' => ['id' => 1, 'parent_id' => null]],
+    ['ThreadPost' => ['id' => 2, 'parent_id' => 1]],
+    ['ThreadPost' => ['id' => 3, 'parent_id' => 1]],
+    ['ThreadPost' => ['id' => 4, 'parent_id' => 1]],
+    ['ThreadPost' => ['id' => 5, 'parent_id' => 1]],
+    ['ThreadPost' => ['id' => 6, 'parent_id' => null]],
+    ['ThreadPost' => ['id' => 7, 'parent_id' => 6]],
+    ['ThreadPost' => ['id' => 8, 'parent_id' => 6]],
+    ['ThreadPost' => ['id' => 9, 'parent_id' => 6]],
+    ['ThreadPost' => ['id' => 10, 'parent_id' => 6]]
+];
+
+$result = Hash::nest($data, ['root' => 6]);
+/* $result now looks like:
+    [
+        (int) 0 => [
+            'ThreadPost' => [
+                'id' => (int) 6,
+                'parent_id' => null
+            ],
+            'children' => [
+                (int) 0 => [
+                    'ThreadPost' => [
+                        'id' => (int) 7,
+                        'parent_id' => (int) 6
+                    ],
+                    'children' => []
+                ],
+                (int) 1 => [
+                    'ThreadPost' => [
+                        'id' => (int) 8,
+                        'parent_id' => (int) 6
+                    ],
+                    'children' => []
+                ],
+                (int) 2 => [
+                    'ThreadPost' => [
+                        'id' => (int) 9,
+                        'parent_id' => (int) 6
+                    ],
+                    'children' => []
+                ],
+                (int) 3 => [
+                    'ThreadPost' => [
+                        'id' => (int) 10,
+                        'parent_id' => (int) 6
+                    ],
+                    'children' => []
+                ]
+            ]
+        ]
+    ]
+    */
+```
diff --git a/docs/en/core-libraries/httpclient.md b/docs/en/core-libraries/httpclient.md
new file mode 100644
index 0000000000..85536682e7
--- /dev/null
+++ b/docs/en/core-libraries/httpclient.md
@@ -0,0 +1,611 @@
+# Http Client
+
+`class` Cake\\Http\\**Client**(mixed $config = [])
+
+CakePHP includes a PSR-18 compliant HTTP client which can be used for
+making requests. It is a great way to communicate with webservices, and
+remote APIs.
+
+## Doing Requests
+
+Doing requests is simple and straight forward. Doing a GET request looks like:
+
+``` php
+use Cake\Http\Client;
+
+$http = new Client();
+
+// Simple get
+$response = $http->get('http://example.com/test.html');
+
+// Simple get with querystring
+$response = $http->get('http://example.com/search', ['q' => 'widget']);
+
+// Simple get with querystring & additional headers
+$response = $http->get('http://example.com/search', ['q' => 'widget'], [
+  'headers' => ['X-Requested-With' => 'XMLHttpRequest'],
+]);
+```
+
+Doing POST and PUT requests is equally simple:
+
+``` php
+// Send a POST request with application/x-www-form-urlencoded encoded data
+$http = new Client();
+$response = $http->post('http://example.com/posts/add', [
+  'title' => 'testing',
+  'body' => 'content in the post',
+]);
+
+// Send a PUT request with application/x-www-form-urlencoded encoded data
+$response = $http->put('http://example.com/posts/add', [
+  'title' => 'testing',
+  'body' => 'content in the post',
+]);
+
+// Other methods as well.
+$http->delete(/* ... */);
+$http->head(/* ... */);
+$http->patch(/* ... */);
+```
+
+If you have created a PSR-7 request object you can send it using
+`sendRequest()`:
+
+``` php
+use Cake\Http\Client;
+use Cake\Http\Client\Request as ClientRequest;
+
+$request = new ClientRequest(
+    'http://example.com/search',
+    ClientRequest::METHOD_GET
+);
+$http = new Client();
+$response = $http->sendRequest($request);
+```
+
+## Creating Multipart Requests with Files
+
+You can include files in request bodies by including a filehandle in the array:
+
+``` php
+$http = new Client();
+$response = $http->post('http://example.com/api', [
+  'image' => fopen('/path/to/a/file', 'r'),
+]);
+```
+
+The filehandle will be read until its end; it will not be rewound before being read.
+
+### Building Multipart Request Bodies
+
+There may be times when you need to build a request body in a very specific way.
+In these situations you can often use `Cake\Http\Client\FormData` to craft
+the specific multipart HTTP request you want:
+
+``` php
+use Cake\Http\Client\FormData;
+
+$data = new FormData();
+
+// Create an XML part
+$xml = $data->newPart('xml', $xmlString);
+// Set the content type.
+$xml->type('application/xml');
+$data->add($xml);
+
+// Create a file upload with addFile()
+// This will append the file to the form data as well.
+$file = $data->addFile('upload', fopen('/some/file.txt', 'r'));
+$file->contentId('abc123');
+$file->disposition('attachment');
+
+// Send the request.
+$response = $http->post(
+    'http://example.com/api',
+    (string)$data,
+    ['headers' => ['Content-Type' => $data->contentType()]]
+);
+```
+
+## Sending Request Bodies
+
+When dealing with REST APIs you often need to send request bodies that are not
+form encoded. Http\Client exposes this through the type option:
+
+``` php
+// Send a JSON request body.
+$http = new Client();
+$response = $http->post(
+  'http://example.com/tasks',
+  json_encode($data),
+  ['type' => 'json']
+);
+```
+
+The `type` key can either be a one of 'json', 'xml' or a full mime type.
+When using the `type` option, you should provide the data as a string. If you're
+doing a GET request that needs both querystring parameters and a request body
+you can do the following:
+
+``` php
+// Send a JSON body in a GET request with query string parameters.
+$http = new Client();
+$response = $http->get(
+  'http://example.com/tasks',
+  ['q' => 'test', '_content' => json_encode($data)],
+  ['type' => 'json']
+);
+```
+
+
+
+## Request Method Options
+
+Each HTTP method takes an `$options` parameter which is used to provide
+addition request information. The following keys can be used in `$options`:
+
+- `headers` - Array of additional headers
+- `cookie` - Array of cookies to use.
+- `proxy` - Array of proxy information.
+- `auth` - Array of authentication data, the `type` key is used to delegate to
+  an authentication strategy. By default Basic auth is used.
+- `ssl_verify_peer` - defaults to `true`. Set to `false` to disable SSL certification
+  verification (not recommended).
+- `ssl_verify_peer_name` - defaults to `true`. Set to `false` to disable
+  host name verification when verifying SSL certificates (not recommended).
+- `ssl_verify_depth` - defaults to 5. Depth to traverse in the CA chain.
+- `ssl_verify_host` - defaults to `true`. Validate the SSL certificate against the host name.
+- `ssl_cafile` - defaults to built in cafile. Overwrite to use custom CA bundles.
+- `timeout` - Duration to wait before timing out in seconds.
+- `type` - Send a request body in a custom content type. Requires `$data` to
+  either be a string, or the `_content` option to be set when doing GET
+  requests.
+- `redirect` - Number of redirects to follow. Defaults to `false`.
+- `curl` - An array of additional curl options (if the curl adapter is used),
+  for example, `[CURLOPT_SSLKEY => 'key.pem']`.
+
+The options parameter is always the 3rd parameter in each of the HTTP methods.
+They can also be used when constructing `Client` to create
+[scoped clients](#http_client_scoped_client).
+
+## Authentication
+
+`Cake\Http\Client` supports a few different authentication systems. Different
+authentication strategies can be added by developers. Auth strategies are called
+before the request is sent, and allow headers to be added to the request
+context.
+
+### Using Basic Authentication
+
+An example of basic authentication:
+
+``` php
+$http = new Client();
+$response = $http->get('http://example.com/profile/1', [], [
+  'auth' => ['username' => 'mark', 'password' => 'secret'],
+]);
+```
+
+By default `Cake\Http\Client` will use basic authentication if there is no
+`'type'` key in the auth option.
+
+### Using Digest Authentication
+
+An example of basic authentication:
+
+``` php
+$http = new Client();
+$response = $http->get('http://example.com/profile/1', [], [
+    'auth' => [
+        'type' => 'digest',
+        'username' => 'mark',
+        'password' => 'secret',
+        'realm' => 'myrealm',
+        'nonce' => 'onetimevalue',
+        'qop' => 1,
+        'opaque' => 'someval',
+    ],
+]);
+```
+
+By setting the 'type' key to 'digest', you tell the authentication subsystem to
+use digest authentication. Digest authentication supports the following
+algorithms:
+
+- MD5
+- SHA-256
+- SHA-512-256
+- MD5-sess
+- SHA-256-sess
+- SHA-512-256-sess
+
+The algorithm will be automatically chosen based on the server challenge.
+
+### OAuth 1 Authentication
+
+Many modern web-services require OAuth authentication to access their APIs.
+The included OAuth authentication assumes that you already have your consumer
+key and consumer secret:
+
+``` php
+$http = new Client();
+$response = $http->get('http://example.com/profile/1', [], [
+    'auth' => [
+        'type' => 'oauth',
+        'consumerKey' => 'bigkey',
+        'consumerSecret' => 'secret',
+        'token' => '...',
+        'tokenSecret' => '...',
+        'realm' => 'tickets',
+    ],
+]);
+```
+
+### OAuth 2 Authentication
+
+Because OAuth2 is often a single header, there is not a specialized
+authentication adapter. Instead you can create a client with the access token:
+
+``` php
+$http = new Client([
+    'headers' => ['Authorization' => 'Bearer ' . $accessToken],
+]);
+$response = $http->get('https://example.com/api/profile/1');
+```
+
+### Proxy Authentication
+
+Some proxies require authentication to use them. Generally this authentication
+is Basic, but it can be implemented by any authentication adapter. By default
+Http\Client will assume Basic authentication, unless the type key is set:
+
+``` php
+$http = new Client();
+$response = $http->get('http://example.com/test.php', [], [
+    'proxy' => [
+        'username' => 'mark',
+        'password' => 'testing',
+        'proxy' => '127.0.0.1:8080',
+    ],
+]);
+```
+
+The second proxy parameter must be a string with an IP or a domain without
+protocol. The username and password information will be passed through the
+request headers, while the proxy string will be passed through
+[stream_context_create()](https://php.net/manual/en/function.stream-context-create.php).
+
+
+
+## Creating Scoped Clients
+
+Having to re-type the domain name, authentication and proxy settings can become
+tedious & error prone. To reduce the chance for mistake and relieve some of the
+tedium, you can create scoped clients:
+
+``` php
+// Create a scoped client.
+$http = new Client([
+    'host' => 'api.example.com',
+    'scheme' => 'https',
+    'auth' => ['username' => 'mark', 'password' => 'testing'],
+]);
+
+// Do a request to api.example.com
+$response = $http->get('/test.php');
+```
+
+If your scoped client only needs information from the URL you can use
+`createFromUrl()`:
+
+``` php
+$http = Client::createFromUrl('https://api.example.com/v1/test');
+```
+
+The above would create a client instance with the `protocol`, `host`, and
+`basePath` options set.
+
+The following information can be used when creating a scoped client:
+
+- host
+- basePath
+- scheme
+- proxy
+- auth
+- port
+- cookies
+- timeout
+- ssl_verify_peer
+- ssl_verify_depth
+- ssl_verify_host
+
+Any of these options can be overridden by specifying them when doing requests.
+host, scheme, proxy, port are overridden in the request URL:
+
+``` php
+// Using the scoped client we created earlier.
+$response = $http->get('http://foo.com/test.php');
+```
+
+The above will replace the domain, scheme, and port. However, this request will
+continue using all the other options defined when the scoped client was created.
+See [Http Client Request Options](#http_client_request_options) for more information on the options
+supported.
+
+## Setting and Managing Cookies
+
+Http\Client can also accept cookies when making requests. In addition to
+accepting cookies, it will also automatically store valid cookies set in
+responses. Any response with cookies, will have them stored in the originating
+instance of Http\Client. The cookies stored in a Client instance are
+automatically included in future requests to domain + path combinations that
+match:
+
+``` php
+$http = new Client([
+    'host' => 'cakephp.org'
+]);
+
+// Do a request that sets some cookies
+$response = $http->get('/');
+
+// Cookies from the first request will be included
+// by default.
+$response2 = $http->get('/changelogs');
+```
+
+You can always override the auto-included cookies by setting them in the
+request's `$options` parameters:
+
+``` php
+// Replace a stored cookie with a custom value.
+$response = $http->get('/changelogs', [], [
+    'cookies' => ['sessionid' => '123abc'],
+]);
+```
+
+You can add cookie objects to the client after creating it using the `addCookie()`
+method:
+
+``` php
+use Cake\Http\Cookie\Cookie;
+
+$http = new Client([
+    'host' => 'cakephp.org'
+]);
+$http->addCookie(new Cookie('session', 'abc123'));
+```
+
+## Client Events
+
+`Client` will emit events when requests are sent. The
+`HttpClient.beforeSend` event is fired before a request is sent, and
+`HttpClient.afterSend` is fired after a request is sent. You can modify the
+request, or set a response in a `beforeSend` listener. The `afterSend` event
+is triggered for all requests, even those that have their responses set by
+a `beforeSend` event.
+
+
+
+## Response Objects
+
+`class` Cake\\Http\\Client\\**Response**
+
+Response objects have a number of methods for inspecting the response data.
+
+### Reading Response Bodies
+
+You read the entire response body as a string:
+
+``` php
+// Read the entire response as a string.
+$response->getStringBody();
+```
+
+You can also access the stream object for the response and use its methods:
+
+``` php
+// Get a Psr\Http\Message\StreamInterface containing the response body
+$stream = $response->getBody();
+
+// Read a stream 100 bytes at a time.
+while (!$stream->eof()) {
+    echo $stream->read(100);
+}
+```
+
+
+
+### Reading JSON and XML Response Bodies
+
+Since JSON and XML responses are commonly used, response objects provide a way
+to use accessors to read decoded data. JSON data is decoded into an array, while
+XML data is decoded into a `SimpleXMLElement` tree:
+
+``` php
+// Get some XML
+$http = new Client();
+$response = $http->get('http://example.com/test.xml');
+$xml = $response->getXml();
+
+// Get some JSON
+$http = new Client();
+$response = $http->get('http://example.com/test.json');
+$json = $response->getJson();
+```
+
+The decoded response data is stored in the response object, so accessing it
+multiple times has no additional cost.
+
+### Accessing Response Headers
+
+You can access headers through a few different methods. Header names are always
+treated as case-insensitive values when accessing them through methods:
+
+``` php
+// Get all the headers as an associative array.
+$response->getHeaders();
+
+// Get a single header as an array.
+$response->getHeader('content-type');
+
+// Get a header as a string
+$response->getHeaderLine('content-type');
+
+// Get the response encoding
+$response->getEncoding();
+```
+
+### Accessing Cookie Data
+
+You can read cookies with a few different methods depending on how much
+data you need about the cookies:
+
+``` php
+// Get all cookies (full data)
+$response->getCookies();
+
+// Get a single cookie's value.
+$response->getCookie('session_id');
+
+// Get a the complete data for a single cookie
+// includes value, expires, path, httponly, secure keys.
+$response->getCookieData('session_id');
+```
+
+### Checking the Status Code
+
+Response objects provide a few methods for checking status codes:
+
+``` php
+// Was the response a 20x
+$response->isOk();
+
+// Was the response a 30x
+$response->isRedirect();
+
+// Get the status code
+$response->getStatusCode();
+```
+
+## Changing Transport Adapters
+
+By default `Http\Client` will prefer using a `curl` based transport adapter.
+If the curl extension is not available a stream based adapter will be used
+instead. You can force select a transport adapter using a constructor option:
+
+``` php
+use Cake\Http\Client\Adapter\Stream;
+
+$http = new Client(['adapter' => Stream::class]);
+```
+
+## Events
+
+The HTTP client triggers couple of events before and after sending a request
+which allows you to modify either the request or response or do other tasks like
+caching, logging etc.
+
+### HttpClient.beforeSend
+
+``` php
+// Somewhere before calling one of the HTTP client's methods which makes a request
+$http->getEventManager()->on(
+    'HttpClient.beforeSend',
+    function (
+        \Cake\Http\Client\ClientEvent $event,
+        \Cake\Http\Client\Request $request,
+        array $adapterOptions,
+        int $redirects
+    ) {
+        // Modify the request
+        $event->setRequest(....);
+        // Modify the adapter options
+        $event->setAdapterOptions(....);
+
+        // Skip making the actual request by returning a response.
+        // You can use $event->setResult($response) to achieve the same.
+        return new \Cake\Http\Client\Response(body: 'something');
+    }
+);
+```
+
+### HttpClient.afterSend
+
+``` php
+// Somewhere before calling one of the HTTP client's methods which makes a request
+$http->getEventManager()->on(
+    'HttpClient.afterSend',
+    function (
+        \Cake\Http\Client\ClientEvent $event,
+        \Cake\Http\Client\Request $request,
+        array $adapterOptions,
+        int $redirects,
+        bool $requestSent // Indicates whether the request was actually sent
+                          // or response returned from ``beforeSend`` event
+    ) {
+        // Get the response
+        $response = $event->getResponse();
+
+        // Return a new/modified response.
+        // You can use $event->setResult($response) to achieve the same.
+        return new \Cake\Http\Client\Response(body: 'something');
+    }
+);
+```
+
+
+
+## Testing
+
+In tests you will often want to create mock responses to external APIs. You can
+use the `HttpClientTrait` to define responses to the requests your application
+is making:
+
+``` php
+use Cake\Http\TestSuite\HttpClientTrait;
+use Cake\TestSuite\TestCase;
+
+class CartControllerTests extends TestCase
+{
+    use HttpClientTrait;
+
+    public function testCheckout()
+    {
+        // Mock a POST request that will be made.
+        $this->mockClientPost(
+            'https://example.com/process-payment',
+            $this->newClientResponse(200, [], json_encode(['ok' => true]))
+        );
+        $this->post("/cart/checkout");
+        // Do assertions.
+    }
+}
+```
+
+There are methods to mock the most commonly used HTTP methods:
+
+``` php
+$this->mockClientGet(/* ... */);
+$this->mockClientPatch(/* ... */);
+$this->mockClientPost(/* ... */);
+$this->mockClientPut(/* ... */);
+$this->mockClientDelete(/* ... */);
+```
+
+### Response::newClientResponse()
+
+`method` Cake\\Http\\TestSuite\\Response::**newClientResponse**(int $code = 200, array $headers = [], string $body = '')
+
+As seen above you can use the `newClientResponse()` method to create responses
+for the requests your application will make. The headers need to be a list of
+strings:
+
+``` php
+$headers = [
+    'Content-Type: application/json',
+    'Connection: close',
+];
+$response = $this->newClientResponse(200, $headers, $body)
+```
diff --git a/docs/en/core-libraries/inflector.md b/docs/en/core-libraries/inflector.md
new file mode 100644
index 0000000000..a22af1b32d
--- /dev/null
+++ b/docs/en/core-libraries/inflector.md
@@ -0,0 +1,261 @@
+# Inflector
+
+`class` Cake\\Utility\\**Inflector**
+
+The Inflector class takes a string and can manipulate it to handle word
+variations such as pluralization or camelizing and is normally accessed
+statically. Example:
+`Inflector::pluralize('example')` returns "examples".
+
+You can try out the inflections online at [inflector.cakephp.org](https://inflector.cakephp.org/) or [sandbox.dereuromark.de](https://sandbox.dereuromark.de/sandbox/inflector).
+
+
+
+## Summary of Inflector Methods and Their Output
+
+Quick summary of the Inflector built-in methods and the results they output
+when provided a multi-word argument:
+
+
+++++
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
MethodArgumentOutput
pluralize()BigAppleBigApples
big_applebig_apples
singularize()BigApplesBigApple
big_applesbig_apple
camelize()big_applesBigApples
big appleBigApple
underscore()BigApplesbig_apples
Big Applesbig apples
humanize()big_applesBig Apples
bigAppleBigApple
classify()big_applesBigApple
big appleBigApple
dasherize()BigApplesbig-apples
big applebig apple
tableize()BigApplebig_apples
Big Applebig apples
variable()big_applebigApple
big applesbigApples
+ +## Creating Plural & Singular Forms + +### Inflector::singularize() + +`static` Cake\\Utility\\Inflector::**singularize**($singular): string + +### Inflector::pluralize() + +`static` Cake\\Utility\\Inflector::**pluralize**($singular): string + +Both `pluralize` and `singularize()` work on most English nouns. If you need +to support other languages, you can use [Inflection Configuration](#inflection-configuration) to +customize the rules used: + +``` php +// Apples +echo Inflector::pluralize('Apple'); +``` + +> [!NOTE] +> `pluralize()` should not be used on a noun that is already in its plural form. + +``` php +// Person +echo Inflector::singularize('People'); +``` + +> [!NOTE] +> `singularize()` should not be used on a noun that is already in its singular form. + +## Creating CamelCase and under_scored Forms + +### Inflector::camelize() + +`static` Cake\\Utility\\Inflector::**camelize**($underscored): string + +### Inflector::underscore() + +`static` Cake\\Utility\\Inflector::**underscore**($camelCase): string + +These methods are useful when creating class names, or property names: + +``` php +// ApplePie +Inflector::camelize('Apple_pie') + +// apple_pie +Inflector::underscore('ApplePie'); +``` + +It should be noted that underscore will only convert camelCase formatted words. +Words that contains spaces will be lower-cased, but will not contain an +underscore. + +## Creating Human Readable Forms + +### Inflector::humanize() + +`static` Cake\\Utility\\Inflector::**humanize**($underscored): string + +This method is useful when converting underscored forms into "Title Case" forms +for human readable values: + +``` php +// Apple Pie +Inflector::humanize('apple_pie'); +``` + +## Creating Table and Class Name Forms + +### Inflector::classify() + +`static` Cake\\Utility\\Inflector::**classify**($underscored): string + +### Inflector::dasherize() + +`static` Cake\\Utility\\Inflector::**dasherize**($dashed): string + +### Inflector::tableize() + +`static` Cake\\Utility\\Inflector::**tableize**($camelCase): string + +When generating code, or using CakePHP's conventions you may need to inflect +table names or class names: + +``` php +// UserProfileSetting +Inflector::classify('user_profile_settings'); + +// user-profile-setting +Inflector::dasherize('UserProfileSetting'); + +// user_profile_settings +Inflector::tableize('UserProfileSetting'); +``` + +## Creating Variable Names + +### Inflector::variable() + +`static` Cake\\Utility\\Inflector::**variable**($underscored): string + +Variable names are often useful when doing meta-programming tasks that involve +generating code or doing work based on conventions: + +``` php +// applePie +Inflector::variable('apple_pie'); +``` + + + +## Inflection Configuration + +CakePHP's naming conventions can be really nice - you can name your database +table `big_boxes`, your model `BigBoxes`, your controller +`BigBoxesController`, and everything just works together automatically. The +way CakePHP knows how to tie things together is by *inflecting* the words +between their singular and plural forms. + +There are occasions (especially for our non-English speaking friends) where you +may run into situations where CakePHP's inflector (the class that pluralizes, +singularizes, camelCases, and under_scores) might not work as you'd like. If +CakePHP won't recognize your Foci or Fish, you can tell CakePHP about your +special cases. + +### Loading Custom Inflections + +### Inflector::rules() + +`static` Cake\\Utility\\Inflector::**rules**($type, $rules, $reset = false): void + +Define new inflection and transliteration rules for Inflector to use. Often, +this method is used in your **config/bootstrap.php**: + +``` php +Inflector::rules('singular', ['/^(bil)er$/i' => '\1', '/^(inflec|contribu)tors$/i' => '\1ta']); +Inflector::rules('uninflected', ['singulars']); +Inflector::rules('irregular', ['phylum' => 'phyla']); // The key is singular form, value is plural form +``` + +The supplied rules will be merged into the respective inflection sets defined in +`Cake/Utility/Inflector`, with the added rules taking precedence over the core +rules. You can use `Inflector::reset()` to clear rules and restore the +original Inflector state. diff --git a/docs/en/core-libraries/internationalization-and-localization.md b/docs/en/core-libraries/internationalization-and-localization.md new file mode 100644 index 0000000000..b9d0842ecd --- /dev/null +++ b/docs/en/core-libraries/internationalization-and-localization.md @@ -0,0 +1,751 @@ +# Internationalization & Localization + +One of the best ways for an application to reach a larger audience is to cater +to multiple languages. This can often prove to be a daunting task, but the +internationalization and localization features in CakePHP make it much easier. + +First, it's important to understand some terminology. *Internationalization* +refers to the ability of an application to be localized. The term *localization* +refers to the adaptation of an application to meet specific language (or +culture) requirements (i.e. a "locale"). Internationalization and localization +are often abbreviated as i18n and l10n respectively; 18 and 10 are the number +of characters between the first and last character. + +## Setting Up Translations + +There are only a few steps to go from a single-language application to a +multi-lingual application, the first of which is to make use of the +`__()` function in your code. Below is an example of some code for a +single-language application: + +``` html +

Popular Articles

+``` + +To internationalize your code, all you need to do is to wrap strings in +`__()` like so: + +``` html +

+``` + +Doing nothing else, these two code examples are functionally identical - they +will both send the same content to the browser. The `__()` function +will translate the passed string if a translation is available, or return it +unmodified. + +### Language Files + +Translations can be made available by using language files stored in the +application. The default format for CakePHP translation files is the +[Gettext](https://en.wikipedia.org/wiki/Gettext) format. Files need to be +placed under **resources/locales/** and within this directory, there should be a +subfolder for each language the application needs to support: + + resources/ + locales/ + en_US/ + default.po + en_GB/ + default.po + validation.po + es/ + default.po + +The default domain is 'default', therefore the locale folder should at least +contain the **default.po** file as shown above. A domain refers to any arbitrary +grouping of translation messages. When no group is used, then the default group +is selected. + +The core strings messages extracted from the CakePHP library can be stored +separately in a file named **cake.po** in **resources/locales/**. +The [CakePHP localized library](https://github.com/cakephp/localized) houses +translations for the client-facing translated strings in the core (the cake +domain). To use these files, link or copy them into their expected location: +**resources/locales/\/cake.po**. If your locale is incomplete or incorrect, +please submit a PR in this repository to fix it. + +Plugins can also contain translation files, the convention is to use the +`under_scored` version of the plugin name as the domain for the translation +messages: + + MyPlugin/ + resources/ + locales/ + fr/ + my_plugin.po + additional.po + de/ + my_plugin.po + +Translation folders can be the two or three letter ISO code of the language or +the full ICU locale name such as `fr_FR`, `es_AR`, `da_DK` which contains +both the language and the country where it is spoken. + +See for the full list of locales. + +::: info Changed in version 4.5.0 +As of 4.5.0 plugins can contain multiple translation domains. Use `MyPlugin.additional` to reference plugin domains. +::: + +An example translation file could look like this: + +``` pot +msgid "My name is {0}" +msgstr "Je m'appelle {0}" + +msgid "I'm {0,number} years old" +msgstr "J'ai {0,number} ans" +``` + +> [!NOTE] +> Translations are cached - Make sure that you always clear the cache after +> making changes to translations! You can either use the +> [cache tool](../console-commands/cache) and run for example +> `bin/cake cache clear _cake_core_`, or manually clear the `tmp/cache/persistent` +> folder (if using file based caching). + +### Extract Pot Files with I18n Shell + +To create the pot files from \_\_() and other internationalized types of +messages that can be found in the application code, you can use the i18n command. +Please read the [following chapter](../console-commands/i18n) to +learn more. + +### Setting the Default Locale + +The default locale can be set in your **config/app.php** file by setting +`App.defaultLocale`: + +``` text +'App' => [ + ... + 'defaultLocale' => env('APP_DEFAULT_LOCALE', 'en_US'), + ... +] +``` + +This will control several aspects of the application, including the default +translations language, the date format, number format and currency whenever any +of those is displayed using the localization libraries that CakePHP provides. + +### Changing the Locale at Runtime + +To change the language for translated strings you can call this method: + +``` php +use Cake\I18n\I18n; + +I18n::setLocale('de_DE'); +``` + +This will also change how numbers and dates are formatted when using one of the +localization tools. + +## Using Translation Functions + +CakePHP provides several functions that will help you internationalize your +application. The most frequently used one is `__()`. This function +is used to retrieve a single translation message or return the same string if no +translation was found: + +``` text +echo __('Popular Articles'); +``` + +If you need to group your messages, for example, translations inside a plugin, +you can use the `__d()` function to fetch messages from another +domain: + +``` text +echo __d('my_plugin', 'Trending right now'); +``` + +> [!NOTE] +> If you want to translate plugins that are vendor namespaced, you must use +> the domain string `vendor/plugin_name`. But the related language file +> will become `plugins///resources/locales//plugin_name.po` +> inside your plugin folder. + +Sometimes translations strings can be ambiguous for people translating them. +This can happen if two strings are identical but refer to different things. For +example, 'letter' has multiple meanings in English. To solve that problem, you +can use the `__x()` function: + +``` text +echo __x('written communication', 'He read the first letter'); + +echo __x('alphabet learning', 'He read the first letter'); +``` + +The first argument is the context of the message and the second is the message +to be translated. + +``` pot +msgctxt "written communication" +msgid "He read the first letter" +msgstr "Er las den ersten Brief" +``` + +### Using Variables in Translation Messages + +Translation functions allow you to interpolate variables into the messages using +special markers defined in the message itself or in the translated string: + +``` text +echo __("Hello, my name is {0}, I'm {1} years old", ['Sara', 12]); +``` + +Markers are numeric, and correspond to the keys in the passed array. You can +also pass variables as independent arguments to the function: + +``` text +echo __("Small step for {0}, Big leap for {1}", 'Man', 'Humanity'); +``` + +All translation functions support placeholder replacements: + +``` text +__d('validation', 'The field {0} cannot be left empty', 'Name'); + +__x('alphabet', 'He read the letter {0}', 'Z'); +``` + +The `'` (single quote) character acts as an escape code in translation +messages. Any variables between single quotes will not be replaced and is +treated as literal text. For example: + +``` text +__("This variable '{0}' be replaced.", 'will not'); +``` + +By using two adjacent quotes your variables will be replaced properly: + +``` text +__("This variable ''{0}'' be replaced.", 'will'); +``` + +These functions take advantage of the +[ICU MessageFormatter](https://php.net/manual/en/messageformatter.format.php) +so you can translate messages and localize dates, numbers and currency at the +same time: + +``` text +echo __( + 'Hi {0}, your balance on the {1,date} is {2,number,currency}', + ['Charles', new DateTime('2014-01-13 11:12:00'), 1354.37] +); + +// Returns +Hi Charles, your balance on the Jan 13, 2014, 11:12 AM is $ 1,354.37 +``` + +Numbers in placeholders can be formatted as well with fine grain control of the +output: + +``` text +echo __( + 'You have traveled {0,number} kilometers in {1,number,integer} weeks', + [5423.344, 5.1] +); + +// Returns +You have traveled 5,423.34 kilometers in 5 weeks + +echo __('There are {0,number,#,###} people on earth', 6.1 * pow(10, 8)); + +// Returns +There are 6,100,000,000 people on earth +``` + +This is the list of formatter specifiers you can put after the word `number`: + +- `integer`: Removes the decimal part +- `currency`: Puts the locale currency symbol and rounds decimals +- `percent`: Formats the number as a percentage + +Dates can also be formatted by using the word `date` after the placeholder +number. A list of extra options follows: + +- `short` +- `medium` +- `long` +- `full` + +The word `time` after the placeholder number is also accepted and it +understands the same options as `date`. + +You can also use named placeholders like `{name}` in the message strings. +When using named placeholders, pass the placeholder and replacement in an array using key/value pairs, +for example: + +``` text +// echos: Hi. My name is Sara. I'm 12 years old. +echo __("Hi. My name is {name}. I'm {age} years old.", ['name' => 'Sara', 'age' => 12]); +``` + +### Plurals + +One crucial part of internationalizing your application is getting your messages +pluralized correctly depending on the language they are shown. CakePHP provides +a couple ways to correctly select plurals in your messages. + +#### Using ICU Plural Selection + +The first one is taking advantage of the `ICU` message format that comes by +default in the translation functions. In the translations file you could have +the following strings + +``` pot +msgid "{0,plural,=0{No records found} =1{Found 1 record} other{Found # records}}" +msgstr "{0,plural,=0{Ningún resultado} =1{1 resultado} other{# resultados}}" + +msgid "{placeholder,plural,=0{No records found} =1{Found 1 record} other{Found {1} records}}" +msgstr "{placeholder,plural,=0{Ningún resultado} =1{1 resultado} other{{1} resultados}}" +``` + +And in the application use the following code to output either of the +translations for such string: + +``` text +__('{0,plural,=0{No records found }=1{Found 1 record} other{Found # records}}', [0]); + +// Returns "Ningún resultado" as the argument {0} is 0 + +__('{0,plural,=0{No records found} =1{Found 1 record} other{Found # records}}', [1]); + +// Returns "1 resultado" because the argument {0} is 1 + +__('{placeholder,plural,=0{No records found} =1{Found 1 record} other{Found {1} records}}', [0, 'many', 'placeholder' => 2]) + +// Returns "many resultados" because the argument {placeholder} is 2 and +// argument {1} is 'many' +``` + +A closer look to the format we just used will make it evident how messages are +built: + +``` text +{ [count placeholder],plural, case1{message} case2{message} case3{...} ... } +``` + +The `[count placeholder]` can be the array key number of any of the variables +you pass to the translation function. It will be used for selecting the correct +plural form. + +Note that to reference `[count placeholder]` within `{message}` you have to +use `#`. + +You can of course use simpler message ids if you don't want to type the full +plural selection sequence in your code + +``` pot +msgid "search.results" +msgstr "{0,plural,=0{Ningún resultado} =1{1 resultado} other{{1} resultados}}" +``` + +Then use the new string in your code: + +``` text +__('search.results', [2, 2]); + +// Returns: "2 resultados" +``` + +The latter version has the downside that there is a need to have a translation +messages file even for the default language, but has the advantage that it makes +the code more readable and leaves the complicated plural selection strings in +the translation files. + +Sometimes using direct number matching in plurals is impractical. For example, +languages like Arabic require a different plural when you refer +to few things and other plural form for many things. In those cases you can +use the ICU matching aliases. Instead of writing: + +``` text +=0{No results} =1{...} other{...} +``` + +You can do: + +``` css +zero{No Results} one{One result} few{...} many{...} other{...} +``` + +Make sure you read the +[Language Plural Rules Guide](https://unicode-org.github.io/cldr-staging/charts/37/supplemental/language_plural_rules.html) +to get a complete overview of the aliases you can use for each language. + +#### Using Gettext Plural Selection + +The second plural selection format accepted is using the built-in capabilities +of Gettext. In this case, plurals will be stored in the `.po` +file by creating a separate message translation line per plural form: + +``` pot +# One message identifier for singular +msgid "One file removed" +# Another one for plural +msgid_plural "{0} files removed" +# Translation in singular +msgstr[0] "Un fichero eliminado" +# Translation in plural +msgstr[1] "{0} ficheros eliminados" +``` + +When using this other format, you are required to use another translation +function: + +``` php +// Returns: "10 ficheros eliminados" +$count = 10; +__n('One file removed', '{0} files removed', $count, $count); + +// It is also possible to use it inside a domain +__dn('my_plugin', 'One file removed', '{0} files removed', $count, $count); +``` + +The number inside `msgstr[]` is the number assigned by Gettext for the plural +form of the language. Some languages have more than two plural forms, for +example Croatian: + +``` pot +msgid "One file removed" +msgid_plural "{0} files removed" +msgstr[0] "{0} datoteka je uklonjena" +msgstr[1] "{0} datoteke su uklonjene" +msgstr[2] "{0} datoteka je uklonjeno" +``` + +Please visit the [Launchpad languages page](https://translations.launchpad.net/+languages) +for a detailed explanation of the plural form numbers for each language. + +## Creating Your Own Translators + +If you need to diverge from CakePHP conventions regarding where and how +translation messages are stored, you can create your own translation message +loader. The easiest way to create your own translator is by defining a loader +for a single domain and locale: + +``` php +use Cake\I18n\Package; +// Prior to 4.2 you need to use Aura\Intl\Package + +I18n::setTranslator('animals', function () { + $package = new Package( + 'default', // The formatting strategy (ICU) + 'default' // The fallback domain + ); + $package->setMessages([ + 'Dog' => 'Chien', + 'Cat' => 'Chat', + 'Bird' => 'Oiseau' + ... + ]); + + return $package; +}, 'fr_FR'); +``` + +The above code can be added to your **config/bootstrap.php** so that +translations can be found before any translation function is used. The absolute +minimum that is required for creating a translator is that the loader function +should return a `Cake\I18n\Package` object (prior to 4.2 it should be an `Aura\Intl\Package` object). +Once the code is in place you can use the translation functions as usual: + +``` php +I18n::setLocale('fr_FR'); +__d('animals', 'Dog'); // Returns "Chien" +``` + +As you see, `Package` objects take translation messages as an array. You can +pass the `setMessages()` method however you like: with inline code, including +another file, calling another function, etc. CakePHP provides a few loader +functions you can reuse if you just need to change where messages are loaded. +For example, you can still use **.po** files, but loaded from another location: + +``` php +use Cake\I18n\MessagesFileLoader as Loader; + +// Load messages from resources/locales/folder/sub_folder/filename.po +I18n::setTranslator( + 'animals', + new Loader('filename', 'folder/sub_folder', 'po'), + 'fr_FR' +); +``` + +### Creating Message Parsers + +It is possible to continue using the same conventions CakePHP uses, but use +a message parser other than `PoFileParser`. For example, if you wanted to load +translation messages using `YAML`, you will first need to created the parser +class: + +``` php +namespace App\I18n\Parser; + +class YamlFileParser +{ + public function parse($file) + { + return yaml_parse_file($file); + } +} +``` + +The file should be created in the **src/I18n/Parser** directory of your +application. Next, create the translations file under +**resources/locales/fr_FR/animals.yaml** + +``` yaml +Dog: Chien +Cat: Chat +Bird: Oiseau +``` + +And finally, configure the translation loader for the domain and locale: + +``` php +use Cake\I18n\MessagesFileLoader as Loader; + +I18n::setTranslator( + 'animals', + new Loader('animals', 'fr_FR', 'yaml'), + 'fr_FR' +); +``` + + + +### Creating Generic Translators + +Configuring translators by calling `I18n::setTranslator()` for each domain and +locale you need to support can be tedious, specially if you need to support more +than a few different locales. To avoid this problem, CakePHP lets you define +generic translator loaders for each domain. + +Imagine that you wanted to load all translations for the default domain and for +any language from an external service: + +``` php +use Cake\I18n\Package; +// Prior to 4.2 you need to use Aura\Intl\Package + +I18n::config('default', function ($domain, $locale) { + $locale = Locale::parseLocale($locale); + $lang = $locale['language']; + $messages = file_get_contents("http://example.com/translations/$lang.json"); + + return new Package( + 'default', // Formatter + null, // Fallback (none for default domain) + json_decode($messages, true) + ) +}); +``` + +The above example calls an example external service to load a JSON file with the +translations and then just build a `Package` object for any locale that is +requested in the application. + +If you'd like to change how packages are loaded for all packages, that don't +have specific loaders set you can replace the fallback package loader by using +the `_fallback` package: + +``` php +I18n::config('_fallback', function ($domain, $locale) { + // Custom code that yields a package here. +}); +``` + +### Plurals and Context in Custom Translators + +The arrays used for `setMessages()` can be crafted to instruct the translator +to store messages under different domains or to trigger Gettext-style plural +selection. The following is an example of storing translations for the same key +in different contexts: + +``` php +[ + 'He reads the letter {0}' => [ + 'alphabet' => 'Él lee la letra {0}', + 'written communication' => 'Él lee la carta {0}', + ], +] +``` + +Similarly, you can express Gettext-style plurals using the messages array by +having a nested array key per plural form: + +``` php +[ + 'I have read one book' => 'He leído un libro', + 'I have read {0} books' => [ + 'He leído un libro', + 'He leído {0} libros', + ], +] +``` + +### Using Different Formatters + +In previous examples we have seen that Packages are built using `default` as +first argument, and it was indicated with a comment that it corresponded to the +formatter to be used. Formatters are classes responsible for interpolating +variables in translation messages and selecting the correct plural form. + +If you're dealing with a legacy application, or you don't need the power offered +by the ICU message formatting, CakePHP also provides the `sprintf` formatter: + +``` text +return Package('sprintf', 'fallback_domain', $messages); +``` + +The messages to be translated will be passed to the `sprintf()` function for +interpolating the variables: + +``` text +__('Hello, my name is %s and I am %d years old', 'José', 29); +``` + +It is possible to set the default formatter for all translators created by +CakePHP before they are used for the first time. This does not include manually +created translators using the `setTranslator()` and `config()` methods: + +``` php +I18n::setDefaultFormatter('sprintf'); +``` + +## Localizing Dates and Numbers + +When outputting Dates and Numbers in your application, you will often need that +they are formatted according to the preferred format for the country or region +that you wish your page to be displayed. + +In order to change how dates and numbers are displayed you just need to change +the current locale setting and use the right classes: + +``` php +use Cake\I18n\I18n; +use Cake\I18n\DateTime; +use Cake\I18n\Number; + +I18n::setLocale('fr-FR'); + +$date = new DateTime('2015-04-05 23:00:00'); + +echo $date; // Displays 05/04/2015 23:00 + +echo Number::format(524.23); // Displays 524,23 +``` + +Make sure you read the [Date & Time](../core-libraries/time) and [Number](../core-libraries/number) +sections to learn more about formatting options. + +By default dates returned for the ORM results use the `Cake\I18n\DateTime` class, +so displaying them directly in you application will be affected by changing the +current locale. + + + +### Parsing Localized Datetime Data + +When accepting localized data from the request, it is nice to accept datetime +information in a user's localized format. In a controller, or +[Middleware](../controllers/middleware) you can configure the Date, Time, and +DateTime types to parse localized formats: + +``` php +use Cake\Database\TypeFactory; + +// Enable default locale format parsing. +TypeFactory::build('datetime')->useLocaleParser(); + +// Configure a custom datetime format parser format. +TypeFactory::build('datetime')->useLocaleParser()->setLocaleFormat('dd-M-y'); + +// You can also use IntlDateFormatter constants. +TypeFactory::build('datetime')->useLocaleParser() + ->setLocaleFormat([IntlDateFormatter::SHORT, -1]); +``` + +The default parsing format is the same as the default string format. + + + +### Converting Request Data from the User's Timezone + +When handling data from users in different timezones you will need to convert +the datetimes in request data into your application's timezone. You can use +`setUserTimezone()` from a controller or [Middleware](../controllers/middleware) to +make this process simpler: + +``` php +// Set the user's timezone +TypeFactory::build('datetime')->setUserTimezone($user->timezone); +``` + +Once set, when your application creates or updates entities from request data, +the ORM will automatically convert datetime values from the user's timezone into +your application's timezone. This ensures that your application is always +working in the timezone defined in `App.defaultTimezone`. + +If your application handles datetime information in a number of actions you can +use a middleware to define both timezone conversion and locale parsing: + +``` php +namespace App\Middleware; + +use Cake\Database\TypeFactory; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; + +class DatetimeMiddleware implements MiddlewareInterface +{ + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface { + // Get the user from the request. + // This example assumes your user entity has a timezone attribute. + $user = $request->getAttribute('identity'); + if ($user) { + TypeFactory::build('datetime') + ->useLocaleParser() + ->setUserTimezone($user->timezone); + } + + return $handler->handle($request); + } +} +``` + +## Automatically Choosing the Locale Based on Request Data + +By using the `LocaleSelectorMiddleware` in your application, CakePHP will +automatically set the locale based on the current user: + +``` php +// in src/Application.php +use Cake\I18n\Middleware\LocaleSelectorMiddleware; + +// Update the middleware function, adding the new middleware +public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue +{ + // Add middleware and set the valid locales + $middlewareQueue->add(new LocaleSelectorMiddleware(['en_US', 'fr_FR'])); + // To accept any locale header value + $middlewareQueue->add(new LocaleSelectorMiddleware(['*'])); +} +``` + +The `LocaleSelectorMiddleware` will use the `Accept-Language` header to +automatically set the user's preferred locale. You can use the locale list +option to restrict which locales will automatically be used. + +## Translate Content/Entities + +If you want to translate content/entities then you should look at the [Translate Behavior](../orm/behaviors/translate). diff --git a/docs/en/core-libraries/logging.md b/docs/en/core-libraries/logging.md new file mode 100644 index 0000000000..211b709a6d --- /dev/null +++ b/docs/en/core-libraries/logging.md @@ -0,0 +1,593 @@ +# Logging + +While CakePHP core Configure Class settings can really help you see +what's happening under the hood, there are certain times that +you'll need to log data to the disk in order to find out what's +going on. With technologies like SOAP, AJAX, and REST APIs, debugging can be +rather difficult. + +Logging can also be a way to find out what's been going on in your +application over time. What search terms are being used? What sorts +of errors are my users being shown? How often is a particular query +being executed? + +Logging data in CakePHP is done with the `log()` function. It is provided by the +`LogTrait`, which is the common ancestor for many CakePHP classes. If the +context is a CakePHP class (Controller, Component, View,...), you can log your +data. You can also use `Log::write()` directly. See [Writing To Logs](#writing-to-logs). + + + +## Logging Configuration + +Configuring `Log` should be done during your application's bootstrap phase. +The **config/app.php** file is intended for just this. You can define +as many or as few loggers as your application needs. Loggers should be +configured using `Cake\Log\Log`. An example would be: + +``` php +use Cake\Log\Engine\FileLog; +use Cake\Log\Log; + +// Classname using logger 'class' constant +Log::setConfig('info', [ + 'className' => FileLog::class, + 'path' => LOGS, + 'levels' => ['info'], + 'file' => 'info', +]); + +// Short classname +Log::setConfig('debug', [ + 'className' => 'File', + 'path' => LOGS, + 'levels' => ['notice', 'debug'], + 'file' => 'debug', +]); + +// Fully namespaced name. +Log::setConfig('error', [ + 'className' => 'Cake\Log\Engine\FileLog', + 'path' => LOGS, + 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'], + 'file' => 'error', +]); +``` + +The above creates three loggers, named `info`, `debug` and `error`. +Each is configured to handle different levels of messages. They also store their +log messages in separate files, so we can separate debug/notice/info logs +from more serious errors. See the section on [Logging Levels](#logging-levels) for more +information on the different levels and what they mean. + +Once a configuration is created you cannot change it. Instead you should drop +the configuration and re-create it using `Cake\Log\Log::drop()` and +`Cake\Log\Log::setConfig()`. + +It is also possible to create loggers by providing a closure. This is useful +when you need full control over how the logger object is built. The closure +has to return the constructed logger instance. For example: + +``` php +Log::setConfig('special', function () { + return new \Cake\Log\Engine\FileLog(['path' => LOGS, 'file' => 'log']); +}); +``` + +Configuration options can also be provided as a `DSN` string. This is +useful when working with environment variables or `PaaS` providers: + +``` php +Log::setConfig('error', [ + 'url' => 'file:///full/path/to/logs/?levels[]=warning&levels[]=error&file=error', +]); +``` + +> [!WARNING] +> If you do not configure logging engines, log messages will not be stored. + +## Error and Exception Logging + +Errors and Exceptions can also be logged. By configuring the corresponding +values in your **config/app.php** file. Errors will be displayed when debug is +`true` and logged when debug is `false`. To log uncaught exceptions, set the +`log` option to `true`. See [Configuration](../development/configuration) for more +information. + + + +## Writing to Logs + +Writing to the log files can be done in two different ways. The first +is to use the static `Cake\Log\Log::write()` method: + +``` php +Log::write('debug', 'Something did not work'); +``` + +The second is to use the `log()` shortcut function available on any +class using the `LogTrait`. Calling `log()` will internally call +`Log::write()`: + +``` php +// Executing this inside a class using LogTrait +$this->log('Something did not work!', 'debug'); +``` + +All configured log streams are written to sequentially each time +`Cake\Log\Log::write()` is called. If you have not configured any +logging engines `log()` will return `false` and no log messages will be +written. + +### Using Placeholders in Messages + +If you need to log dynamically defined data, you can use placeholders in your +log messages and provide an array of key/value pairs in the `$context` +parameter: + +``` php +// Will log `Could not process for userid=1` +Log::write('error', 'Could not process for userid={user}', ['user' => $user->id]); +``` + +Placeholders that do not have keys defined will not be replaced. If you need to +use a literal braced word, you must escape the placeholder: + +``` php +// Will log `No {replace}` +Log::write('error', 'No \\{replace}', ['replace' => 'no']); +``` + +If you include objects in your logging placeholders those objects must implement +one of the following methods: + +- `__toString()` +- `toArray()` +- `__debugInfo()` + + + +### Using Levels + +CakePHP supports the standard POSIX set of logging levels. Each level represents +an increasing level of severity: + +- Emergency: system is unusable +- Alert: action must be taken immediately +- Critical: critical conditions +- Error: error conditions +- Warning: warning conditions +- Notice: normal but significant condition +- Info: informational messages +- Debug: debug-level messages + +You can refer to these levels by name when configuring loggers, and when writing +log messages. Alternatively, you can use convenience methods like +`Cake\Log\Log::error()` to clearly indicate the logging +level. Using a level that is not in the above levels will result in an +exception. + +> [!NOTE] +> When `levels` is set to an empty value in a logger's configuration, it +> will take messages of any level. + + + +### Logging Scopes + +Often times you'll want to configure different logging behavior for different +subsystems or parts of your application. Take for example an e-commerce shop. +You'll probably want to handle logging for orders and payments differently than +you do other less critical logs. + +CakePHP exposes this concept as logging scopes. When log messages are written +you can include a scope name. If there is a configured logger for that scope, +the log messages will be directed to those loggers. For example: + +``` php +use Cake\Log\Engine\FileLog; + +// Configure logs/shops.log to receive all levels, but only +// those with `orders` and `payments` scope. +Log::setConfig('shops', [ + 'className' => FileLog::class, + 'path' => LOGS, + 'levels' => [], + 'scopes' => ['orders', 'payments'], + 'file' => 'shops.log', +]); + +// Configure logs/payments.log to receive all levels, but only +// those with `payments` scope. +Log::setConfig('payments', [ + 'className' => FileLog::class, + 'path' => LOGS, + 'levels' => [], + 'scopes' => ['payments'], + 'file' => 'payments.log', +]); + +Log::warning('this gets written only to shops.log', ['scope' => ['orders']]); +Log::warning('this gets written to both shops.log and payments.log', ['scope' => ['payments']]); +``` + +Scopes can also be passed as a single string or a numerically indexed array. +Note that using this form will limit the ability to pass more data as context: + +``` php +Log::warning('This is a warning', ['orders']); +Log::warning('This is a warning', 'payments'); +``` + +> [!NOTE] +> When `scopes` is set to an empty array or `null` in a logger's +> configuration, it will take messages of any scope. Setting it to `false` +> will only match messages without scope. + + + +## Logging to Files + +As its name implies `FileLog` writes log messages to files. The level of log +message being written determines the name of the file the message is stored in. +If a level is not supplied, `LOG_ERR` is used which writes to the +error log. The default log location is **logs/\$level.log**: + +``` php +// Executing this inside a CakePHP class +$this->log("Something didn't work!"); + +// Results in this being appended to logs/error.log +// 2007-11-02 10:22:02 Error: Something didn't work! +``` + +The configured directory must be writable by the web server user in +order for logging to work correctly. + +You can configure additional/alternate FileLog locations when configuring +a logger. FileLog accepts a `path` which allows for +custom paths to be used: + +``` php +Log::setConfig('custom_path', [ + 'className' => 'File', + 'path' => '/path/to/custom/place/' +]); +``` + +`FileLog` engine takes the following options: + +- `size` Used to implement basic log file rotation. If log file size + reaches specified size the existing file is renamed by appending timestamp + to filename and new log file is created. Can be integer bytes value or + human readable string values like '10MB', '100KB' etc. Defaults to 10MB. +- `rotate` Log files are rotated specified times before being removed. + If value is 0, old versions are removed rather then rotated. Defaults to 10. +- `mask` Set the file permissions for created files. If left empty the default + permissions are used. + +> [!NOTE] +> Missing directories will be automatically created to avoid +> unnecessary errors thrown when using the FileEngine. + + + +## Logging to Syslog + +In production environments it is highly recommended that you setup your system to +use syslog instead of the file logger. This will perform much better as any +writes will be done in a (almost) non-blocking fashion and your operating system +logger can be configured separately to rotate files, pre-process writes or use +a completely different storage for your logs. + +Using syslog is pretty much like using the default FileLog engine, you just need +to specify `Syslog` as the engine to be used for logging. The following +configuration snippet will replace the default logger with syslog, this should +be done in the **config/bootstrap.php** file: + +``` php +Log::setConfig('default', [ + 'engine' => 'Syslog' +]); +``` + +The configuration array accepted for the Syslog logging engine understands the +following keys: + +- `format`: An sprintf template string with two placeholders, the first one + for the error level, and the second for the message itself. This key is + useful to add additional information about the server or process in the + logged message. For example: `%s - Web Server 1 - %s` will look like + `error - Web Server 1 - An error occurred in this request` after + replacing the placeholders. This option is deprecated. You should use + [Logging Formatters](#logging-formatters) instead. +- `prefix`: An string that will be prefixed to every logged message. +- `flag`: An integer flag to be used for opening the connection to the + logger, by default `LOG_ODELAY` will be used. See `openlog` documentation + for more options +- `facility`: The logging slot to use in syslog. By default `LOG_USER` is + used. See `syslog` documentation for more options + +## Creating Log Engines + +Log engines can be part of your application, or part of +plugins. If for example you had a database logger called +`DatabaseLog`. As part of your application it would be placed in +**src/Log/Engine/DatabaseLog.php**. As part of a plugin it would be placed in +**plugins/LoggingPack/src/Log/Engine/DatabaseLog.php**. To configure log +engine you should use `Cake\Log\Log::setConfig()`. For example +configuring our DatabaseLog would look like: + +``` php +// For src/Log +Log::setConfig('otherFile', [ + 'className' => 'Database', + 'model' => 'LogEntry', + // ... +]); + +// For plugin called LoggingPack +Log::setConfig('otherFile', [ + 'className' => 'LoggingPack.Database', + 'model' => 'LogEntry', + // ... +]); +``` + +When configuring a log engine the `className` parameter is used to +locate and load the log handler. All of the other configuration +properties are passed to the log engine's constructor as an array. : + +``` php +namespace App\Log\Engine; +use Cake\Log\Engine\BaseLog; + +class DatabaseLog extends BaseLog +{ + public function __construct(array $config = []) + { + parent::__construct($config); + // ... + } + + public function log($level, string $message, array $context = []) + { + // Write to the database. + } +} +``` + +CakePHP requires that all logging engine implement `Psr\Log\LoggerInterface`. +The class `Cake\Log\Engine\BaseLog` is an easy way to satisfy the +interface as it only requires you to implement the `log()` method. + + + +## Logging Formatters + +Logging formatters allow you to control how log messages are formatted +independent of the storage engine. Each core provided logging engine comes with +a formatter configured to maintain backwards compatible output. However, you can +adjust the formatters to fit your requirements. Formatters are configured +alongside the logging engine: + +``` php +use Cake\Log\Engine\SyslogLog; +use App\Log\Formatter\CustomFormatter; + +// Simple formatting configuration with no options. +Log::setConfig('error', [ + 'className' => SyslogLog::class, + 'formatter' => CustomFormatter::class, +]); + +// Configure a formatter with additional options. +Log::setConfig('error', [ + 'className' => SyslogLog::class, + 'formatter' => [ + 'className' => CustomFormatter::class, + 'key' => 'value', + ], +]); +``` + +To implement your own logging formatter you need to extend +`Cake\Log\Format\AbstractFormatter` or one of its subclasses. The primary +method you need to implement is `format($level, $message, $context)` which is +responsible for formatting log messages. + + + +## Testing Logs + +To test logging, add `Cake\TestSuite\LogTestTrait` to your test case. The +`LogTestTrait` uses PHPUnit hooks to attach log engines that intercept the log +messages your application is making. Once you have captured logs you can perform +assertions on log messages your application is emitting. For example: + +``` php +namespace App\Test\TestCase\Controller; + +use Cake\TestSuite\LogTestTrait; +use Cake\TestSuite\TestCase; + +class UsersControllerTest extends TestCase +{ + use LogTestTrait; + + public function setUp(): void + { + parent::setUp(); + $this->setupLog([ + 'error' => ['scopes' => ['app.security']] + ]); + } + + public function testResetPassword() + { + $this->post('/users/resetpassword', ['email' => 'bob@example.com']); + $this->assertLogMessageContains('info', 'bob@example.com reset password', 'app.security'); + } +} +``` + +You use `setupLog()` to define the log messages you wish to capture and +perform assertions on. After logs have been emitted you can make assertions on +the contents of logs, or the absence of them: + +- `assertLogMessage(string $level, string $expectedMessage, ?string $scope = null, string $failMsg = '')` Assert that a log message was found. +- `assertLogMessageContains(string $level, string $expectedMessage, ?string $scope = null, string $failMsg = '')` Assert that a log message contains the + substring. +- `assertLogAbsent(string $level, ?string $failMsg = '')` Assert that no log + messages of the given level were captured. + +The `LogTestTrait` will automatically clean up any loggers that were +configured. + +## Log API + +`class` Cake\\Log\\**Log** + +### Log::setConfig() + +`static` Cake\\Log\\Log::**setConfig**($key, $config): void + +param string \$name +Name for the logger being connected, used +to drop a logger later on. + +param array \$config +Array of configuration information and +constructor arguments for the logger. + +Get or set the configuration for a Logger. See [Log Configuration](#log-configuration) for +more information. + +### Log::configured() + +`static` Cake\\Log\\Log::**configured**(): array + +returns +An array of configured loggers. + +Get the names of the configured loggers. + +### Log::drop() + +`static` Cake\\Log\\Log::**drop**($name): bool + +param string \$name +Name of the logger you wish to no longer receive +messages. + +### Log::write() + +`static` Cake\\Log\\Log::**write**($level, $message, $scope = []): bool + +Write a message into all the configured loggers. +`$level` indicates the level of log message being created. +`$message` is the message of the log entry being written to. +`$scope` is the scope(s) a log message is being created in. + +### Log::levels() + +`static` Cake\\Log\\Log::**levels**(): array + +Call this method without arguments, eg: Log::levels() to obtain current +level configuration. + +### Convenience Methods + +The following convenience methods were added to log \$message with the +appropriate log level. + +#### Log::emergency() + +`static` Cake\\Log\\Log::**emergency**($message, $scope = []): bool + +#### Log::alert() + +`static` Cake\\Log\\Log::**alert**($message, $scope = []): bool + +#### Log::critical() + +`static` Cake\\Log\\Log::**critical**($message, $scope = []): bool + +#### Log::error() + +`static` Cake\\Log\\Log::**error**($message, $scope = []): bool + +#### Log::warning() + +`static` Cake\\Log\\Log::**warning**($message, $scope = []): bool + +#### Log::notice() + +`static` Cake\\Log\\Log::**notice**($message, $scope = []): bool + +#### Log::info() + +`static` Cake\\Log\\Log::**info**($message, $scope = []): bool + +#### Log::debug() + +`static` Cake\\Log\\Log::**debug**($message, $scope = []): bool + +## Logging Trait + +> A trait that provides shortcut methods for logging + +### Log::log() + +`method` Cake\\Log\\Log::**log**($msg, $level = LOG_ERR): bool + +## Using Monolog + +Monolog is a popular logger for PHP. Since it implements the same interfaces as +the CakePHP loggers, you can use them in your application as the default +logger. + +After installing Monolog using composer, configure the logger using the +`Log::setConfig()` method: + +``` php +// config/bootstrap.php + +use Monolog\Logger; +use Monolog\Handler\StreamHandler; + +Log::setConfig('default', function () { + $log = new Logger('app'); + $log->pushHandler(new StreamHandler('path/to/your/combined.log')); + + return $log; +}); + +// Optionally stop using the now redundant default loggers +Log::drop('debug'); +Log::drop('error'); +``` + +Use similar methods if you want to configure a different logger for your console: + +``` php +// config/bootstrap_cli.php + +use Monolog\Logger; +use Monolog\Handler\StreamHandler; + +Log::setConfig('default', function () { + $log = new Logger('cli'); + $log->pushHandler(new StreamHandler('path/to/your/combined-cli.log')); + + return $log; +}); + +// Optionally stop using the now redundant default CLI loggers +Configure::delete('Log.debug'); +Configure::delete('Log.error'); +``` + +> [!NOTE] +> When using a console specific logger, make sure to conditionally configure +> your application logger. This will prevent duplicate log entries. diff --git a/docs/en/core-libraries/number.md b/docs/en/core-libraries/number.md new file mode 100644 index 0000000000..fdc5704919 --- /dev/null +++ b/docs/en/core-libraries/number.md @@ -0,0 +1,348 @@ +# Number + +`class` Cake\\I18n\\**Number** + +If you need `NumberHelper` functionalities outside of a `View`, +use the `Number` class: + +``` php +namespace App\Controller; + +use Cake\I18n\Number; + +class UsersController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('Authentication.Authentication'); + } + + public function afterLogin() + { + $identity = $this->Authentication->getIdentity(); + $storageUsed = $identity->storage_used; + if ($storageUsed > 5000000) { + // Notify users of quota + $this->Flash->success(__('You are using {0} storage', Number::toReadableSize($storageUsed))); + } + } +} +``` + + + +All of these functions return the formatted number; they do not +automatically echo the output into the view. + +## Formatting Currency Values + +### Number::currency() + +`method` Cake\\I18n\\Number::**currency**(mixed $value, string $currency = null, array $options = []): string + +This method is used to display a number in common currency formats +(EUR, GBP, USD), based on the 3-letter ISO 4217 currency code. Usage in a view looks like: + +``` php +// Called as NumberHelper +echo $this->Number->currency($value, $currency); + +// Called as Number +echo Number::currency($value, $currency); +``` + +The first parameter, `$value`, should be a floating point number +that represents the amount of money you are expressing. The second +parameter is a string used to choose a predefined currency formatting +scheme: + +| \$currency | 1234.56, formatted by currency type | +|------------|-------------------------------------| +| EUR | €1.234,56 | +| GBP | £1,234.56 | +| USD | \$1,234.56 | + +The third parameter is an array of options for further defining the +output. The following options are available: + +| Option | Description | +|----|----| +| before | Text to display before the rendered number. | +| after | Text to display after the rendered number. | +| zero | The text to use for zero values; can be a string or a number. ie. 0, 'Free!'. | +| places | Number of decimal places to use, ie. 2 | +| precision | Maximal number of decimal places to use, ie. 2 | +| locale | The locale name to use for formatting number, ie. "fr_FR". | +| fractionSymbol | String to use for fraction numbers, ie. ' cents'. | +| fractionPosition | Either 'before' or 'after' to place the fraction symbol. | +| pattern | An ICU number pattern to use for formatting the number ie. \#,###.00 | +| useIntlCode | Set to `true` to replace the currency symbol with the international currency code. | + +If `$currency` value is `null`, the default currency will be retrieved from +`Cake\I18n\Number::defaultCurrency()`. To format currencies in an +accounting format you should set the currency format: + +``` php +Number::setDefaultCurrencyFormat(Number::FORMAT_CURRENCY_ACCOUNTING); +``` + +## Setting the Default Currency + +### Number::setDefaultCurrency() + +`method` Cake\\I18n\\Number::**setDefaultCurrency**($currency): void + +Setter for the default currency. This removes the need to always pass the +currency to `Cake\I18n\Number::currency()` and change all +currency outputs by setting other default. If `$currency` is set to `null`, +it will clear the currently stored value. + +## Getting the Default Currency + +### Number::getDefaultCurrency() + +`method` Cake\\I18n\\Number::**getDefaultCurrency**(): string + +Getter for the default currency. If default currency was set earlier using +`setDefaultCurrency()`, then that value will be returned. By default, it will +retrieve the `intl.default_locale` ini value if set and `'en_US'` if not. + +## Formatting Floating Point Numbers + +### Number::precision() + +`method` Cake\\I18n\\Number::**precision**(float $value, int $precision = 3, array $options = []): string + +This method displays a number with the specified amount of +precision (decimal places). It will round in order to maintain the +level of precision defined. : + +``` php +// Called as NumberHelper +echo $this->Number->precision(456.91873645, 2); + +// Outputs +456.92 + +// Called as Number +echo Number::precision(456.91873645, 2); +``` + +## Formatting Percentages + +### Number::toPercentage() + +`method` Cake\\I18n\\Number::**toPercentage**(mixed $value, int $precision = 2, array $options = []): string + +| Option | Description | +|----|----| +| multiply | Boolean to indicate whether the value has to be multiplied by 100. Useful for decimal percentages. | + +Like `Cake\I18n\Number::precision()`, this method formats a number +according to the supplied precision (where numbers are rounded to meet the +given precision). This method also expresses the number as a percentage +and appends the output with a percent sign. : + +``` php +// Called as NumberHelper. Output: 45.69% +echo $this->Number->toPercentage(45.691873645); + +// Called as Number. Output: 45.69% +echo Number::toPercentage(45.691873645); + +// Called with multiply. Output: 45.7% +echo Number::toPercentage(0.45691, 1, [ + 'multiply' => true +]); +``` + +## Interacting with Human Readable Values + +### Number::toReadableSize() + +`method` Cake\\I18n\\Number::**toReadableSize**(string $size): string + +This method formats data sizes in human readable forms. It provides +a shortcut way to convert bytes to KB, MB, GB, and TB. The size is +displayed with a two-digit precision level, according to the size +of data supplied (i.e. higher sizes are expressed in larger +terms): + +``` php +// Called as NumberHelper +echo $this->Number->toReadableSize(0); // 0 Byte +echo $this->Number->toReadableSize(1024); // 1 KB +echo $this->Number->toReadableSize(1321205.76); // 1.26 MB +echo $this->Number->toReadableSize(5368709120); // 5 GB + +// Called as Number +echo Number::toReadableSize(0); // 0 Byte +echo Number::toReadableSize(1024); // 1 KB +echo Number::toReadableSize(1321205.76); // 1.26 MB +echo Number::toReadableSize(5368709120); // 5 GB +``` + +## Formatting Numbers + +### Number::format() + +`method` Cake\\I18n\\Number::**format**(mixed $value, array $options = []): string + +This method gives you much more control over the formatting of +numbers for use in your views (and is used as the main method by +most of the other NumberHelper methods). Using this method might +looks like: + +``` php +// Called as NumberHelper +$this->Number->format($value, $options); + +// Called as Number +Number::format($value, $options); +``` + +The `$value` parameter is the number that you are planning on +formatting for output. With no `$options` supplied, the number +1236.334 would output as 1,236. Note that the default precision is +zero decimal places. + +The `$options` parameter is where the real magic for this method +resides. + +- If you pass an integer then this becomes the amount of precision + or places for the function. +- If you pass an associated array, you can use the following keys: + +| Option | Description | +|----|----| +| places | Number of decimal places to use, ie. 2 | +| precision | Maximum number of decimal places to use, ie. 2 | +| pattern | An ICU number pattern to use for formatting the number ie. \#,###.00 | +| locale | The locale name to use for formatting number, ie. "fr_FR". | +| before | Text to display before the rendered number. | +| after | Text to display after the rendered number. | + +Example: + +``` php +// Called as NumberHelper +echo $this->Number->format('123456.7890', [ + 'places' => 2, + 'before' => '¥ ', + 'after' => ' !' +]); +// Output '¥ 123,456.79 !' + +echo $this->Number->format('123456.7890', [ + 'locale' => 'fr_FR' +]); +// Output '123 456,79 !' + +// Called as Number +echo Number::format('123456.7890', [ + 'places' => 2, + 'before' => '¥ ', + 'after' => ' !' +]); +// Output '¥ 123,456.79 !' + +echo Number::format('123456.7890', [ + 'locale' => 'fr_FR' +]); +// Output '123 456,79 !' +``` + +### Number::ordinal() + +`method` Cake\\I18n\\Number::**ordinal**(mixed $value, array $options = []): string + +This method will output an ordinal number. + +Examples: + +``` php +echo Number::ordinal(1); +// Output '1st' + +echo Number::ordinal(2); +// Output '2nd' + +echo Number::ordinal(2, [ + 'locale' => 'fr_FR' +]); +// Output '2e' + +echo Number::ordinal(410); +// Output '410th' +``` + +## Format Differences + +### Number::formatDelta() + +`method` Cake\\I18n\\Number::**formatDelta**(mixed $value, array $options = []): string + +This method displays differences in value as a signed number: + +``` php +// Called as NumberHelper +$this->Number->formatDelta($value, $options); + +// Called as Number +Number::formatDelta($value, $options); +``` + +The `$value` parameter is the number that you are planning on +formatting for output. With no `$options` supplied, the number +1236.334 would output as 1,236. Note that the default precision is +zero decimal places. + +The `$options` parameter takes the same keys as `Number::format()` itself: + +| Option | Description | +|-----------|------------------------------------------------------------| +| places | Number of decimal places to use, ie. 2 | +| precision | Maximum number of decimal places to use, ie. 2 | +| locale | The locale name to use for formatting number, ie. "fr_FR". | +| before | Text to display before the rendered number. | +| after | Text to display after the rendered number. | + +Example: + +``` php +// Called as NumberHelper +echo $this->Number->formatDelta('123456.7890', [ + 'places' => 2, + 'before' => '[', + 'after' => ']' +]); +// Output '[+123,456.79]' + +// Called as Number +echo Number::formatDelta('123456.7890', [ + 'places' => 2, + 'before' => '[', + 'after' => ']' +]); +// Output '[+123,456.79]' +``` + + + +## Configure formatters + +### Number::config() + +`method` Cake\\I18n\\Number::**config**(string $locale, int $type = NumberFormatter::DECIMAL, array $options = []): void + +This method allows you to configure formatter defaults which persist across calls +to various methods. + +Example: + +``` php +Number::config('en_IN', \NumberFormatter::CURRENCY, [ + 'pattern' => '#,##,##0' +]); +``` diff --git a/docs/en/core-libraries/plugin.md b/docs/en/core-libraries/plugin.md new file mode 100644 index 0000000000..182b41c31b --- /dev/null +++ b/docs/en/core-libraries/plugin.md @@ -0,0 +1,58 @@ +# Plugin Class + +`class` Cake\\Core\\**Plugin** + +The Plugin class is responsible for resource location and path management of plugins. + +## Locating Plugins + +### Plugin::path() + +`static` Cake\\Core\\Plugin::**path**(string $plugin): string + +Plugins can be located with Plugin. Using `Plugin::path('DebugKit');` +for example, will give you the full path to the DebugKit plugin: + +``` php +$path = Plugin::path('DebugKit'); +``` + +## Check if a Plugin is Loaded + +You can check dynamically inside your code if a specific plugin has been loaded: + +``` php +$isLoaded = Plugin::isLoaded('DebugKit'); +``` + +Use `Plugin::loaded()` if you want to get a list of all currently loaded plugins. + +## Finding Paths to Namespaces + +### Plugin::classPath() + +`static` Cake\\Core\\Plugin::**classPath**(string $plugin): string + +Used to get the location of the plugin's class files: + +``` php +$path = App::classPath('DebugKit'); +``` + +## Finding Paths to Resources + +### Plugin::templatePath() + +`static` Cake\\Core\\Plugin::**templatePath**(string $plugin): string + +The method returns the path to the plugins' templates: + +``` php +$path = Plugin::templatePath('DebugKit'); +``` + +The same goes for the config path: + +``` php +$path = Plugin::configPath('DebugKit'); +``` diff --git a/docs/en/core-libraries/registry-objects.md b/docs/en/core-libraries/registry-objects.md new file mode 100644 index 0000000000..6215c70862 --- /dev/null +++ b/docs/en/core-libraries/registry-objects.md @@ -0,0 +1,57 @@ +# Registry Objects + +The registry classes provide a simple way to create and retrieve loaded +instances of a given object type. There are registry classes for Components, +Helpers, Tasks, and Behaviors. + +While the examples below will use Components, the same behavior can be expected +for Helpers, Behaviors, and Tasks in addition to Components. + +## Loading Objects + +Objects can be loaded on-the-fly using add\() +Example: + +``` php +$this->loadComponent('Acl.Acl'); +$this->addHelper('Flash') +``` + +This will result in the `Acl` property and `Flash` helper being loaded. +Configuration can also be set on-the-fly. Example: + +``` php +$this->loadComponent('Cookie', ['name' => 'sweet']); +``` + +Any keys and values provided will be passed to the Component's constructor. The +one exception to this rule is `className`. Classname is a special key that is +used to alias objects in a registry. This allows you to have component names +that do not reflect the classnames, which can be helpful when extending core +components: + +``` php +$this->Flash = $this->loadComponent('Flash', ['className' => 'MyCustomFlash']); +$this->Flash->error(); // Actually using MyCustomFlash::error(); +``` + +## Triggering Callbacks + +Callbacks are not provided by registry objects. You should use the +[events system](../core-libraries/events) to dispatch any events/callbacks +for your application. + +## Disabling Callbacks + +In previous versions, collection objects provided a `disable()` method to disable +objects from receiving callbacks. You should use the features in the events system to +accomplish this now. For example, you could disable component callbacks in the +following way: + +``` php +// Remove MyComponent from callbacks. +$this->getEventManager()->off($this->MyComponent); + +// Re-enable MyComponent for callbacks. +$this->getEventManager()->on($this->MyComponent); +``` diff --git a/docs/en/core-libraries/security.md b/docs/en/core-libraries/security.md new file mode 100644 index 0000000000..d28286c75a --- /dev/null +++ b/docs/en/core-libraries/security.md @@ -0,0 +1,111 @@ +# Security Utility + +`class` Cake\\Utility\\**Security** + +The [security library](https://api.cakephp.org/5.x/class-Cake.Utility.Security.html) +handles basic security measures such as providing methods for +hashing and encrypting data. + +## Encrypting and Decrypting Data + +### Security::encrypt() + +`static` Cake\\Utility\\Security::**encrypt**($text, $key, $hmacSalt = null): string + +### Security::decrypt() + +`static` Cake\\Utility\\Security::**decrypt**($cipher, $key, $hmacSalt = null): string|null + +Encrypt `$text` using AES-256. The `$key` should be a value with a +lots of variance in the data much like a good password. The returned result +will be the encrypted value with an HMAC checksum. + +The [openssl](https://php.net/openssl) extension is required for encrypting/decrypting. + +An example use would be: + +``` php +// Assuming key is stored somewhere it can be re-used for +// decryption later. +$key = 'wt1U5MACWJFTXGenFoZoiLwQGrLgdbHA'; +$result = Security::encrypt($value, $key); +``` + +If you do not supply an HMAC salt, the value of `Security::getSalt()` will be used. +Encrypted values can be decrypted using +`Cake\Utility\Security::decrypt()`. + +This method should **never** be used to store passwords. + +Decrypt a previously encrypted value. The `$key` and `$hmacSalt` +parameters must match the values used to encrypt or decryption will fail. An +example use would be: + +``` php +// Assuming the key is stored somewhere it can be re-used for +// Decryption later. +$key = 'wt1U5MACWJFTXGenFoZoiLwQGrLgdbHA'; + +$cipher = $user->secrets; +$result = Security::decrypt($cipher, $key); +``` + +If the value cannot be decrypted due to changes in the key or HMAC salt +`false` will be returned. + +## Hashing Data + +### Security::hash() + +`static` Cake\\Utility\\Security::**hash**( $string, $type = NULL, $salt = false ): string + +Create a hash from string using given method. Fallback on next +available method. If `$salt` is set to `true`, the application's salt +value will be used: + +``` php +// Using the application's salt value +$sha1 = Security::hash('CakePHP Framework', 'sha1', true); + +// Using a custom salt value +$sha1 = Security::hash('CakePHP Framework', 'sha1', 'my-salt'); + +// Using the default hash algorithm +$hash = Security::hash('CakePHP Framework'); +``` + +The `hash()` method supports the following hashing strategies: + +- md5 +- sha1 +- sha256 + +And any other hash algorithm that PHP's `hash()` function supports. + +> [!WARNING] +> You should not be using `hash()` for passwords in new applications. +> Instead you should use the `DefaultPasswordHasher` class which uses bcrypt +> by default. + +## Getting Secure Random Data + +### Security::randomBytes() + +`static` Cake\\Utility\\Security::**randomBytes**($length): string + +Get `$length` number of bytes from a secure random source. This function draws +data from one of the following sources: + +- PHP's `random_bytes` function. +- `openssl_random_pseudo_bytes` from the SSL extension. + +If neither source is available a warning will be emitted and an unsafe value +will be used for backwards compatibility reasons. + +### Security::randomString() + +`static` Cake\\Utility\\Security::**randomString**($length): string + +Get a random string `$length` long from a secure random source. This method +draws from the same random source as `randomBytes()` and will encode the data +as a hexadecimal string. diff --git a/docs/en/core-libraries/text.md b/docs/en/core-libraries/text.md new file mode 100644 index 0000000000..221653f2d8 --- /dev/null +++ b/docs/en/core-libraries/text.md @@ -0,0 +1,451 @@ +# Text + +`class` Cake\\Utility\\**Text** + +The Text class includes convenience methods for creating and manipulating +strings and is normally accessed statically. Example: +`Text::uuid()`. + +If you need `Cake\View\Helper\TextHelper` functionalities outside +of a `View`, use the `Text` class: + +``` php +namespace App\Controller; + +use Cake\Utility\Text; + +class UsersController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('Auth') + }; + + public function afterLogin() + { + $message = $this->Users->find('new_message')->first(); + if (!empty($message)) { + // Notify user of new message + $this->Flash->success(__( + 'You have a new message: {0}', + Text::truncate($message['Message']['body'], 255, ['html' => true]) + )); + } + } +} +``` + +## Convert Strings into ASCII + +### Text::transliterate() + +`static` Cake\\Utility\\Text::**transliterate**($string, $transliteratorId = null): string + +Transliterate by default converts all characters in provided string into +equivalent ASCII characters. The method expects UTF-8 encoding. The character +conversion can be controlled using transliteration identifiers which you can +pass using the `$transliteratorId` argument or change the default identifier +string using `Text::setTransliteratorId()`. ICU transliteration identifiers +are basically of form `:` and you can specify +multiple conversion pairs separated by `;`. You can find more info about +transliterator identifiers +[here](https://unicode-org.github.io/icu/userguide/transforms/general/#transliterator-identifiers): + +``` php +// apple puree +Text::transliterate('apple purée'); + +// Ubermensch (only latin characters are transliterated) +Text::transliterate('Übérmensch', 'Latin-ASCII;'); +``` + +## Creating URL Safe Strings + +### Text::slug() + +`static` Cake\\Utility\\Text::**slug**(string $string, array|string $options = []): string + +Slug transliterates all characters into ASCII versions and converting unmatched +characters and spaces to dashes. The slug method expects UTF-8 encoding. + +You can provide an array of options that controls slug. `$options` can also be +a string in which case it will be used as replacement string. The supported +options are: + +- `replacement` Replacement string, defaults to '-'. + +- `transliteratorId` A valid tranliterator id string. If default `null` + `Text::$_defaultTransliteratorId` to be used. + If `false` no transliteration will be done, only non words will be removed. + +- `preserve` Specific non-word character to preserve. Defaults to `null`. + For example, this option can be set to '.' to generate clean file names: + + ``` php + // apple-puree + Text::slug('apple purée'); + + // apple_puree + Text::slug('apple purée', '_'); + + // foo-bar.tar.gz + Text::slug('foo bar.tar.gz', ['preserve' => '.']); + ``` + +## Generating UUIDs + +### Text::uuid() + +`static` Cake\\Utility\\Text::**uuid**(): string + +The UUID method is used to generate unique identifiers as per `4122`. The +UUID is a 128-bit string in the format of +`485fc381-e790-47a3-9794-1337c0a8fe68`. : + +``` php +Text::uuid(); // 485fc381-e790-47a3-9794-1337c0a8fe68 +``` + +::: info Added in version 5.3.0 +You can now configure a custom UUID generator using dependency injection. +::: + +Starting from CakePHP 5.3.0, you can configure a custom UUID generator by +setting a closure in your configuration: + +``` php +// In your config/app.php or config/bootstrap.php +use Cake\Core\Configure; + +Configure::write('Text.uuidGenerator', function () { + // Return your custom UUID string + return \MyUuidLibrary::generate(); +}); +``` + +This allows you to integrate your own UUID generation strategy or use +third-party UUID libraries. When a custom generator is configured, it will +be used instead of the default UUID generation method. + +## Simple String Parsing + +### Text::tokenize() + +`static` Cake\\Utility\\Text::**tokenize**(string $data, string $separator = ',', string $leftBound = '(', string $rightBound = ')'): array + +Tokenizes a string using `$separator`, ignoring any instance of `$separator` +that appears between `$leftBound` and `$rightBound`. + +This method can be useful when splitting up data that has regular formatting +such as tag lists: + +``` php +$data = "cakephp 'great framework' php"; +$result = Text::tokenize($data, ' ', "'", "'"); +// Result contains +['cakephp', "'great framework'", 'php']; +``` + +### Text::parseFileSize() + +`method` Cake\\Utility\\Text::**parseFileSize**(string $size, mixed $default = false): mixed + +This method unformats a number from a human-readable byte size to an integer +number of bytes: + +``` php +$int = Text::parseFileSize('2GB'); +``` + +## Formatting Strings + +### Text::insert() + +`static` Cake\\Utility\\Text::**insert**(string $str, array $data, array $options = []): string + +The insert method is used to create string templates and to allow for key/value +replacements: + +``` php +Text::insert( + 'My name is :name and I am :age years old.', + ['name' => 'Bob', 'age' => '65'] +); +// Returns: "My name is Bob and I am 65 years old." +``` + +### Text::cleanInsert() + +`static` Cake\\Utility\\Text::**cleanInsert**(string $str, array $options): string + +Cleans up a `Text::insert` formatted string with given `$options` depending +on the 'clean' key in `$options`. The default method used is text but html is +also available. The goal of this function is to replace all whitespace and +unneeded markup around placeholders that did not get replaced by +`Text::insert`. + +You can use the following options in the options array: + +``` php +$options = [ + 'clean' => [ + 'method' => 'text', // or html + ], + 'before' => '', + 'after' => '' +]; +``` + +## Wrapping Text + +### Text::wrap() + +`static` Cake\\Utility\\Text::**wrap**(string $text, array|int $options = []): string + +Wraps a block of text to a set width and indents blocks as well. +Can intelligently wrap text so words are not sliced across lines: + +``` php +$text = 'This is the song that never ends.'; +$result = Text::wrap($text, 22); + +// Returns +This is the song that +never ends. +``` + +You can provide an array of options that control how wrapping is done. The +supported options are: + +- `width` The width to wrap to. Defaults to 72. +- `wordWrap` Whether or not to wrap whole words. Defaults to `true`. +- `indent` The character to indent lines with. Defaults to ''. +- `indentAt` The line number to start indenting text. Defaults to 0. + +### Text::wrapBlock() + +`static` Cake\\Utility\\Text::**wrapBlock**(string $text, array|int $options = []): string + +If you need to ensure that the total width of the generated block won't +exceed a certain length even with internal indentation, you need to use +`wrapBlock()` instead of `wrap()`. This is particularly useful to generate +text for the console for example. It accepts the same options as `wrap()`: + +``` php +$text = 'This is the song that never ends. This is the song that never ends.'; +$result = Text::wrapBlock($text, [ + 'width' => 22, + 'indent' => ' → ', + 'indentAt' => 1 +]); + +// Returns +This is the song that + → never ends. This + → is the song that + → never ends. +``` + + + +## Highlighting Substrings + +### Text::highlight() + +`method` Cake\\Utility\\Text::**highlight**(string $text, array|string $phrase, array $options = []): string + +Highlights `$phrase` in `$text` using the `$options['format']` string +specified or a default string. + +Options: + +- `format` string - The piece of HTML with the phrase that will be + highlighted +- `html` bool - If `true`, will ignore any HTML tags, ensuring that only + the correct text is highlighted + +Example: + +``` php +// Called as TextHelper +echo $this->Text->highlight( + $lastSentence, + 'using', + ['format' => '\1'] +); + +// Called as Text +use Cake\Utility\Text; + +echo Text::highlight( + $lastSentence, + 'using', + ['format' => '\1'] +); +``` + +Output: + +> Highlights \$phrase in \$text \using\ the +> \$options\['format'\] string specified or a default string. + +## Truncating Text + +### Text::truncate() + +`method` Cake\\Utility\\Text::**truncate**(string $text, int $length = 100, array $options = []): string + +If `$text` is longer than `$length`, this method truncates it at `$length` +and adds a suffix consisting of `'ellipsis'`, if defined. If `'exact'` is +passed as `false`, the truncation will occur at the first whitespace after the +point at which `$length` is exceeded. If `'html'` is passed as `true`, +HTML tags will be respected and will not be cut off. + +`$options` is used to pass all extra parameters, and has the following +possible keys by default, all of which are optional: + +``` php +[ + 'ellipsis' => '...', + 'exact' => true, + 'html' => false +] +``` + +Example: + +``` php +// Called as TextHelper +echo $this->Text->truncate( + 'The killer crept forward and tripped on the rug.', + 22, + [ + 'ellipsis' => '...', + 'exact' => false + ] +); + +// Called as Text +use Cake\Utility\Text; + +echo Text::truncate( + 'The killer crept forward and tripped on the rug.', + 22, + [ + 'ellipsis' => '...', + 'exact' => false + ] +); +``` + +Output: + + The killer crept... + +## Truncating the Tail of a String + +### Text::tail() + +`method` Cake\\Utility\\Text::**tail**(string $text, int $length = 100, array $options = []): string + +If `$text` is longer than `$length`, this method removes an initial +substring with length consisting of the difference and prepends a prefix +consisting of `'ellipsis'`, if defined. If `'exact'` is passed as `false`, +the truncation will occur at the first whitespace prior to the point at which +truncation would otherwise take place. + +`$options` is used to pass all extra parameters, and has the following +possible keys by default, all of which are optional: + +``` php +[ + 'ellipsis' => '...', + 'exact' => true +] +``` + +Example: + +``` php +$sampleText = 'I packed my bag and in it I put a PSP, a PS3, a TV, ' . + 'a C# program that can divide by zero, death metal t-shirts' + +// Called as TextHelper +echo $this->Text->tail( + $sampleText, + 70, + [ + 'ellipsis' => '...', + 'exact' => false + ] +); + +// Called as Text +use Cake\Utility\Text; + +echo Text::tail( + $sampleText, + 70, + [ + 'ellipsis' => '...', + 'exact' => false + ] +); +``` + +Output: + + ...a TV, a C# program that can divide by zero, death metal t-shirts + +## Extracting an Excerpt + +### Text::excerpt() + +`method` Cake\\Utility\\Text::**excerpt**(string $text, string $phrase, int $radius = 100, string $ellipsis = '…'): string + +Extracts an excerpt from `$text` surrounding the `$phrase` with a number +of characters on each side determined by `$radius`, and prefix/suffix with +`$ellipsis`. This method is especially handy for search results. The query +string or keywords can be shown within the resulting document. : + +``` php +// Called as TextHelper +echo $this->Text->excerpt($lastParagraph, 'method', 50, '...'); + +// Called as Text +use Cake\Utility\Text; + +echo Text::excerpt($lastParagraph, 'method', 50, '...'); +``` + +Output: + + ... by $radius, and prefix/suffix with $ellipsis. This method is especially + handy for search results. The query... + +## Converting an Array to Sentence Form + +### Text::toList() + +`method` Cake\\Utility\\Text::**toList**(array $list, ?string $and = null, $separator = ', '): string + +Creates a comma-separated list where the last two items are joined with 'and': + +``` php +$colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; + +// Called as TextHelper +echo $this->Text->toList($colors); + +// Called as Text +use Cake\Utility\Text; + +echo Text::toList($colors); +``` + +Output: + + red, orange, yellow, green, blue, indigo and violet + + diff --git a/docs/en/core-libraries/time.md b/docs/en/core-libraries/time.md new file mode 100644 index 0000000000..2e5793f59a --- /dev/null +++ b/docs/en/core-libraries/time.md @@ -0,0 +1,513 @@ +# Date & Time + +`class` Cake\\I18n\\**DateTime** + +If you need `TimeHelper` functionalities outside of a `View`, +use the `DateTime` class: + +``` php +use Cake\I18n\DateTime; + +class UsersController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('Authentication.Authentication'); + } + + public function afterLogin() + { + $identity = $this->Authentication->getIdentity(); + $time = new DateTime($identity->date_of_birth); + if ($time->isToday()) { + // Greet user with a happy birthday message + $this->Flash->success(__('Happy birthday to you...')); + } + } +} +``` + +Under the hood, CakePHP uses [Chronos](https://github.com/cakephp/chronos) +to power its `DateTime` utility. Anything you can do with `Chronos` and +PHP's `DateTimeImmutable`, you can do with `DateTime`. + +For more details on Chronos please see [the API documentation](https://api.cakephp.org/chronos/). + + + +## Creating DateTime Instances + +`DateTime` are immutable objects as immutability prevents accidental changes +to data, and avoids order based dependency issues. + +There are a few ways to create `DateTime` instances: + +``` php +use Cake\I18n\DateTime; + +// Create from a string datetime. +$time = DateTime::createFromFormat( + 'Y-m-d H:i:s', + '2021-01-31 22:11:30', + 'America/New_York' +); + +// Create from a timestamp and set timezone +$time = DateTime::createFromTimestamp(1612149090, 'America/New_York'); + +// Get the current time. +$time = DateTime::now(); + +// Or just use 'new' +$time = new DateTime('2021-01-31 22:11:30', 'America/New_York'); + +$time = new DateTime('2 hours ago'); +``` + +The `DateTime` class constructor can take any parameter that the internal `DateTimeImmutable` +PHP class can. When passing a number or numeric string, it will be interpreted +as a UNIX timestamp. + +In test cases, you can mock out `now()` using `setTestNow()`: + +``` php +// Fixate time. +$time = new DateTime('2021-01-31 22:11:30'); +DateTime::setTestNow($time); + +// Outputs '2021-01-31 22:11:30' +$now = DateTime::now(); +echo $now->i18nFormat('yyyy-MM-dd HH:mm:ss'); + +// Outputs '2021-01-31 22:11:30' +$now = DateTime::parse('now'); +echo $now->i18nFormat('yyyy-MM-dd HH:mm:ss'); +``` + +## Manipulation + +Remember, `DateTime` instance always return a new instance from setters +instead of modifying itself: + +``` php +$time = DateTime::now(); + +// Create and reassign a new instance +$newTime = $time->year(2013) + ->month(10) + ->day(31); +// Outputs '2013-10-31 22:11:30' +echo $newTime->i18nFormat('yyyy-MM-dd HH:mm:ss'); +``` + +You can also use the methods provided by PHP's built-in `DateTime` class: + +``` php +$time = $time->setDate(2013, 10, 31); +``` + +Failing to reassign the new `DateTime` instances will result in the +original, unmodified instance being used: + +``` php +$time->year(2013) + ->month(10) + ->day(31); +// Outputs '2021-01-31 22:11:30' +echo $time->i18nFormat('yyyy-MM-dd HH:mm:ss'); +``` + +You can create another instance with modified dates, through subtraction and +addition of their components: + +``` php +$time = DateTime::create(2021, 1, 31, 22, 11, 30); +$newTime = $time->subDays(5) + ->addHours(-2) + ->addMonths(1); +// Outputs '2/26/21, 8:11 PM' +echo $newTime; + +// Using strtotime strings. +$newTime = $time->modify('+1 month -5 days -2 hours'); +// Outputs '2/26/21, 8:11 PM' +echo $newTime; +``` + +You can get the internal components of a date by accessing its properties: + +``` php +$time = DateTime::create(2021, 1, 31, 22, 11, 30); +echo $time->year; // 2021 +echo $time->month; // 1 +echo $time->day; // 31 +echo $time->timezoneName; // America/New_York +``` + +## Formatting + +### DateTime::setJsonEncodeFormat() + +`static` Cake\\I18n\\DateTime::**setJsonEncodeFormat**($format): void + +This method sets the default format used when converting an object to json: + +``` php +DateTime::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss'); // For any immutable DateTime +Date::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss'); // For any mutable Date + +$time = DateTime::parse('2021-01-31 22:11:30'); +echo json_encode($time); // Outputs '2021-01-31 22:11:30' + +Date::setJsonEncodeFormat(static function($time) { + return $time->format(DATE_ATOM); +}); +``` + +> [!NOTE] +> This method must be called statically. + +> [!NOTE] +> Be aware that this is not a PHP Datetime string format! You need to use a +> ICU date formatting string as specified in the following resource: +> . + +::: info Changed in version 4.1.0 +The `callable` parameter type was added. +::: + +### DateTime::i18nFormat() + +`method` Cake\\I18n\\DateTime::**i18nFormat**($format = null, $timezone = null, $locale = null): string|int + +A very common thing to do with `Time` instances is to print out formatted +dates. CakePHP makes this a snap: + +``` php +$time = DateTime::parse('2021-01-31 22:11:30'); + +// Prints a localized datetime stamp. Outputs '1/31/21, 10:11 PM' +echo $time; + +// Outputs '1/31/21, 10:11 PM' for the en-US locale +echo $time->i18nFormat(); + +// Use the full date and time format. Outputs 'Sunday, January 31, 2021 at 10:11:30 PM Eastern Standard Time' +echo $time->i18nFormat(\IntlDateFormatter::FULL); + +// Use full date but short time format. Outputs 'Sunday, January 31, 2021 at 10:11 PM' +echo $time->i18nFormat([\IntlDateFormatter::FULL, \IntlDateFormatter::SHORT]); + +// Outputs '2021-Jan-31 22:11:30' +echo $time->i18nFormat('yyyy-MMM-dd HH:mm:ss'); +``` + +It is possible to specify the desired format for the string to be displayed. +You can either pass [IntlDateFormatter constants](https://www.php.net/manual/en/class.intldateformatter.php) as the first +argument of this function, or pass a full ICU date formatting string as +specified in the following resource: +. + +You can also format dates with non-gregorian calendars: + +``` php +// On ICU version 66.1 +$time = DateTime::create(2021, 1, 31, 22, 11, 30); + +// Outputs 'Sunday, Bahman 12, 1399 AP at 10:11:30 PM Eastern Standard Time' +echo $time->i18nFormat(\IntlDateFormatter::FULL, null, 'en-IR@calendar=persian'); + +// Outputs 'Sunday, January 31, 3 Reiwa at 10:11:30 PM Eastern Standard Time' +echo $time->i18nFormat(\IntlDateFormatter::FULL, null, 'en-JP@calendar=japanese'); + +// Outputs 'Sunday, Twelfth Month 19, 2020(geng-zi) at 10:11:30 PM Eastern Standard Time' +echo $time->i18nFormat(\IntlDateFormatter::FULL, null, 'en-CN@calendar=chinese'); + +// Outputs 'Sunday, Jumada II 18, 1442 AH at 10:11:30 PM Eastern Standard Time' +echo $time->i18nFormat(\IntlDateFormatter::FULL, null, 'en-SA@calendar=islamic'); +``` + +The following calendar types are supported: + +- japanese +- buddhist +- chinese +- persian +- indian +- islamic +- hebrew +- coptic +- ethiopic + +> [!NOTE] +> For constant strings i.e. IntlDateFormatter::FULL Intl uses ICU library +> that feeds its data from CLDR () which version +> may vary depending on PHP installation and give different results. + +### DateTime::nice() + +`method` Cake\\I18n\\DateTime::**nice**(): string + +Print out a predefined 'nice' format: + +``` php +$time = DateTime::parse('2021-01-31 22:11:30', new \DateTimeZone('America/New_York')); + +// Outputs 'Jan 31, 2021, 10:11 PM' in en-US +echo $time->nice(); +``` + +You can alter the timezone in which the date is displayed without altering the +`DateTime` object itself. This is useful when you store dates in one timezone, but +want to display them in a user's own timezone: + +``` php +// Outputs 'Monday, February 1, 2021 at 4:11:30 AM Central European Standard Time' +echo $time->i18nFormat(\IntlDateFormatter::FULL, 'Europe/Paris'); + +// Outputs 'Monday, February 1, 2021 at 12:11:30 PM Japan Standard Time' +echo $time->i18nFormat(\IntlDateFormatter::FULL, 'Asia/Tokyo'); + +// Timezone is unchanged. Outputs 'America/New_York' +echo $time->timezoneName; +``` + +Leaving the first parameter as `null` will use the default formatting string: + +``` php +// Outputs '2/1/21, 4:11 AM' +echo $time->i18nFormat(null, 'Europe/Paris'); +``` + +Finally, it is possible to use a different locale for displaying a date: + +``` php +// Outputs 'lundi 1 février 2021 à 04:11:30 heure normale d’Europe centrale' +echo $time->i18nFormat(\IntlDateFormatter::FULL, 'Europe/Paris', 'fr-FR'); + +// Outputs '1 févr. 2021 à 04:11' +echo $time->nice('Europe/Paris', 'fr-FR'); +``` + +### Setting the Default Locale and Format String + +The default locale in which dates are displayed when using `nice` +`i18nFormat` is taken from the directive +[intl.default_locale](https://www.php.net/manual/en/intl.configuration.php#ini.intl.default-locale). +You can, however, modify this default at runtime: + +``` php +DateTime::setDefaultLocale('es-ES'); +Date::setDefaultLocale('es-ES'); + +// Outputs '31 ene. 2021 22:11' +echo $time->nice(); +``` + +From now on, datetimes will be displayed in the Spanish preferred format unless +a different locale is specified directly in the formatting method. + +Likewise, it is possible to alter the default formatting string to be used for +`i18nFormat`: + +``` php +DateTime::setToStringFormat(\IntlDateFormatter::SHORT); // For any DateTime +Date::setToStringFormat(\IntlDateFormatter::SHORT); // For any Date + +// The same method exists on Date, and DateTime +DateTime::setToStringFormat([ + \IntlDateFormatter::FULL, + \IntlDateFormatter::SHORT +]); +// Outputs 'Sunday, January 31, 2021 at 10:11 PM' +echo $time; + +// The same method exists on Date and DateTime +DateTime::setToStringFormat("EEEE, MMMM dd, yyyy 'at' KK:mm:ss a"); +// Outputs 'Sunday, January 31, 2021 at 10:11:30 PM' +echo $time; +``` + +It is recommended to always use the constants instead of directly passing a date +format string. + +> [!NOTE] +> Be aware that this is not a PHP Datetime string format! You need to use a +> ICU date formatting string as specified in the following resource: +> . + +### Formatting Relative Times + +### DateTime::timeAgoInWords() + +`method` Cake\\I18n\\DateTime::**timeAgoInWords**(array $options = []): string + +Often it is useful to print times relative to the present: + +``` php +$time = new DateTime('Jan 31, 2021'); +// On June 12, 2021, this would output '4 months, 1 week, 6 days ago' +echo $time->timeAgoInWords( + ['format' => 'MMM d, YYY', 'end' => '+1 year'] +); +``` + +The `end` option lets you define at which point after which relative times +should be formatted using the `format` option. The `accuracy` option lets +us control what level of detail should be used for each interval range: + +``` php +// Outputs '4 months ago' +echo $time->timeAgoInWords([ + 'accuracy' => ['month' => 'month'], + 'end' => '1 year' +]); +``` + +By setting `accuracy` to a string, you can specify what is the maximum level +of detail you want output: + +``` php +$time = new DateTime('+23 hours'); +// Outputs 'in about a day' +echo $time->timeAgoInWords([ + 'accuracy' => 'day' +]); +``` + +## Conversion + +### DateTime::toQuarter() + +`method` Cake\\I18n\\DateTime::**toQuarter**(): int|array + +### DateTime::toQuarterRange() + +`method` Cake\\I18n\\DateTime::**toQuarterRange**(): array + +Once created, you can convert `DateTime` instances into timestamps or quarter +values: + +``` php +$time = new DateTime('2021-01-31'); +echo $time->toQuarter(); // Outputs '1' +echo $time->toUnixString(); // Outputs '1612069200' +``` + +::: info Added in version 5.3.0 +The `toQuarterRange()` method was added. +::: + +You can also get the date range for a quarter: + +``` php +$time = new DateTime('2021-01-31'); +$range = $time->toQuarterRange(); +// Outputs ['2021-01-01', '2021-03-31'] + +$time = new DateTime('2021-12-25'); +$range = $time->toQuarterRange(); +// Outputs ['2021-10-01', '2021-12-31'] +``` + +## Comparing With the Present + +### DateTime::isYesterday() + +`method` Cake\\I18n\\DateTime::**isYesterday**() + +### DateTime::isThisWeek() + +`method` Cake\\I18n\\DateTime::**isThisWeek**() + +### DateTime::isThisMonth() + +`method` Cake\\I18n\\DateTime::**isThisMonth**() + +### DateTime::isThisYear() + +`method` Cake\\I18n\\DateTime::**isThisYear**() + +You can compare a `DateTime` instance with the present in a variety of ways: + +``` php +$time = new DateTime('+3 days'); + +debug($time->isYesterday()); +debug($time->isThisWeek()); +debug($time->isThisMonth()); +debug($time->isThisYear()); +``` + +Each of the above methods will return `true`/`false` based on whether or +not the `DateTime` instance matches the present. + +## Comparing With Intervals + +### DateTime::isWithinNext() + +`method` Cake\\I18n\\DateTime::**isWithinNext**($interval) + +You can see if a `DateTime` instance falls within a given range using +`wasWithinLast()` and `isWithinNext()`: + +``` php +$time = new DateTime('+3 days'); + +// Within 2 days. Outputs 'false' +debug($time->isWithinNext('2 days')); + +// Within 2 next weeks. Outputs 'true' +debug($time->isWithinNext('2 weeks')); +``` + +### DateTime::wasWithinLast() + +`method` Cake\\I18n\\DateTime::**wasWithinLast**($interval) + +You can also compare a `DateTime` instance within a range in the past: + +``` php +$time = new DateTime('-72 hours'); + +// Within past 2 days. Outputs 'false' +debug($time->wasWithinLast('2 days')); + +// Within past 3 days. Outputs 'true' +debug($time->wasWithinLast('3 days')); + +// Within past 2 weeks. Outputs 'true' +debug($time->wasWithinLast('2 weeks')); +``` + + + +## Date + +`class` Cake\\I18n\\**Date** + +The immutable `Date` class in CakePHP represents calendar dates unaffected by +time and timezones. The `Date` class wraps the `Cake\Chronos\ChronosDate` class. + +> [!NOTE] +> Unlike the `DateTime` class, `Date` does not extends the `DateTimeInterface`. +> So you cannot cannot directly compare a `Date` instance with a `DateTime` instance. +> But you can do comparisons like `$dateTime->toNative() > $date->toNative()`. + +## Time + +`class` Cake\\I18n\\**Time** + +The `Time` class represents clock times independent of date or time zones +Similar to the `DateTime` and `` `Date `` classes, the `Time` class is also immutable. +It wraps the `Cake\Chronos\ChronosTime` class. + +## Accepting Localized Request Data + +When creating text inputs that manipulate dates, you'll probably want to accept +and parse localized datetime strings. See the [Parsing Localized Dates](../core-libraries/internationalization-and-localization#parsing-localized-dates). + +## Supported Timezones + +CakePHP supports all valid PHP timezones. For a list of supported timezones, [see this page](https://php.net/manual/en/timezones.php). diff --git a/docs/en/core-libraries/validation.md b/docs/en/core-libraries/validation.md new file mode 100644 index 0000000000..97cbcfcb3d --- /dev/null +++ b/docs/en/core-libraries/validation.md @@ -0,0 +1,677 @@ +# Validation + +The validation package in CakePHP provides features to build validators that can +validate arbitrary arrays of data with ease. You can find a [list of available +Validation rules in the API](https://api.cakephp.org/5.x/class-Cake.Validation.Validation.html). + + + +## Creating Validators + +`class` Cake\\Validation\\**Validator** + +Validator objects define the rules that apply to a set of fields. +Validator objects contain a mapping between fields and validation sets. In +turn, the validation sets contain a collection of rules that apply to the field +they are attached to. Creating a validator is simple: + +``` php +use Cake\Validation\Validator; + +$validator = new Validator(); +``` + +Once created, you can start defining sets of rules for the fields you want to +validate: + +``` php +$validator + ->requirePresence('title') + ->notEmptyString('title', 'Please fill this field') + ->add('title', [ + 'length' => [ + 'rule' => ['minLength', 10], + 'message' => 'Titles need to be at least 10 characters long', + ] + ]) + ->allowEmptyDateTime('published') + ->add('published', 'boolean', [ + 'rule' => 'boolean', + ]) + ->requirePresence('body') + ->add('body', 'length', [ + 'rule' => ['minLength', 50], + 'message' => 'Articles must have a substantial body.', + ]); +``` + +As seen in the example above, validators are built with a fluent interface that +allows you to define rules for each field you want to validate. + +There were a few methods called in the example above, so let's go over the +various features. The `add()` method allows you to add new rules to +a validator. You can either add rules individually or in groups as seen above. + +### Requiring Field Presence + +The `requirePresence()` method requires the field to be present in any +validated array. If the field is absent, validation will fail. The +`requirePresence()` method has 4 modes: + +- `true` The field's presence is always required. +- `false` The field's presence is not required. +- `create` The field's presence is required when validating a **create** + operation. +- `update` The field's presence is required when validating an **update** + operation. + +By default, `true` is used. Key presence is checked by using +`array_key_exists()` so that null values will count as present. You can set +the mode using the second parameter: + +``` php +$validator->requirePresence('author_id', 'create'); +``` + +If you have multiple fields that are required, you can define them as a list: + +``` php +// Define multiple fields for create +$validator->requirePresence(['author_id', 'title'], 'create'); + +// Define multiple fields for mixed modes +$validator->requirePresence([ + 'author_id' => [ + 'mode' => 'create', + 'message' => 'An author is required.', + ], + 'published' => [ + 'mode' => 'update', + 'message' => 'The published state is required.', + ], +]); +``` + +### Allowing Empty Fields + +Validators offer several methods to control which fields accept empty values and +which empty values are accepted and not forwarded to other validation rules for +the named field. CakePHP provides empty value support for different shapes +of data: + +1. `allowEmptyString()` Should be used when you want to only accept + an empty string. +2. `allowEmptyArray()` Should be used when you want to accept an array. +3. `allowEmptyDate()` Should be used when you want to accept an empty string, + or an array that is marshalled into a date field. +4. `allowEmptyTime()` Should be used when you want to accept an empty string, + or an array that is marshalled into a time field. +5. `allowEmptyDateTime()` Should be used when you want to accept an empty + string or an array that is marshalled into a datetime or timestamp field. +6. `allowEmptyFile()` Should be used when you want to accept an array that + contains an empty uploaded file. + +You also can use following specific validators: `notEmptyString()`, `notEmptyArray()`, `notEmptyFile()`, `notEmptyDate()`, `notEmptyTime()`, `notEmptyDateTime()`. + +The `allowEmpty*` methods support a `when` parameter that allows you to control +when a field can or cannot be empty: + +- `false` The field is not allowed to be empty. +- `create` The field can be empty when validating a **create** + operation. +- `update` The field can be empty when validating an **update** + operation. +- A callback that returns `true` or `false` to indicate whether a field is + allowed to be empty. See the [Conditional Validation](#conditional-validation) section for examples on + how to use this parameter. + +An example of these methods in action is: + +``` php +$validator->allowEmptyDateTime('published') + ->allowEmptyString('title', 'Title cannot be empty', false) + ->allowEmptyString('body', 'Body cannot be empty', 'update') + ->allowEmptyFile('header_image', 'update'); + ->allowEmptyDateTime('posted', 'update'); +``` + +### Adding Validation Rules + +The `Validator` class provides methods that make building validators simple +and expressive. For example adding validation rules to a username could look +like: + +``` php +$validator = new Validator(); +$validator + ->email('username') + ->ascii('username') + ->lengthBetween('username', [4, 8]); +``` + +See the [Validator API documentation](https://api.cakephp.org/5.x/class-Cake.Validation.Validator.html) for the +full set of validator methods. + + + +### Using Custom Validation Rules + +In addition to using methods on the `Validator`, and coming from providers, you +can also use any callable, including anonymous functions, as validation rules: + +``` php +// Use a global function +$validator->add('title', 'custom', [ + 'rule' => 'validate_title', + 'message' => 'The title is not valid' +]); + +// Use an array callable that is not in a provider +$validator->add('title', 'custom', [ + 'rule' => [$this, 'method'], + 'message' => 'The title is not valid' +]); + +// Use a closure +$extra = 'Some additional value needed inside the closure'; +$validator->add('title', 'custom', [ + 'rule' => function ($value, $context) use ($extra) { + // Custom logic that returns true/false + }, + 'message' => 'The title is not valid' +]); + +// Use a rule from a custom provider +$validator->add('title', 'custom', [ + 'rule' => 'customRule', + 'provider' => 'custom', + 'message' => 'The title is not unique enough' +]); +``` + +Closures or callable methods will receive 2 arguments when called. The first +will be the value for the field being validated. The second is a context array +containing data related to the validation process: + +- **data**: The original data passed to the validation method, useful if you + plan to create rules comparing values. +- **providers**: The complete list of rule provider objects, useful if you + need to create complex rules by calling multiple providers. +- **newRecord**: Whether the validation call is for a new record or + a preexisting one. +- **entity**: The entity being validated if provided to `validate()`. + +Closures should return boolean true if the validation passes. If it fails, +return boolean false or for a custom error message return a string, see the +[Conditional/Dynamic Error Messages](#dynamic_validation_error_messages) +section for further details. + +::: info Changed in version 5.3.0 +The `entity` key was added to validation context. +::: + + + +### Conditional/Dynamic Error Messages + +Validation rule methods, being it [custom callables](#custom-validation-rules), +or [methods supplied by providers](#adding-validation-providers), can either +return a boolean, indicating whether the validation succeeded, or they can return +a string, which means that the validation failed, and that the returned string +should be used as the error message. + +Possible existing error messages defined via the `message` option will be +overwritten by the ones returned from the validation rule method: + +``` php +$validator->add('length', 'custom', [ + 'rule' => function ($value, $context) { + if (!$value) { + return false; + } + + if ($value < 10) { + return 'Error message when value is less than 10'; + } + + if ($value > 20) { + return 'Error message when value is greater than 20'; + } + + return true; + }, + 'message' => 'Generic error message used when `false` is returned' +]); +``` + + + +### Conditional Validation + +When defining validation rules, you can use the `on` key to define when +a validation rule should be applied. If left undefined, the rule will always be +applied. Other valid values are `create` and `update`. Using one of these +values will make the rule apply to only create or update operations. + +Additionally, you can provide a callable function that will determine whether or +not a particular rule should be applied: + +``` php +$validator->add('picture', 'file', [ + 'rule' => ['mimeType', ['image/jpeg', 'image/png']], + 'on' => function ($context) { + return !empty($context['data']['show_profile_picture']); + } +]); +``` + +You can access the other submitted field values using the `$context['data']` +array. The above example will make the rule for 'picture' optional depending on +whether the value for `show_profile_picture` is empty. You could also use the +`uploadedFile` validation rule to create optional file upload inputs: + +``` php +$validator->add('picture', 'file', [ + 'rule' => ['uploadedFile', ['optional' => true]], +]); +``` + +The `allowEmpty*`, `notEmpty*` and `requirePresence()` methods will also +accept a callback function as their last argument. If present, the callback +determines whether or not the rule should be applied. For example, a field is +sometimes allowed to be empty: + +``` php +$validator->allowEmptyString('tax', 'This field is required', function ($context) { + return !$context['data']['is_taxable']; +}); +``` + +Likewise, a field can be required to be populated when certain conditions are +met: + +``` php +$validator->notEmptyString('email_frequency', 'This field is required', function ($context) { + return !empty($context['data']['wants_newsletter']); +}); +``` + +In the above example, the `email_frequency` field cannot be left empty if the +the user wants to receive the newsletter. + +Further it's also possible to require a field to be present under certain +conditions only: + +``` php +$validator->requirePresence('full_name', function ($context) { + if (isset($context['data']['action'])) { + return $context['data']['action'] === 'subscribe'; + } + + return false; +}); +$validator->requirePresence('email'); +``` + +This would require the `full_name` field to be present only in case the user +wants to create a subscription, while the `email` field would always be +required. + +The `$context` parameter passed to custom conditional callbacks contains the +following keys: + +- `data` The data being validated. +- `newRecord` a boolean indicating whether a new or existing record is being + validated. +- `field` The current field being validated. +- `providers` The validation providers attached to the current validator. + +### Marking Rules as the Last to Run + +When fields have multiple rules, each validation rule will be run even if the +previous one has failed. This allows you to collect as many validation errors as +you can in a single pass. If you want to stop execution after +a specific rule has failed, you can set the `last` option to `true`: + +``` php +$validator = new Validator(); +$validator + ->add('body', [ + 'minLength' => [ + 'rule' => ['minLength', 10], + 'last' => true, + 'message' => 'Comments must have a substantial body.', + ], + 'maxLength' => [ + 'rule' => ['maxLength', 250], + 'message' => 'Comments cannot be too long.', + ], + ]); +``` + +If the minLength rule fails in the example above, the maxLength rule will not be +run. + +## Make Rules 'last' by default + +You can have the `last` option automatically applied to each rule you can use +the `setStopOnFailure()` method to enable this behavior: + +``` php +public function validationDefault(Validator $validator): Validator +{ + $validator + ->setStopOnFailure() + ->requirePresence('email', 'create') + ->notBlank('email') + ->email('email'); + + return $validator; +} +``` + +When enabled all fields will stop validation on the first failing rule instead +of checking all possible rules. In this case only a single error message will +appear under the form field. + + + +### Adding Validation Providers + +The `Validator`, `ValidationSet` and `ValidationRule` classes do not +provide any validation methods themselves. Validation rules come from +'providers'. You can bind any number of providers to a Validator object. +Validator instances come with a 'default' provider setup automatically. The +default provider is mapped to the `Cake\Validation\Validation` +class. This makes it simple to use the methods on that class as validation +rules. When using Validators and the ORM together, additional providers are +configured for the table and entity objects. You can use the `setProvider()` +method to add any additional providers your application needs: + +``` php +$validator = new Validator(); + +// Use an object instance. +$validator->setProvider('custom', $myObject); + +// Use a class name. Methods must be static. +$validator->setProvider('custom', 'App\Model\Validation'); +``` + +Validation providers can be objects, or class names. If a class name is used the +methods must be static. To use a provider other than 'default', be sure to set +the `provider` key in your rule: + +``` php +// Use a rule from the table provider +$validator->add('title', 'custom', [ + 'rule' => 'customTableMethod', + 'provider' => 'table' +]); +``` + +If you wish to add a `provider` to all `Validator` objects that are created +in the future, you can use the `addDefaultProvider()` method as follows: + +``` php +use Cake\Validation\Validator; + +// Use an object instance. +Validator::addDefaultProvider('custom', $myObject); + +// Use a class name. Methods must be static. +Validator::addDefaultProvider('custom', 'App\Model\Validation'); +``` + +> [!NOTE] +> DefaultProviders must be added before the `Validator` object is created +> therefore **config/bootstrap.php** is the best place to set up your +> default providers. + +You can use the [Localized plugin](https://github.com/cakephp/localized) to +get providers based on countries. With this plugin, you'll be able to validate +model fields, depending on a country, ie: + +``` php +namespace App\Model\Table; + +use Cake\ORM\Table; +use Cake\Validation\Validator; + +class PostsTable extends Table +{ + public function validationDefault(Validator $validator): Validator + { + // add the provider to the validator + $validator->setProvider('fr', 'Cake\Localized\Validation\FrValidation'); + // use the provider in a field validation rule + $validator->add('phoneField', 'myCustomRuleNameForPhone', [ + 'rule' => 'phone', + 'provider' => 'fr' + ]); + + return $validator; + } +} +``` + +The localized plugin uses the two letter ISO code of the countries for +validation, like en, fr, de. + +There are a few methods that are common to all classes, defined through the +[ValidationInterface interface](https://github.com/cakephp/localized/blob/master/src/Validation/ValidationInterface.php): + +``` text +phone() to check a phone number +postal() to check a postal code +personId() to check a country specific person ID +``` + +### Nesting Validators + +When validating [Modelless Forms](../core-libraries/form) with nested data, or when working +with models that contain array data types, it is necessary to validate the +nested data you have. CakePHP makes it simple to add validators to specific +attributes. For example, assume you are working with a non-relational database +and need to store an article and its comments: + +``` php +$data = [ + 'title' => 'Best article', + 'comments' => [ + ['comment' => ''], + ], +]; +``` + +To validate the comments you would use a nested validator: + +``` php +$validator = new Validator(); +$validator->add('title', 'not-blank', ['rule' => 'notBlank']); + +$commentValidator = new Validator(); +$commentValidator->add('comment', 'not-blank', ['rule' => 'notBlank']); + +// Connect the nested validators. +$validator->addNestedMany('comments', $commentValidator); + +// Get all errors including those from nested validators. +$validator->validate($data); +``` + +You can create 1:1 'relationships' with `addNested()` and 1:N 'relationships' +with `addNestedMany()`. With both methods, the nested validator's errors will +contribute to the parent validator's errors and influence the final result. +Like other validator features, nested validators support error messages and +conditional application: + +``` php +$validator->addNestedMany( + 'comments', + $commentValidator, + 'Invalid comment', + 'create' +); +``` + +The error message for a nested validator can be found in the `_nested` key. + + + +### Creating Reusable Validators + +While defining validators inline where they are used makes for good example +code, it doesn't lead to maintainable applications. Instead, you should +create `Validator` sub-classes for your reusable validation logic: + +``` php +// In src/Model/Validation/ContactValidator.php +namespace App\Model\Validation; + +use Cake\Validation\Validator; + +class ContactValidator extends Validator +{ + public function __construct() + { + parent::__construct(); + // Add validation rules here. + } +} +``` + +## Validating Data + +Now that you've created a validator and added the rules you want to it, you can +start using it to validate data. Validators are able to validate array +data. For example, if you wanted to validate a contact form before creating and +sending an email you could do the following: + +``` php +use Cake\Validation\Validator; + +$validator = new Validator(); +$validator + ->requirePresence('email') + ->add('email', 'validFormat', [ + 'rule' => 'email', + 'message' => 'E-mail must be valid', + ]) + ->requirePresence('name') + ->notEmptyString('name', 'We need your name.') + ->requirePresence('comment') + ->notEmptyString('comment', 'You need to give a comment.'); + +$errors = $validator->validate($this->request->getData()); +if (empty($errors)) { + // Send an email. +} +``` + +The `getErrors()` method will return a non-empty array when there are validation +failures. The returned array of errors will be structured like: + +``` php +$errors = [ + 'email' => ['E-mail must be valid'], +]; +``` + +If you have multiple errors on a single field, an array of error messages will +be returned per field. By default the `getErrors()` method applies rules for +the 'create' mode. If you'd like to apply 'update' rules you can do the +following: + +``` php +$errors = $validator->validate($this->request->getData(), false); +if (!$errors) { + // Send an email. +} +``` + +> [!NOTE] +> If you need to validate entities you should use methods like +> `Cake\ORM\Table::newEntity()`, +> `Cake\ORM\Table::newEntities()`, +> `Cake\ORM\Table::patchEntity()`, +> `Cake\ORM\Table::patchEntities()` +> as they are designed for that. + +## Validating Entity Data + +Validation is meant for checking request data coming from forms or other user +interfaces used to populate the entities. + +The request data is validated automatically when using the `newEntity()`, +`newEntities()`, `patchEntity()` or `patchEntities()` methods of `Table` class: + +``` php +// In the ArticlesController class +$article = $this->Articles->newEntity($this->request->getData()); +if ($article->getErrors()) { + // Do work to show error messages. +} +``` + +Similarly, when you need to validate multiple entities at a time, you can +use the `newEntities()` method: + +``` php +// In the ArticlesController class +$entities = $this->Articles->newEntities($this->request->getData()); +foreach ($entities as $entity) { + if (!$entity->getErrors()) { + $this->Articles->save($entity); + } +} +``` + +The `newEntity()`, `patchEntity()`, `newEntities()` and `patchEntities()` +methods allow you to specify which associations are validated, and which +validation sets to apply using the `options` parameter: + +``` php +$valid = $this->Articles->newEntity($article, [ + 'associated' => [ + 'Comments' => [ + 'associated' => ['User'], + 'validate' => 'special', + ], + ], +]); +``` + +Apart from validating user provided data maintaining integrity of data regardless +where it came from is important. To solve this problem CakePHP offers a second +level of validation which is called "application rules". You can read more about +them in the [Applying Application Rules](../orm/validation#application-rules) section. + +## Core Validation Rules + +CakePHP provides a basic suite of validation methods in the `Validation` +class. The Validation class contains a variety of static methods that provide +validators for several common validation situations. + +The [API documentation](https://api.cakephp.org/5.x/class-Cake.Validation.Validation.html) for the +`Validation` class provides a good list of the validation rules that are +available, and their basic usage. + +Some of the validation methods accept additional parameters to define boundary +conditions or valid options. You can provide these boundary conditions and +options as follows: + +``` php +$validator = new Validator(); +$validator + ->add('title', 'minLength', [ + 'rule' => ['minLength', 10], + ]) + ->add('rating', 'validValue', [ + 'rule' => ['range', 1, 5], + ]); +``` + +Core rules that take additional parameters should have an array for the +`rule` key that contains the rule as the first element, and the additional +parameters as the remaining parameters. diff --git a/docs/en/core-libraries/xml.md b/docs/en/core-libraries/xml.md new file mode 100644 index 0000000000..3993a274b0 --- /dev/null +++ b/docs/en/core-libraries/xml.md @@ -0,0 +1,202 @@ +# Xml + +`class` Cake\\Utility\\**Xml** + +The Xml class allows you to transform arrays into SimpleXMLElement or +DOMDocument objects, and back into arrays again. + +## Loading XML documents + +### Xml::build() + +`static` Cake\\Utility\\Xml::**build**($input, array $options = []): SimpleXMLElement|DOMDocument + +You can load XML-ish data using `Xml::build()`. Depending on your +`$options` parameter, this method will return a SimpleXMLElement (default) +or DOMDocument object. You can use `Xml::build()` to build XML +objects from a variety of sources. For example, you can load XML from +strings: + +``` php +$text = ' + + 1 + Best post + ... +'; +$xml = Xml::build($text); +``` + +You can also build Xml objects from local files by overriding the default option: + +``` php +// Local file +$xml = Xml::build('/home/awesome/unicorns.xml', ['readFile' => true]); +``` + +You can also build Xml objects using an array: + +``` php +$data = [ + 'post' => [ + 'id' => 1, + 'title' => 'Best post', + 'body' => ' ... ', + ] +]; +$xml = Xml::build($data); +``` + +If your input is invalid, the Xml class will throw an exception: + +``` php +$xmlString = 'What is XML?'; +try { + $xmlObject = Xml::build($xmlString); // Here will throw an exception +} catch (\Cake\Utility\Exception\XmlException $e) { + throw new InternalErrorException(); +} +``` + +> [!NOTE] +> [DOMDocument](https://php.net/domdocument) and +> [SimpleXML](https://php.net/simplexml) implement different APIs. +> Be sure to use the correct methods on the object you request from Xml. + +## Loading HTML documents + +HTML documents can be parsed into `SimpleXmlElement` or `DOMDocument` +objects with `loadHtml()`: + +``` php +$html = Xml::loadHtml($htmlString, ['return' => 'domdocument']); +``` + +By default entity loading and huge document parsing are disabled. These modes +can be enabled with the `loadEntities` and `parseHuge` options respectively. + +## Transforming a XML String in Array + +### Xml::toArray() + +`static` Cake\\Utility\\Xml::**toArray**($obj): array + +Converting XML strings into arrays is simple with the Xml class as well. By +default you'll get a SimpleXml object back: + +``` php +$xmlString = 'value'; +$xmlArray = Xml::toArray(Xml::build($xmlString)); +``` + +If your XML is invalid a `Cake\Utility\Exception\XmlException` will be raised. + +## Transforming an Array into a String of XML + +``` php +$xmlArray = ['root' => ['child' => 'value']]; +// You can use Xml::build() too. +$xmlObject = Xml::fromArray($xmlArray, ['format' => 'tags']); +$xmlString = $xmlObject->asXML(); +``` + +Your array must have only one element in the "top level" and it can not be +numeric. If the array is not in this format, Xml will throw an exception. +Examples of invalid arrays: + +``` text +// Top level with numeric key +[ + ['key' => 'value'] +]; + +// Multiple keys in top level +[ + 'key1' => 'first value', + 'key2' => 'other value' +]; +``` + +By default array values will be output as XML tags. If you want to define +attributes or text values you can prefix the keys that are supposed to be +attributes with `@`. For value text, use `@` as the key: + +``` php +$xmlArray = [ + 'project' => [ + '@id' => 1, + 'name' => 'Name of project, as tag', + '@' => 'Value of project', + ], +]; +$xmlObject = Xml::fromArray($xmlArray); +$xmlString = $xmlObject->asXML(); +``` + +The content of `$xmlString` will be: + +``` php + +Value of projectName of project, as tag +``` + +### Using Namespaces + +To use XML Namespaces, create a key in your array with the name `xmlns:` +in a generic namespace or input the prefix `xmlns:` in a custom namespace. See +the samples: + +``` php +$xmlArray = [ + 'root' => [ + 'xmlns:' => 'https://cakephp.org', + 'child' => 'value', + ] +]; +$xml1 = Xml::fromArray($xmlArray); + +$xmlArray( + 'root' => [ + 'tag' => [ + 'xmlns:pref' => 'https://cakephp.org', + 'pref:item' => [ + 'item 1', + 'item 2' + ] + ] + ] +); +$xml2 = Xml::fromArray($xmlArray); +``` + +The value of `$xml1` and `$xml2` will be, respectively: + +``` php + +value + + +item 1item 2 +``` + +### Creating a Child + +After you have created your XML document, you just use the native interfaces for +your document type to add, remove, or manipulate child nodes: + +``` php +// Using SimpleXML +$myXmlOriginal = 'value'; +$xml = Xml::build($myXmlOriginal); +$xml->root->addChild('young', 'new value'); + +// Using DOMDocument +$myXmlOriginal = 'value'; +$xml = Xml::build($myXmlOriginal, ['return' => 'domdocument']); +$child = $xml->createElement('young', 'new value'); +$xml->firstChild->appendChild($child); +``` + +> [!TIP] +> After manipulating your XML using SimpleXMLElement or DomDocument you can +> use `Xml::toArray()` without a problem. diff --git a/docs/en/debug-kit.md b/docs/en/debug-kit.md new file mode 100644 index 0000000000..0dd94f3d73 --- /dev/null +++ b/docs/en/debug-kit.md @@ -0,0 +1,3 @@ +# Debug Kit + +This page has [moved](https://book.cakephp.org/debugkit/5.x/en/). diff --git a/docs/en/deployment.md b/docs/en/deployment.md new file mode 100644 index 0000000000..3eff1c1885 --- /dev/null +++ b/docs/en/deployment.md @@ -0,0 +1,140 @@ +# Deployment + +Once your app is ready to be deployed there are a few things you should do. + +## Moving files + +You can clone your repository onto your production server and then checkout the +commit/tag you want to run. Then, run `composer install`. While this requires +some knowledge about git and an existing install of `git` and `composer` +this process will take care about library dependencies and file and folder +permissions. + +Be aware that when deploying via FTP you will have to fix file and +folder permissions. + +You can also use this deployment technique to setup a staging or demo-server +(pre-production) and keep it in sync with your local environment. + +## Adjusting Configuration + +You'll want to make a few adjustments to your application's configuration for +a production environment. The value of `debug` is extremely important. +Turning debug = `false` disables a number of development features that should +never be exposed to the Internet at large. Disabling debug changes the following +features: + +- Debug messages, created with `pr()`, `debug()` and `dd()` are + disabled. +- Core CakePHP caches duration are defaulted to 365 days, instead of 10 seconds + as in development. +- Error views are less informative, and generic error pages are displayed + instead of detailed error messages with stack traces. +- PHP Warnings and Errors are not displayed. + +In addition to the above, many plugins and application extensions use `debug` +to modify their behavior. + +You can check against an environment variable to set the debug level dynamically +between environments. This will avoid deploying an application with debug +`true` and also save yourself from having to change the debug level each time +before deploying to a production environment. + +For example, you can set an environment variable in your Apache configuration: + + SetEnv CAKEPHP_DEBUG 1 + +And then you can set the debug level dynamically in **app_local.php**: + +``` php +$debug = (bool)getenv('CAKEPHP_DEBUG'); + +return [ + 'debug' => $debug, + ..... +]; +``` + +It is recommended that you put configuration that is shared across all +of your application's environments in **config/app.php**. For configuration that +varies between environments either use **config/app_local.php** or environment +variables. + +## Check Your Security + +If you're throwing your application out into the wild, it's a good idea to make +sure it doesn't have any obvious leaks: + +- Ensure you are using the [Csrf Middleware](security/csrf#csrf-middleware) component or middleware. +- You may want to enable the [Form Protection Component](controllers/components/form-protection) component. + It can help prevent several types of form tampering and reduce the possibility + of mass-assignment issues. +- Ensure your models have the correct [Validation](core-libraries/validation) rules + enabled. +- Check that only your `webroot/` directory is publicly visible, and that your + secrets (such as your app salt, and any security keys) are private and unique + as well. + +## Set Document Root + +Setting the document root correctly on your application is an important step to +keeping your code secure and your application safer. CakePHP applications +should have the document root set to the application's `webroot`. This +makes the application and configuration files inaccessible through a URL. +Setting the document root is different for different webservers. See the +[Url Rewriting](installation#without-url-rewriting) documentation for webserver specific +information. + +In all cases you will want to set the virtual host/domain's document to be +`webroot/`. This removes the possibility of files outside of the webroot +directory being executed. + + + +## Improve Your Application's Performance + +Class loading can take a big share of your application's processing time. +In order to avoid this problem, it is recommended that you run this command in +your production server once the application is deployed: + + php composer.phar dumpautoload -o + +Since handling static assets, such as images, JavaScript and CSS files of +plugins, through the `Dispatcher` is incredibly inefficient, it is strongly +recommended to symlink them for production. This can be done by using +the `plugin` command: + + bin/cake plugin assets symlink + +The above command will symlink the `webroot/` directory of all loaded plugins +to appropriate path in the app's `webroot/` directory. + +If your filesystem doesn't allow creating symlinks the directories will be +copied instead of being symlinked. You can also explicitly copy the directories +using: + + bin/cake plugin assets copy + +CakePHP uses `assert()` internally to provide runtime type checking and +provide better error messages during development. You can have PHP skip these +assertions by updating your `php.ini` to include: + +``` ini +; Turn off assert() code generation. +zend.assertions = -1 +``` + +Skipping code generation for `assert()` will yield faster runtime performance, +and is recommended for applications that have good test coverage or that are +using a static analyzer. + +## Deploying an update + +On each deploy you'll likely have a few tasks to co-ordinate on your web server. Some typical ones +are: + +1. Install dependencies with `composer install`. Avoid using `composer update` when doing deploys as you could get unexpected versions of packages. +2. Run database [migrations](migrations) with either the Migrations plugin + or another tool. +3. Clear model schema cache with `bin/cake schema_cache clear`. The [Schema Cache Tool](console-commands/schema-cache) + has more information on this command. diff --git a/docs/en/development/application.md b/docs/en/development/application.md new file mode 100644 index 0000000000..4d05a51899 --- /dev/null +++ b/docs/en/development/application.md @@ -0,0 +1,84 @@ +# Application + +The `Application` is the heart of your application. It controls +how your application is configured, and what plugins, middleware, console +commands and routes are included. + +You can find your `Application` class at **src/Application.php**. By default +it will be pretty slim and only define a few default +[Middleware](../controllers/middleware). Applications can define the following hook +methods: + +- `bootstrap` Used to load [configuration files](../development/configuration), define constants and other global functions. + By default this will include **config/bootstrap.php**. This is the ideal place + to load [Plugins](../plugins) and global [event listeners](../core-libraries/events). +- `routes` Used to load [routes](../development/routing). By default this + will include **config/routes.php**. +- `middleware` Used to add [middleware](../controllers/middleware) to your application. +- `console` Used to add [console commands](../console-commands) to your + application. By default this will automatically discover console commands in + your application and all plugins. + +## Bootstrapping your Application + +If you have any additional configuration needs, you should add them to your +application's **config/bootstrap.php** file. This file is included before each +request, and CLI command. + +This file is ideal for a number of common bootstrapping tasks: + +- Defining convenience functions. +- Declaring constants. +- Defining cache configuration. +- Defining logging configuration. +- Loading custom inflections. +- Loading configuration files. + +It might be tempting to place formatting functions there in order to use them in +your controllers. As you'll see in the [Controllers](../controllers) and [Views](../views) +sections there are better ways you add custom logic to your application. + + + +### Application::bootstrap() + +In addition to the **config/bootstrap.php** file which should be used to +configure low-level concerns of your application, you can also use the +`Application::bootstrap()` hook method to load/initialize plugins, and attach +global event listeners: + +``` php +// in src/Application.php +namespace App; + +use Cake\Http\BaseApplication; + +class Application extends BaseApplication +{ + public function bootstrap() + { + // Call the parent to `require_once` config/bootstrap.php + parent::bootstrap(); + + // CakePHP has the ability to fallback to using the `Cake\ORM\Table` + // class to represent your database tables when a related class is + // not created for that table. But using this "auto-tables" feature + // can make debugging more difficult in some scenarios. So we disable + // this feature except for the CLI environment (since the classes + // would not be present when using the `bake` code generation tool). + if (PHP_SAPI !== 'cli') { + FactoryLocator::add( + 'Table', + (new TableLocator())->allowFallbackClass(false) + ); + } + + // Load MyPlugin + $this->addPlugin('MyPlugin'); + } +} +``` + +Loading plugins and events in `Application::bootstrap()` makes +[Integration Testing](../development/testing#integration-testing) easier as events and routes will be re-processed on +each test method. diff --git a/docs/en/development/configuration.md b/docs/en/development/configuration.md new file mode 100644 index 0000000000..6166111267 --- /dev/null +++ b/docs/en/development/configuration.md @@ -0,0 +1,594 @@ +# Configuration + +While conventions remove the need to configure all of CakePHP, you'll still need +to configure a few things like your database credentials. + +Additionally, there are optional configuration options that allow you to swap +out default values & implementations with ones tailored to your application. + +
+ +app.php, app_local.example.php + +
+ +
+ +configuration + +
+ +## Configuring your Application + +Configuration is generally stored in either PHP or INI files, and loaded during +the application bootstrap. CakePHP comes with one configuration file by default, +but if required you can add additional configuration files and load them in +your application's bootstrap code. `Cake\Core\Configure` is used +for global configuration, and classes like `Cache` provide `setConfig()` +methods to make configuration simple and transparent. + +The application skeleton features a **config/app.php** file which should contain +configuration that doesn't vary across the various environments your application +is deployed in. The **config/app_local.php** file should contain the +configuration data that varies between environments and should be managed by +configuration management, or your deployment tooling. Both of these files reference environment variables +through the `env()` function that enables configuration values to set through +the server environment. + +### Loading Additional Configuration Files + +If your application has many configuration options it can be helpful to split +configuration into multiple files. After creating each of the files in your +**config/** directory you can load them in **bootstrap.php**: + +``` php +use Cake\Core\Configure; +use Cake\Core\Configure\Engine\PhpConfig; + +Configure::setConfig('default', new PhpConfig()); +Configure::load('app', 'default', false); +Configure::load('other_config', 'default'); +``` + + + +## Environment Variables + +Many modern cloud providers, like Heroku, let you define environment +variables for configuration data. You can configure your CakePHP through +environment variables in the [12factor app style](https://12factor.net/). +Environment variables allow your application to require less state making your +application easier to manage when it is deployed across a number of +environments. + +As you can see in your **app.php**, the `env()` function is used to read +configuration from the environment, and build the application configuration. +CakePHP uses `DSN` strings for databases, logs, email transports and cache +configurations allowing you to easily vary these libraries in each environment. + +For local development, CakePHP leverages [dotenv](https://github.com/josegonzalez/php-dotenv) to make local development +automatically reload environment variables. Use composer to require this library +and then there is a block of code in `bootstrap.php` that needs to be +uncommented to harness it. + +You will see a `config/.env.example` in your +application. By copying this file into `config/.env` and customizing the +values you can configure your application. + +You should avoid committing the `config/.env` file to your repository and +instead use the `config/.env.example` as a template with placeholder values so +everyone on your team knows what environment variables are in use and what +should go in each one. + +Once your environment variables have been set, you can use `env()` to read +data from the environment: + +``` php +$debug = env('APP_DEBUG', false); +``` + +The second value passed to the env function is the default value. This value +will be used if no environment variable exists for the given key. + + + +### General Configuration + +Below is a description of the variables and how they affect your CakePHP +application. + +debug +Changes CakePHP debugging output. `false` = Production mode. No error +messages, errors, or warnings shown. `true` = Errors and warnings shown. + +App.namespace +The namespace to find app classes under. + +> [!NOTE] +> When changing the namespace in your configuration, you will also +> need to update your **composer.json** file to use this namespace +> as well. Additionally, create a new autoloader by running +> `php composer.phar dumpautoload`. + +
+ +App.baseUrl +Un-comment this definition if you **don’t** plan to use Apache’s +mod_rewrite with CakePHP. Don’t forget to remove your .htaccess +files too. + +App.base +The base directory the app resides in. If `false` this +will be auto detected. If not `false`, ensure your string starts +with a / and does NOT end with a /. For example, /basedir is a valid +App.base. + +App.encoding +Define what encoding your application uses. This encoding +is used to generate the charset in the layout, and encode entities. +It should match the encoding values specified for your database. + +App.webroot +The webroot directory. + +App.wwwRoot +The file path to webroot. + +App.fullBaseUrl +The fully qualified domain name (including protocol) to your application's +root. This is used when generating absolute URLs. By default this value +is generated using the `$_SERVER` environment. However, you should define it +manually to optimize performance or if you are concerned about people +manipulating the `Host` header. +In a CLI context (from command) the fullBaseUrl cannot be read from \$\_SERVER, +as there is no webserver involved. You do need to specify it yourself if +you do need to generate URLs from a shell (for example, when sending emails). + +App.imageBaseUrl +Web path to the public images directory under webroot. If you are using +a `CDN` you should set this value to the CDN's location. + +App.cssBaseUrl +Web path to the public css directory under webroot. If you are using +a `CDN` you should set this value to the CDN's location. + +App.jsBaseUrl +Web path to the public js directory under webroot. If you are using +a `CDN` you should set this value to the CDN's location. + +App.paths +Configure paths for non class based resources. Supports the +`plugins`, `templates`, `locales` subkeys, which allow the definition +of paths for plugins, view templates and locale files respectively. + +App.uploadedFilesAsObjects +Defines whether uploaded files are being represented as objects (`true`), +or arrays (`false`). This option is being treated as enabled by default. +See the [File Uploads section](../controllers/request-response#request-file-uploads) in the Request & +Response Objects chapter for more information. + +Security.salt +A random string used in hashing. This value is also used as the +HMAC salt when doing symmetric encryption. + +Asset.timestamp +Appends a timestamp which is last modified time of the particular +file at the end of asset files URLs (CSS, JavaScript, Image) when +using proper helpers. Valid values: + +- (bool) `false` - Doesn't do anything (default) +- (bool) `true` - Appends the timestamp when debug is `true` +- (string) 'force' - Always appends the timestamp. + +Asset.cacheTime +Sets the asset cache time. This determines the http header `Cache-Control`'s +`max-age`, and the http header's `Expire`'s time for assets. +This can take anything that you version of PHP's [strtotime function](https://php.net/manual/en/function.strtotime.php) can take. +The default is `+1 day`. + +
+ +### Using a CDN + +To use a CDN for loading your static assets, change `App.imageBaseUrl`, +`App.cssBaseUrl`, `App.jsBaseUrl` to point the CDN URI, for example: +`https://mycdn.example.com/` (note the trailing `/`). + +All images, scripts and styles loaded via HtmlHelper will prepend the absolute +CDN path, matching the same relative path used in the application. Please note +there is a specific use case when using plugin based assets: plugins will not +use the plugin's prefix when absolute `...BaseUrl` URI is used, for example By +default: + +- `$this->Helper->assetUrl('TestPlugin.logo.png')` resolves to `test_plugin/logo.png` + +If you set `App.imageBaseUrl` to `https://mycdn.example.com/`: + +- `$this->Helper->assetUrl('TestPlugin.logo.png')` resolves to `https://mycdn.example.com/logo.png`. + +### Database Configuration + +See the [Database Configuration](../orm/database-basics#database-configuration) for information +on configuring your database connections. + +### Caching Configuration + +See the [Caching Configuration](../core-libraries/caching#cache-configuration) for information on +configuring caching in CakePHP. + +### Error and Exception Handling Configuration + +See the [Error and Exception Configuration](../development/errors#error-configuration) for +information on configuring error and exception handlers. + +### Logging Configuration + +See the [Log Configuration](../core-libraries/logging#log-configuration) for information on configuring logging in +CakePHP. + +### Email Configuration + +See the [Email Configuration](../core-libraries/email#email-configuration) for information on +configuring email presets in CakePHP. + +### Session Configuration + +See the [Session Configuration](../development/sessions#session-configuration) for information on configuring session +handling in CakePHP. + +### Routing configuration + +See the [Routes Configuration](../development/routing#routes-configuration) for more information +on configuring routing and creating routes for your application. + + + +## Additional Class Paths + +Additional class paths are setup through the autoloaders your application uses. +When using `composer` to generate your autoloader, you could do the following, +to provide fallback paths for controllers in your application: + +``` json +"autoload": { + "psr-4": { + "App\\Controller\\": "/path/to/directory/with/controller/folders/", + "App\\": "src/" + } +} +``` + +The above would setup paths for both the `App` and `App\Controller` +namespace. The first key will be searched, and if that path does not contain the +class/file the second key will be searched. You can also map a single namespace +to multiple directories with the following: + +``` json +"autoload": { + "psr-4": { + "App\\": ["src/", "/path/to/directory/"] + } +} +``` + +### Plugin, View Template and Locale Paths + +Since plugins, view templates and locales are not classes, they cannot have an +autoloader configured. CakePHP provides three Configure variables to setup additional +paths for these resources. In your **config/app.php** you can set these variables: + +``` text +return [ + // More configuration + 'App' => [ + 'paths' => [ + 'plugins' => [ + ROOT . DS . 'plugins' . DS, + '/path/to/other/plugins/', + ], + 'templates' => [ + ROOT . DS . 'templates' . DS, + ROOT . DS . 'templates2' . DS, + ], + 'locales' => [ + ROOT . DS . 'resources' . DS . 'locales' . DS, + ], + ], + ], +]; +``` + +Paths should end with a directory separator, or they will not work properly. + +## Inflection Configuration + +See the [Inflection Configuration](../core-libraries/inflector#inflection-configuration) docs for more information. + +## Configure Class + +`class` Cake\\Core\\**Configure** + +CakePHP's Configure class can be used to store and retrieve +application or runtime specific values. Be careful, this class +allows you to store anything in it, then use it in any other part +of your code: a sure temptation to break the MVC pattern CakePHP +was designed for. The main goal of Configure class is to keep +centralized variables that can be shared between many objects. +Remember to try to live by "convention over configuration" and you +won't end up breaking the MVC structure CakePHP provides. + +### Writing Configuration data + +`static` Cake\\Core\\Configure::**write**($key, $value): void + +Use `write()` to store data in the application's configuration: + +``` php +Configure::write('Company.name', 'Pizza, Inc.'); +Configure::write('Company.slogan', 'Pizza for your body and soul'); +``` + +> [!NOTE] +> The `dot notation` used in the `$key` parameter can be used to +> organize your configuration settings into logical groups. + +The above example could also be written in a single call: + +``` php +Configure::write('Company', [ + 'name' => 'Pizza, Inc.', + 'slogan' => 'Pizza for your body and soul' +]); +``` + +You can use `Configure::write('debug', $bool)` to switch between debug and +production modes on the fly. + +> [!NOTE] +> Any configuration changes done using `Configure::write()` are in memory +> and will not persist across requests. + +### Reading Configuration Data + +`static` Cake\\Core\\Configure::**read**($key = null, $default = null): mixed + +Used to read configuration data from the application. If a key is supplied, the +data is returned. Using our examples from write() above, we can read that data +back: + +``` php +// Returns 'Pizza Inc.' +Configure::read('Company.name'); + +// Returns 'Pizza for your body and soul' +Configure::read('Company.slogan'); + +Configure::read('Company'); +// Returns: +['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul']; + +// Returns 'fallback' as Company.nope is undefined. +Configure::read('Company.nope', 'fallback'); +``` + +If `$key` is left null, all values in Configure will be returned. + +`static` Cake\\Core\\Configure::**readOrFail**($key): mixed + +Reads configuration data just like `Cake\Core\Configure::read()` +but expects to find a key/value pair. In case the requested pair does not +exist, a `RuntimeException` will be thrown: + +``` php +Configure::readOrFail('Company.name'); // Yields: 'Pizza, Inc.' +Configure::readOrFail('Company.geolocation'); // Will throw an exception + +Configure::readOrFail('Company'); + +// Yields: +['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul']; +``` + +### Checking to see if Configuration Data is Defined + +`static` Cake\\Core\\Configure::**check**($key): bool + +Used to check if a key/path exists and has non-null value: + +``` php +$exists = Configure::check('Company.name'); +``` + +### Deleting Configuration Data + +`static` Cake\\Core\\Configure::**delete**($key): void + +Used to delete information from the application's configuration: + +``` php +Configure::delete('Company.name'); +``` + +### Reading & Deleting Configuration Data + +`static` Cake\\Core\\Configure::**consume**($key): mixed + +Read and delete a key from Configure. This is useful when you want to +combine reading and deleting values in a single operation. + +`static` Cake\\Core\\Configure::**consumeOrFail**($key): mixed + +Consumes configuration data just like `Cake\Core\Configure::consume()` +but expects to find a key/value pair. In case the requested pair does not +exist, a `RuntimeException` will be thrown: + +``` php +Configure::consumeOrFail('Company.name'); // Yields: 'Pizza, Inc.' +Configure::consumeOrFail('Company.geolocation'); // Will throw an exception + +Configure::consumeOrFail('Company'); + +// Yields: +['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul']; +``` + +## Reading and writing configuration files + +`static` Cake\\Core\\Configure::**setConfig**($name, $engine) + +CakePHP comes with two built-in configuration file engines. +`Cake\Core\Configure\Engine\PhpConfig` is able to read PHP config +files, in the same format that Configure has historically read. +`Cake\Core\Configure\Engine\IniConfig` is able to read ini config +files. See the [PHP documentation](https://php.net/parse_ini_file) for more +information on the specifics of ini files. To use a core config engine, you'll +need to attach it to Configure using `Configure::config()`: + +``` php +use Cake\Core\Configure\Engine\PhpConfig; + +// Read config files from config +Configure::config('default', new PhpConfig()); + +// Read config files from another path. +Configure::config('default', new PhpConfig('/path/to/your/config/files/')); +``` + +You can have multiple engines attached to Configure, each reading different +kinds or sources of configuration files. You can interact with attached engines +using a few other methods on Configure. To check which engine aliases are +attached you can use `Configure::configured()`: + +``` php +// Get the array of aliases for attached engines. +Configure::configured(); + +// Check if a specific engine is attached +Configure::configured('default'); +``` + +`static` Cake\\Core\\Configure::**drop**($name): bool + +You can also remove attached engines. `Configure::drop('default')` +would remove the default engine alias. Any future attempts to load configuration +files with that engine would fail: + +``` php +Configure::drop('default'); +``` + + + +### Loading Configuration Files + +`static` Cake\\Core\\Configure::**load**($key, $config = 'default', $merge = true): bool + +Once you've attached a config engine to Configure you can load configuration +files: + +``` php +// Load my_file.php using the 'default' engine object. +Configure::load('my_file', 'default'); +``` + +Loaded configuration files merge their data with the existing runtime +configuration in Configure. This allows you to overwrite and add new values into +the existing runtime configuration. By setting `$merge` to `true`, values +will not ever overwrite the existing configuration. + +> [!WARNING] +> When merging configuration files with \$merge = true, dot notation in keys is +> not expanded: +> +> ``` php +> // config1.php +> 'Key1' => [ +> 'Key2' => [ +> 'Key3' => ['NestedKey1' => 'Value'], +> ], +> ], +> +> // config2.php +> 'Key1.Key2' => [ +> 'Key3' => ['NestedKey2' => 'Value2'], +> ] +> +> Configure::load('config1', 'default'); +> Configure::load('config2', 'default', true); +> +> // Now Key1.Key2.Key3 has the value ['NestedKey2' => 'Value2'] +> // instead of ['NestedKey1' => 'Value', 'NestedKey2' => 'Value2'] +> ``` + +### Creating or Modifying Configuration Files + +`static` Cake\\Core\\Configure::**dump**($key, $config = 'default', $keys = []): bool + +Dumps all or some of the data in Configure into a file or storage system +supported by a config engine. The serialization format is decided by the config +engine attached as \$config. For example, if the 'default' engine is +a `Cake\Core\Configure\Engine\PhpConfig`, the generated file will be +a PHP configuration file loadable by the +`Cake\Core\Configure\Engine\PhpConfig` + +Given that the 'default' engine is an instance of PhpConfig. +Save all data in Configure to the file \`my_config.php\`: + +``` php +Configure::dump('my_config', 'default'); +``` + +Save only the error handling configuration: + +``` php +Configure::dump('error', 'default', ['Error', 'Exception']); +``` + +`Configure::dump()` can be used to either modify or overwrite +configuration files that are readable with `Configure::load()` + +### Storing Runtime Configuration + +`static` Cake\\Core\\Configure::**store**($name, $cacheConfig = 'default', $data = null): bool + +You can also store runtime configuration values for use in a future request. +Since configure only remembers values for the current request, you will +need to store any modified configuration information if you want to +use it in subsequent requests: + +``` php +// Store the current configuration in the 'user_1234' key in the 'default' cache. +Configure::store('user_1234', 'default'); +``` + +Stored configuration data is persisted in the named cache configuration. See the +[Caching](../core-libraries/caching) documentation for more information on caching. + +### Restoring Runtime Configuration + +`static` Cake\\Core\\Configure::**restore**($name, $cacheConfig = 'default'): bool + +Once you've stored runtime configuration, you'll probably need to restore it +so you can access it again. `Configure::restore()` does exactly that: + +``` php +// Restore runtime configuration from the cache. +Configure::restore('user_1234', 'default'); +``` + +When restoring configuration information it's important to restore it with +the same key, and cache configuration as was used to store it. Restored +information is merged on top of the existing runtime configuration. + +### Configuration Engines + +CakePHP provides the ability to load configuration files from a number of +different sources, and features a pluggable system for [creating your own +configuration engines](https://api.cakephp.org/5.x/interface-Cake.Core.Configure.ConfigEngineInterface.html). +The built in configuration engines are: + +- [JsonConfig](https://api.cakephp.org/5.x/class-Cake.Core.Configure.Engine.JsonConfig.html) +- [IniConfig](https://api.cakephp.org/5.x/class-Cake.Core.Configure.Engine.IniConfig.html) +- [PhpConfig](https://api.cakephp.org/5.x/class-Cake.Core.Configure.Engine.PhpConfig.html) + +By default your application will use `PhpConfig`. diff --git a/docs/en/development/debugging.md b/docs/en/development/debugging.md new file mode 100644 index 0000000000..e675c16b8d --- /dev/null +++ b/docs/en/development/debugging.md @@ -0,0 +1,234 @@ +# Debugging + +Debugging is an inevitable and necessary part of any development +cycle. While CakePHP doesn't offer any tools that directly connect +with any IDE or editor, CakePHP does provide several tools to +assist in debugging and exposing what is running under the hood of +your application. + +## Basic Debugging + +`function` **debug(mixed $var, boolean $showHtml = null, $showFrom = true)** + +The `debug()` function is a globally available function that works +similarly to the PHP function `print_r()`. The `debug()` function +allows you to show the contents of a variable in a number of +different ways. First, if you'd like data to be shown in an +HTML-friendly way, set the second parameter to `true`. The function +also prints out the line and file it is originating from by +default. + +Output from this function is only shown if the core `$debug` variable +has been set to `true`. + +Also see `dd()`, `pr()` and `pj()`. + +`function` **stackTrace()** + +The `stackTrace()` function is available globally, and allows you to output +a stack trace wherever the function is called. + +`function` **breakpoint()** + +If you have [Psysh](https://psysh.org/) installed you can use this +function in CLI environments to open an interactive console with the current +local scope: + +``` text +// Some code +eval(breakpoint()); +``` + +Will open an interactive console that can be used to check local variables +and execute other code. You can exit the interactive debugger and resume the +original execution by running `quit` or `q` in the interactive session. + +## Using the Debugger Class + +`class` Cake\\Error\\**Debugger** + +To use the debugger, first ensure that `Configure::read('debug')` is set to +`true`. You can use `filter_var(env('DEBUG', true), FILTER_VALIDATE_BOOLEAN),` in **config/app.php** file to ensure that `debug` +is a boolean. + +The following configuration options can be set in **config/app.php** to change how +`Debugger` behaves: + +- `Debugger.editor` Choose the which editor URL format you want to use. + By default atom, emacs, macvim, phpstorm, sublime, textmate, and vscode are + available. You can add additional editor link formats using + `Debugger::addEditor()` during your application bootstrap. +- `Debugger.editorBasePath` The path that replaces `ROOT` when generating + editor links. +- `Debugger.outputMask` A mapping of `key` to `replacement` values that + `Debugger` should replace in dumped data and logs generated by `Debugger`. + +::: info Added in version 5.3.0 +The `Debugger.editorBasePath` configure option was added. +::: + +## Outputting Values + +`static` Cake\\Error\\Debugger::**dump**($var, $depth = 3): void + +Dump prints out the contents of a variable. It will print out all +properties and methods (if any) of the supplied variable: + +``` php +$foo = [1,2,3]; + +Debugger::dump($foo); + +// Outputs +array( + 1, + 2, + 3 +) + +// Simple object +$car = new Car(); + +Debugger::dump($car); + +// Outputs +object(Car) { + color => 'red' + make => 'Toyota' + model => 'Camry' + mileage => (int)15000 +} +``` + +### Masking Data + +When dumping data with `Debugger` or rendering error pages, you may want to +hide sensitive keys like passwords or API keys. In your **config/bootstrap.php** +you can mask specific keys: + +``` css +Debugger::setOutputMask([ + 'password' => 'xxxxx', + 'awsKey' => 'yyyyy', +]); +``` + +As of 4.1.0 you can use the `Debugger.outputMask` configuration value to set +output masks. + +## Logging With Stack Traces + +`static` Cake\\Error\\Debugger::**log**($var, $level = 7, $depth = 3): void + +Creates a detailed stack trace log at the time of invocation. The +`log()` method prints out data similar to that done by +`Debugger::dump()`, but to the debug.log instead of the output +buffer. Note your **tmp** directory (and its contents) must be +writable by the web server for `log()` to work correctly. + +## Generating Stack Traces + +`static` Cake\\Error\\Debugger::**trace**($options): array|string + +Returns the current stack trace. Each line of the trace includes +the calling method, including which file and line the call +originated from: + +``` text +// In PostsController::index() +pr(Debugger::trace()); + +// Outputs +PostsController::index() - APP/Controller/DownloadsController.php, line 48 +Dispatcher::_invoke() - CORE/src/Routing/Dispatcher.php, line 265 +Dispatcher::dispatch() - CORE/src/Routing/Dispatcher.php, line 237 +[main] - APP/webroot/index.php, line 84 +``` + +Above is the stack trace generated by calling `Debugger::trace()` in +a controller action. Reading the stack trace bottom to top shows +the order of currently running functions (stack frames). + +## Getting an Excerpt From a File + +`static` Cake\\Error\\Debugger::**excerpt**($file, $line, $context): array + +Grab an excerpt from the file at \$path (which is an absolute +filepath), highlights line number \$line with \$context number of +lines around it. : + +``` php +pr(Debugger::excerpt(ROOT . DS . LIBS . 'debugger.php', 321, 2)); + +// Will output the following. +Array +( + [0] => * @access public + [1] => */ + [2] => function excerpt($file, $line, $context = 2) { + + [3] => $data = $lines = array(); + [4] => $data = @explode("\n", file_get_contents($file)); +) +``` + +Although this method is used internally, it can be handy if you're +creating your own error messages or log entries for custom +situations. + +`static` Debugger::**getType**($var): string + +Get the type of a variable. Objects will return their class name + +## Editor Integration + +Exception and error pages can contain URLs that directly open in your editor or +IDE. CakePHP ships with URL formats for several popular editors, and you can add +additional editor formats if required during application bootstrap: + +``` text +// Generate links for vscode. +Debugger::setEditor('vscode') + +// Add a custom format +// Format strings will have the {file} and {line} +// placeholders replaced. +Debugger::addEditor('custom', 'thing://open={file}&line={line}'); + +// You can also use a closure to generate URLs +Debugger::addEditor('custom', function ($file, $line) { + return "thing://open={$file}&line={$line}"; +}); +``` + +## Using Logging to Debug + +Logging messages is another good way to debug applications, and you can use +`Cake\Log\Log` to do logging in your application. All objects that +use `LogTrait` have an instance method `log()` which can be used +to log messages: + +``` php +$this->log('Got here', 'debug'); +``` + +The above would write `Got here` into the debug log. You can use log entries +to help debug methods that involve redirects or complicated loops. You can also +use `Cake\Log\Log::write()` to write log messages. This method can be called +statically anywhere in your application one Log has been loaded: + +``` php +// At the top of the file you want to log in. +use Cake\Log\Log; + +// Anywhere that Log has been imported. +Log::debug('Got here'); +``` + +## Debug Kit + +DebugKit is a plugin that provides a number of good debugging tools. It +primarily provides a toolbar in the rendered HTML, that provides a plethora of +information about your application and the current request. See the [DebugKit +Documentation](https://book.cakephp.org/debugkit/) for how to install and use +DebugKit. diff --git a/docs/en/development/dependency-injection.md b/docs/en/development/dependency-injection.md new file mode 100644 index 0000000000..33e6867b07 --- /dev/null +++ b/docs/en/development/dependency-injection.md @@ -0,0 +1,398 @@ +# Dependency Injection + +The CakePHP service container enables you to manage class dependencies for your +application services through dependency injection. Dependency injection +automatically "injects" an object's dependencies via the constructor without +having to manually instantiate them. + +You can use the service container to define 'application services'. These +classes can use models and interact with other objects like loggers and mailers +to build re-usable workflows and business logic for your application. + +CakePHP will use the `DI container` in the following situations: + +- Constructing controllers. +- Calling actions on your controllers. +- Constructing Components. +- Constructing Console Commands. +- Constructing Middleware by classname. + +## Controller Example + +``` php +// In src/Controller/UsersController.php +class UsersController extends AppController +{ + // The $users service will be created via the service container. + public function ssoCallback(UsersService $users) + { + if ($this->request->is('post')) { + // Use the UsersService to create/get the user from a + // Single Signon Provider. + $user = $users->ensureExists($this->request->getData()); + } + } +} + +// In src/Application.php +public function services(ContainerInterface $container): void +{ + $container->add(UsersService::class); +} +``` + +In this example, the `UsersController::ssoCallback()` action needs to fetch +a user from a Single-Sign-On provider and ensure it exists in the local +database. Because this service is injected into our controller, we can easily +swap the implementation out with a mock object or a dummy sub-class when +testing. + +## Command Example + +``` php +// In src/Command/CheckUsersCommand.php +use Cake\Console\CommandFactoryInterface; + +class CheckUsersCommand extends Command +{ + public function __construct(protected UsersService $users, ?CommandFactoryInterface $factory = null) + { + parent::__construct($factory); + } + + public function execute(Arguments $args, ConsoleIo $io) + { + $valid = $this->users->check('all'); + } + +} + +// In src/Application.php +public function services(ContainerInterface $container): void +{ + $container + ->add(CheckUsersCommand::class) + ->addArgument(UsersService::class) + ->addArgument(CommandFactoryInterface::class); + $container->add(UsersService::class); +} +``` + +The injection process is a bit different here. Instead of adding the +`UsersService` to the container we first have to add the Command as +a whole to the Container and add the `UsersService` as an argument. +With that you can then access that service inside the constructor +of the command. + +## Component Example + +``` php +// In src/Controller/Component/SearchComponent.php +class SearchComponent extends Component +{ + public function __construct( + ComponentRegistry $registry, + private UserService $users, + array $config = [] + ) { + parent::__construct($registry, $config); + } + + public function something() + { + $valid = $this->users->check('all'); + } +} + +// In src/Application.php +public function services(ContainerInterface $container): void +{ + $container->add(SearchComponent::class) + ->addArgument(ComponentRegistry::class) + ->addArgument(UsersService::class); + $container->add(UsersService::class); +} +``` + +## Adding Services + +In order to have services created by the container, you need to tell it which +classes it can create and how to build those classes. The +simplest definition is via a class name: + +``` php +// Add a class by its name. +$container->add(BillingService::class); +``` + +Your application and plugins define the services they have in the +`services()` hook method: + +``` php +// in src/Application.php +namespace App; + +use App\Service\BillingService; +use Cake\Core\ContainerInterface; +use Cake\Http\BaseApplication; + +class Application extends BaseApplication +{ + public function services(ContainerInterface $container): void + { + $container->add(BillingService::class); + } +} +``` + +You can define implementations for interfaces that your application uses: + +``` php +use App\Service\AuditLogServiceInterface; +use App\Service\AuditLogService; + +// in your Application::services() method. + +// Add an implementation for an interface. +$container->add(AuditLogServiceInterface::class, AuditLogService::class); +``` + +The container can leverage factory functions to create objects if necessary: + +``` php +$container->add(AuditLogServiceInterface::class, function (...$args) { + return new AuditLogService(...$args); +}); +``` + +Factory functions will receive all of the resolved dependencies for the class +as arguments. + +Once you've defined a class, you also need to define the dependencies it +requires. Those dependencies can be either objects or primitive values: + +``` php +// Add a primitive value like a string, array or number. +$container->add('apiKey', 'abc123'); + +$container->add(BillingService::class) + ->addArgument('apiKey'); +``` + +Your services can depend on `ServerRequest` in controller actions as it will +be added automatically. + +### Adding Shared Services + +By default services are not shared. Every object (and dependencies) is created +each time it is fetched from the container. If you want to re-use a single +instance, often referred to as a singleton, you can mark a service as 'shared': + +``` php +// in your Application::services() method. + +$container->addShared(BillingService::class); +``` + +### Using ORM Tables as Services + +If you want to have ORM Tables injected as a dependency to a service, you can +add `TableContainer` to your applications's service container: + +``` php +// In your Application::services() method. +// Allow your Tables to be dependency injected. +$container->delegate(new \Cake\ORM\Locator\TableContainer()); +``` + +::: info Added in version 5.3.0 +`TableContainer` was added. +::: + +### Extending Definitions + +Once a service is defined you can modify or update the service definition by +extending them. This allows you to add additional arguments to services defined +elsewhere: + +``` php +// Add an argument to a partially defined service elsewhere. +$container->extend(BillingService::class) + ->addArgument('logLevel'); +``` + +### Tagging Services + +By tagging services you can get all of those services resolved at the same +time. This can be used to build services that combine collections of other +services like in a reporting system: + +``` php +$container->add(BillingReport::class)->addTag('reports'); +$container->add(UsageReport::class)->addTag('reports'); + +$container->add(ReportAggregate::class, function () use ($container) { + return new ReportAggregate($container->get('reports')); +}); +``` + + + +### Using Configuration Data + +Often you'll need configuration data in your services. If you need a specific value, +you can inject it as a constructor argument using the `Cake\Core\Attribute\Configure` +attribute: + +``` php +use Cake\Core\Attribute\Configure; + +class InjectedService +{ + public function __construct( + #[Configure('MyService.apiKey')] protected string $apiKey, + ) { } +} +``` + +::: info Added in version 5.3.0 +::: + +If you want to inject a copy of all configuration data, CakePHP includes +an injectable configuration reader: + +``` php +use Cake\Core\ServiceConfig; + +// Use a shared instance +$container->addShared(ServiceConfig::class); +``` + +The `ServiceConfig` class provides a read-only view of all the data available +in `Configure` so you don't have to worry about accidentally changing +configuration. + +## Service Providers + +Service providers allow you to group related services together helping you +organize your services. Service providers can help increase your application's +performance as defined services are lazily registered after +their first use. + +### Creating Service Providers + +An example ServiceProvider would look like: + +``` php +namespace App\ServiceProvider; + +use Cake\Core\ContainerInterface; +use Cake\Core\ServiceProvider; +// Other imports here. + +class BillingServiceProvider extends ServiceProvider +{ + protected $provides = [ + StripeService::class, + 'configKey', + ]; + + public function services(ContainerInterface $container): void + { + $container->add(StripeService::class); + $container->add('configKey', 'some value'); + } +} +``` + +Service providers use their `services()` method to define all the services they +will provide. Additionally those services **must be** defined in the `$provides` +property. Failing to include a service in the `$provides` property will result +in it not be loadable from the container. + +### Using Service Providers + +To load a service provider add it into the container using the +`addServiceProvider()` method: + +``` php +// in your Application::services() method. +$container->addServiceProvider(new BillingServiceProvider()); +``` + +### Bootable ServiceProviders + +If your service provider needs to run logic when it is added to the container, +you can implement the `bootstrap()` method. This situation can come up when your +service provider needs to load additional configuration files, load additional +service providers or modify a service defined elsewhere in your application. An +example of a bootable service would be: + +``` php +namespace App\ServiceProvider; + +use Cake\Core\ServiceProvider; +// Other imports here. + +class BillingServiceProvider extends ServiceProvider +{ + protected $provides = [ + StripeService::class, + 'configKey', + ]; + + public function bootstrap($container) + { + $container->addServiceProvider(new InvoicingServiceProvider()); + } +} +``` + + + +## Mocking Services in Tests + +In tests that use `ConsoleIntegrationTestTrait` or `IntegrationTestTrait` +you can replace services that are injected via the container with mocks or +stubs: + +``` php +// In a test method or setup(). +$this->mockService(StripeService::class, function () { + return new FakeStripe(); +}); + +// If you need to remove a mock +$this->removeMockService(StripeService::class); +``` + +Any defined mocks will be replaced in your application's container during +testing, and automatically injected into your controllers and commands. Mocks +are cleaned up at the end of each test. + +## Auto Wiring + +Auto Wiring is turned off by default. To enable it: + +``` php +// In src/Application.php +public function services(ContainerInterface $container): void +{ + $container->delegate( + new \League\Container\ReflectionContainer() + ); +} +``` + +While your dependencies will now be resolved automatically, this approach will +not cache resolutions which can be detrimental to performance. To enable +caching: + +``` php +$container->delegate( + // or consider using the value of Configure::read('debug') + new \League\Container\ReflectionContainer(true) +); +``` + +Read more about auto wiring in the [PHP League Container documentation](https://container.thephpleague.com/4.x/auto-wiring/). diff --git a/docs/en/development/errors.md b/docs/en/development/errors.md new file mode 100644 index 0000000000..237e562e02 --- /dev/null +++ b/docs/en/development/errors.md @@ -0,0 +1,690 @@ +# Error & Exception Handling + +CakePHP applications come with error and exception handling setup for you. PHP +errors are trapped and displayed or logged. Uncaught exceptions are rendered +into error pages automatically. + + + +## Configuration + +Error configuration is done in your application's **config/app.php** file. By +default CakePHP uses `Cake\Error\ErrorTrap` and `Cake\Error\ExceptionTrap` +to handle both PHP errors and exceptions respectively. The error configuration +allows you to customize error handling for your application. The following +options are supported: + +- `errorLevel` - int - The level of errors you are interested in capturing. + Use the built-in PHP error constants, and bitmasks to select the level of + error you are interested in. See [Deprecation Warnings](#deprecation-warnings) to disable + deprecation warnings. +- `trace` - bool - Include stack traces for errors in log files. Stack + traces will be included in the log after each error. This is helpful for + finding where/when errors are being raised. +- `exceptionRenderer` - string - The class responsible for rendering uncaught + exceptions. If you choose a custom class you should place the file for that + class in **src/Error**. This class needs to implement a `render()` method. +- `log` - bool - When `true`, exceptions + their stack traces will be + logged to `Cake\Log\Log`. +- `skipLog` - array - An array of exception classnames that should not be + logged. This is useful to remove NotFoundExceptions or other common, but + uninteresting log messages. +- `extraFatalErrorMemory` - int - Set to the number of megabytes to increase + the memory limit by when a fatal error is encountered. This allows breathing + room to complete logging or error handling. +- `logger` (prior to 4.4.0 use `errorLogger`) -`Cake\Error\ErrorLoggerInterface` - The class responsible for logging + errors and unhandled exceptions. Defaults to `Cake\Error\ErrorLogger`. +- `errorRenderer` - `Cake\Error\ErrorRendererInterface` - The class responsible + for rendering errors. Default is chosen based on PHP SAPI. +- `ignoredDeprecationPaths` - array - A list of glob compatible paths that + deprecation errors should be ignored in. Added in 4.2.0 + +By default, PHP errors are displayed when `debug` is `true`, and logged +when debug is `false`. The fatal error handler will be called independent +of `debug` level or `errorLevel` configuration, but the result will be +different based on `debug` level. The default behavior for fatal errors is +show a page to internal server error (`debug` disabled) or a page with the +message, file and line (`debug` enabled). + +> [!NOTE] +> If you use a custom error handler, the supported options will +> depend on your handler. + + + +## Deprecation Warnings + +CakePHP uses deprecation warnings to indicate when features have been +deprecated. We also recommend this system for use in your plugins and +application code when useful. You can trigger deprecation warnings with +`deprecationWarning()`: + +``` text +deprecationWarning('5.0', 'The example() method is deprecated. Use getExample() instead.'); +``` + +When upgrading CakePHP or plugins you may encounter new deprecation warnings. +You can temporarily disable deprecation warnings in one of a few ways: + +1. Using the `Error.errorLevel` setting to `E_ALL ^ E_USER_DEPRECATED` to + ignore *all* deprecation warnings. + +2. Using the `Error.ignoredDeprecationPaths` configuration option to ignore + deprecations with glob compatible expressions. For example: + + ``` text + 'Error' => [ + 'ignoredDeprecationPaths' => [ + 'vendors/company/contacts/*', + 'src/Models/*', + ], + ], + ``` + + Would ignore all deprecations from your `Models` directory and the + `Contacts` plugin in your application. + +## Changing Exception Handling + +Exception handling in CakePHP offers several ways to tailor how exceptions are +handled. Each approach gives you different amounts of control over the +exception handling process. + +1. *Listen to events* This allows you to be notified through CakePHP events when + errors and exceptions have been handled. +2. *Custom templates* This allows you to change the rendered view + templates as you would any other template in your application. +3. *Custom Controller* This allows you to control how exception + pages are rendered. +4. *Custom ExceptionRenderer* This allows you to control how exception + pages and logging are performed. +5. *Create & register your own traps* This gives you complete + control over how errors & exceptions are handled, logged and rendered. Use + `Cake\Error\ExceptionTrap` and `Cake\Error\ErrorTrap` as reference when + implementing your traps. + +## Listen to Events + +The `ErrorTrap` and `ExceptionTrap` handlers will trigger CakePHP events +when they handle errors. You can listen to the `Error.beforeRender` event to be +notified of PHP errors. The `Exception.beforeRender` event is dispatched when an +exception is handled: + +``` php +$errorTrap = new ErrorTrap(Configure::read('Error')); +$errorTrap->getEventManager()->on( + 'Error.beforeRender', + function (EventInterface $event, PhpError $error) { + // do your thing + } +); +``` + +Within an `Error.beforeRender` handler you have a few options: + +- Stop the event to prevent rendering. +- Return a string to skip rendering and use the provided string instead + +Within an `Exception.beforeRender` handler you have a few options: + +- Stop the event to prevent rendering. +- Set the `exception` data attribute with `setData('exception', $err)` + to replace the exception that is being rendered. +- Return a response from the event listener to skip rendering and use + the provided response instead. + + + +## Custom Templates + +The default exception trap renders all uncaught exceptions your application +raises with the help of `Cake\Error\Renderer\WebExceptionRenderer`, and your application's +`ErrorController`. + +The error page views are located at **templates/Error/**. All 4xx errors use +the **error400.php** template, and 5xx errors use the **error500.php**. Your +error templates will have the following variables available: + +- `message` The exception message. +- `code` The exception code. +- `url` The request URL. +- `error` The exception object. + +In debug mode if your error extends `Cake\Core\Exception\CakeException` the +data returned by `getAttributes()` will be exposed as view variables as well. + +> [!NOTE] +> You will need to set `debug` to false, to see your **error404** and +> **error500** templates. In debug mode, you'll see CakePHP's development +> error page. + +### Custom Error Page Layout + +By default error templates use **templates/layout/error.php** for a layout. +You can use the `layout` property to pick a different layout: + +``` php +// inside templates/Error/error400.php +$this->layout = 'my_error'; +``` + +The above would use **templates/layout/my_error.php** as the layout for your +error pages. + +Many exceptions raised by CakePHP will render specific view templates in debug +mode. With debug turned off all exceptions raised by CakePHP will use either +**error400.php** or **error500.php** based on their status code. + +## Custom Controller + +The `App\Controller\ErrorController` class is used by CakePHP's exception +rendering to render the error page view and receives all the standard request +life-cycle events. By modifying this class you can control which components are +used and which templates are rendered. + +If your application uses [Prefix Routing](../development/routing#prefix-routing) you can create custom error +controllers for each routing prefix. For example, if you had an `Admin` +prefix. You could create the following class: + +``` php +namespace App\Controller\Admin; + +use App\Controller\AppController; +use Cake\Event\EventInterface; + +class ErrorController extends AppController +{ + /** + * beforeRender callback. + * + * @param \Cake\Event\EventInterface $event Event. + * @return void + */ + public function beforeRender(EventInterface $event): void + { + $this->viewBuilder()->setTemplatePath('Error'); + } +} +``` + +This controller would only be used when an error is encountered in a prefixed +controller, and allows you to define prefix specific logic/templates as needed. + +### Exception specific logic + +Within your controller you can define public methods to handle custom +application errors. For example a `MissingWidgetException` would be handled by +a `missingWidget()` controller method, and CakePHP would use +`templates/Error/missing_widget.php` as the template. For example: + +``` php +namespace App\Controller\Admin; + +use App\Controller\AppController; +use Cake\Event\EventInterface; + +class ErrorController extends AppController +{ + protected function missingWidget(MissingWidgetException $exception) + { + // You can prepare additional template context or trap errors. + } +} +``` + +::: info Added in version 5.2.0 +Exception specific controller methods and templates were added. +::: + + + +## Custom ExceptionRenderer + +If you want to control the entire exception rendering and logging process you +can use the `Error.exceptionRenderer` option in **config/app.php** to choose +a class that will render exception pages. Changing the ExceptionRenderer is +useful when you want to change the logic used to create an error controller, +choose the template, or control the overall rendering process. + +Your custom exception renderer class should be placed in **src/Error**. Let's +assume our application uses `App\Exception\MissingWidgetException` to indicate +a missing widget. We could create an exception renderer that renders specific +error pages when this error is handled: + +``` php +// In src/Error/AppExceptionRenderer.php +namespace App\Error; + +use Cake\Error\Renderer\WebExceptionRenderer; + +class AppExceptionRenderer extends WebExceptionRenderer +{ + public function missingWidget($error) + { + $response = $this->controller->getResponse(); + + return $response->withStringBody('Oops that widget is missing.'); + } +} + +// In Application::middleware() +$middlewareQueue->add(new ErrorHandlerMiddleware( + ['exceptionRenderer' => AppExceptionRenderer::class] + Configure::read('Error'), + $this, +)); +// ... +``` + +The above would handle our `MissingWidgetException`, +and allow us to provide custom display/handling logic for those application +exceptions. + +Exception rendering methods receive the handled exception as an argument, and +should return a `Response` object. You can also implement methods to add +additional logic when handling CakePHP errors: + +``` php +// In src/Error/AppExceptionRenderer.php +namespace App\Error; + +use Cake\Error\Renderer\WebExceptionRenderer; + +class AppExceptionRenderer extends WebExceptionRenderer +{ + public function notFound($error) + { + // Do something with NotFoundException objects. + } +} +``` + +### Changing the ErrorController Class + +The exception renderer dictates which controller is used for exception +rendering. If you want to change which controller is used to render exceptions, +override the `_getController()` method in your exception renderer: + +``` php +// in src/Error/AppExceptionRenderer +namespace App\Error; + +use App\Controller\SuperCustomErrorController; +use Cake\Controller\Controller; +use Cake\Error\Renderer\WebExceptionRenderer; + +class AppExceptionRenderer extends WebExceptionRenderer +{ + protected function _getController(): Controller + { + return new SuperCustomErrorController(); + } +} + +// In Application::middleware() +$middlewareQueue->add(new ErrorHandlerMiddleware( + ['exceptionRenderer' => AppExceptionRenderer::class] + Configure::read('Error'), + $this, +)); +// ... +``` + +
+ +application exceptions + +
+ +## Creating your own Application Exceptions + +You can create your own application exceptions using any of the built in [SPL +exceptions](https://php.net/manual/en/spl.exceptions.php), `Exception` +itself, or `Cake\Core\Exception\Exception`. +If your application contained the following exception: + +``` php +use Cake\Core\Exception\CakeException; + +class MissingWidgetException extends CakeException +{ +} +``` + +You could provide nice development errors, by creating +**templates/Error/missing_widget.php**. When in production mode, the above +error would be treated as a 500 error and use the **error500** template. + +Exceptions that subclass `Cake\Http\Exception\HttpException`, will have their +error code used as an HTTP status code if the error code is between `400` and +`506`. + +The constructor for `Cake\Core\Exception\CakeException` allows you to +pass in additional data. This additional data is interpolated into the the +`_messageTemplate`. This allows you to create data rich exceptions, that +provide more context around your errors: + +``` php +use Cake\Core\Exception\CakeException; + +class MissingWidgetException extends CakeException +{ + // Context data is interpolated into this format string. + protected $_messageTemplate = 'Seems that %s is missing.'; + + // You can set a default exception code as well. + protected $_defaultCode = 404; +} + +throw new MissingWidgetException(['widget' => 'Pointy']); +``` + +When rendered, this your view template would have a `$widget` variable set. If +you cast the exception as a string or use its `getMessage()` method you will +get `Seems that Pointy is missing.`. + +> [!NOTE] +> Prior to CakePHP 4.2.0 use class `Cake\Core\Exception\Exception` instead +> of `Cake\Core\Exception\CakeException` + +### Logging Exceptions + +Using the built-in exception handling, you can log all the exceptions that are +dealt with by ErrorTrap by setting the `log` option to `true` in your +**config/app.php**. Enabling this will log every exception to +`Cake\Log\Log` and the configured loggers. + +> [!NOTE] +> If you are using a custom exception handler this setting will have +> no effect. Unless you reference it inside your implementation. + + + +## Built in Exceptions for CakePHP + +### HTTP Exceptions + +There are several built-in exceptions inside CakePHP, outside of the +internal framework exceptions, there are several +exceptions for HTTP methods + +> nocontentsentry +> +> > Used for doing 400 Bad Request error. +> +> nocontentsentry +> +> > Used for doing a 401 Unauthorized error. +> +> nocontentsentry +> +> > Used for doing a 403 Forbidden error. +> +> nocontentsentry +> +> > Used for doing a 403 error caused by an invalid CSRF token. +> +> nocontentsentry +> +> > Used for doing a 404 Not found error. +> +> nocontentsentry +> +> > Used for doing a 405 Method Not Allowed error. +> +> nocontentsentry +> +> > Used for doing a 406 Not Acceptable error. +> +> nocontentsentry +> +> > Used for doing a 409 Conflict error. +> +> nocontentsentry +> +> > Used for doing a 410 Gone error. + +For more details on HTTP 4xx error status codes see `2616#section-10.4`. + +> nocontentsentry +> +> > Used for doing a 500 Internal Server Error. +> +> nocontentsentry +> +> > Used for doing a 501 Not Implemented Errors. +> +> nocontentsentry +> +> > Used for doing a 503 Service Unavailable error. + +For more details on HTTP 5xx error status codes see `2616#section-10.5`. + +You can throw these exceptions from your controllers to indicate failure states, +or HTTP errors. An example use of the HTTP exceptions could be rendering 404 +pages for items that have not been found: + +``` php +use Cake\Http\Exception\NotFoundException; + +public function view($id = null) +{ + $article = $this->Articles->findById($id)->first(); + if (empty($article)) { + throw new NotFoundException(__('Article not found')); + } + $this->set('article', $article); + $this->viewBuilder()->setOption('serialize', ['article']); +} +``` + +By using exceptions for HTTP errors, you can keep your code both clean, and give +RESTful responses to client applications and users. + +### Using HTTP Exceptions in your Controllers + +You can throw any of the HTTP related exceptions from your controller actions +to indicate failure states. For example: + +``` php +use Cake\Network\Exception\NotFoundException; + +public function view($id = null) +{ + $article = $this->Articles->findById($id)->first(); + if (empty($article)) { + throw new NotFoundException(__('Article not found')); + } + $this->set('article', 'article'); + $this->viewBuilder()->setOption('serialize', ['article']); +} +``` + +The above would cause the configured exception handler to catch and +process the `NotFoundException`. By default this will create an error +page, and log the exception. + +### Other Built In Exceptions + +In addition, CakePHP uses the following exceptions: + +> nocontentsentry +> +> > The chosen view class could not be found. +> +> nocontentsentry +> +> > The chosen template file could not be found. +> +> nocontentsentry +> +> > The chosen layout could not be found. +> +> nocontentsentry +> +> > The chosen helper could not be found. +> +> nocontentsentry +> +> > The chosen element file could not be found. +> +> nocontentsentry +> +> > The chosen cell class could not be found. +> +> nocontentsentry +> +> > The chosen cell view file could not be found. +> +> nocontentsentry +> +> > A configured component could not be found. +> +> nocontentsentry +> +> > The requested controller action could not be found. +> +> nocontentsentry +> +> > Accessing private/protected/\_ prefixed actions. +> +> nocontentsentry +> +> > A console library class encounter an error. +> +> nocontentsentry +> +> > A model's connection is missing. +> +> nocontentsentry +> +> > A database driver could not be found. +> +> nocontentsentry +> +> > A PHP extension is missing for the database driver. +> +> nocontentsentry +> +> > A model's table could not be found. +> +> nocontentsentry +> +> > A model's entity could not be found. +> +> nocontentsentry +> +> > A model's behavior could not be found. +> +> nocontentsentry +> +> > An entity couldn't be saved/deleted while using `Cake\ORM\Table::saveOrFail()` or +> > `Cake\ORM\Table::deleteOrFail()`. +> +> nocontentsentry +> +> The requested record could not be found. This will also set HTTP response +> headers to 404. +> +> nocontentsentry +> +> > The requested controller could not be found. +> +> nocontentsentry +> +> > The requested URL cannot be reverse routed or cannot be parsed. +> +> nocontentsentry +> +> > Base exception class in CakePHP. All framework layer exceptions thrown by +> > CakePHP will extend this class. + +These exception classes all extend `Exception`. +By extending Exception, you can create your own 'framework' errors. + +`method` Class::**responseHeader**($header = null, $value = null) + +All Http and Cake exceptions extend the Exception class, which has a method +to add headers to the response. For instance when throwing a 405 +MethodNotAllowedException the rfc2616 says: + + "The response MUST include an Allow header containing a list of valid + methods for the requested resource." + +## Customizing PHP Error Handling + +By default PHP errors are rendered to console or HTML output, and also logged. +If necessary, you can swap out CakePHP's error handling logic with your own. + +### Custom Error Logging + +Error handlers use instances of `Cake\Error\ErrorLoggingInterface` to create +log messages and log them to the appropriate place. You can replace the error +logger using the `Error.logger` configure value. An example error +logger: + +``` php +namespace App\Error; + +use Cake\Error\ErrorLoggerInterface; +use Cake\Error\PhpError; +use Psr\Http\Message\ServerRequestInterface; +use Throwable; + +/** + * Log errors and unhandled exceptions to `Cake\Log\Log` + */ +class ErrorLogger implements ErrorLoggerInterface +{ + /** + * @inheritDoc + */ + public function logError( + PhpError $error, + ?ServerRequestInterface $request, + bool $includeTrace = false + ): void { + // Log PHP Errors + } + + /** + * @inheritDoc + */ + public function logException( + ?ServerRequestInterface $request, + bool $includeTrace = false + ): void { + // Log exceptions. + } +} +``` + +### Custom Error Rendering + +CakePHP includes error renderers for both web and console environments. If +however, you would like to replace the logic that renders errors you can create +a class: + +``` php +// src/Error/CustomErrorRenderer.php +namespace App\Error; + +use Cake\Error\ErrorRendererInterface; +use Cake\Error\PhpError; + +class CustomErrorRenderer implements ErrorRendererInterface +{ + public function write(string $out): void + { + // output the rendered error to the appropriate output stream + } + + public function render(PhpError $error, bool $debug): string + { + // Convert the error into the output string. + } +} +``` + +The constructor of your renderer will be passed an array of all the Error +configuration. You connect your custom error renderer to CakePHP via the +`Error.errorRenderer` config value. When replacing error handling you will +need to account for both web and command line environments. diff --git a/docs/en/development/rest.md b/docs/en/development/rest.md new file mode 100644 index 0000000000..ad54e9dd8e --- /dev/null +++ b/docs/en/development/rest.md @@ -0,0 +1,149 @@ +# REST + +REST is a foundational concept to the open web. CakePHP provides functionality +to build applications that expose REST APIs with low complexity abstractions and +interfaces. + +CakePHP provides methods for exposing your controller actions via HTTP methods, +and serializing view variables based on content-type negotiation. Content-Type +negotiation allows clients of your application to send requests with serialize +data and receive responses with serialized data via the `Accept` and +`Content-Type` headers, or URL extensions. + +## Getting Started + +To get started with adding a REST API to your application, we'll first need +a controller containing actions that we want to expose as an API. A basic +controller might look something like this: + +``` php +// src/Controller/RecipesController.php +use Cake\View\JsonView; + +class RecipesController extends AppController +{ + public function viewClasses(): array + { + return [JsonView::class]; + } + + public function index() + { + $recipes = $this->Recipes->find('all')->all(); + $this->set('recipes', $recipes); + $this->viewBuilder()->setOption('serialize', ['recipes']); + } + + public function view($id) + { + $recipe = $this->Recipes->get($id); + $this->set('recipe', $recipe); + $this->viewBuilder()->setOption('serialize', ['recipe']); + } + + public function add() + { + $this->request->allowMethod(['post', 'put']); + $recipe = $this->Recipes->newEntity($this->request->getData()); + if ($this->Recipes->save($recipe)) { + $message = 'Saved'; + } else { + $message = 'Error'; + } + $this->set([ + 'message' => $message, + 'recipe' => $recipe, + ]); + $this->viewBuilder()->setOption('serialize', ['recipe', 'message']); + } + + public function edit($id) + { + $this->request->allowMethod(['patch', 'post', 'put']); + $recipe = $this->Recipes->get($id); + $recipe = $this->Recipes->patchEntity($recipe, $this->request->getData()); + if ($this->Recipes->save($recipe)) { + $message = 'Saved'; + } else { + $message = 'Error'; + } + $this->set([ + 'message' => $message, + 'recipe' => $recipe, + ]); + $this->viewBuilder()->setOption('serialize', ['recipe', 'message']); + } + + public function delete($id) + { + $this->request->allowMethod(['delete']); + $recipe = $this->Recipes->get($id); + $message = 'Deleted'; + if (!$this->Recipes->delete($recipe)) { + $message = 'Error'; + } + $this->set('message', $message); + $this->viewBuilder()->setOption('serialize', ['message']); + } +} +``` + +In our `RecipesController`, we have several actions that define the logic +to create, edit, view and delete recipes. In each of our actions we're using +the `serialize` option to tell CakePHP which view variables should be +serialized when making API responses. We'll connect our controller to the +application URLs with [Resource Routes](../development/routing#resource-routes): + +``` php +// in config/routes.php +$routes->scope('/', function (RouteBuilder $routes): void { + $routes->setExtensions(['json']); + $routes->resources('Recipes'); +}); +``` + +These routes will enable URLs like `/recipes.json` to return a JSON encoded +response. Clients could also make a request to `/recipes` with the +`Content-Type: application/json` header as well. + +## Encoding Response Data + +In the above controller, we're defining a `viewClasses()` method. This method +defines which views your controller has available for content-negotitation. +We're including CakePHP's `JsonView` which enables JSON based responses. To +learn more about it and Xml based views see [JSON and XML views](../views/json-and-xml-views). is +used by CakePHP to select a view class to render a REST response with. + +Next, we have several methods that expose basic logic to create, edit, view and +delete recipes. In each of our actions we're using the `serialize` option to +tell CakePHP which view variables should be serialized when making API +responses. + +If we wanted to modify the data before it is converted into JSON we should not +define the `serialize` option, and instead use template files. We would place +the REST templates for our RecipesController inside **templates/Recipes/json**. + +See the [Controller Viewclasses](../controllers#controller-viewclasses) for more information on how CakePHP's +response negotiation functionality. + +## Parsing Request Bodies + +Creating the logic for the edit action requires another step. Because our +resources are serialized as JSON it would be ergonomic if our requests also +contained the JSON representation. + +In our `Application` class ensure the following is present: + +``` php +$middlewareQueue->add(new BodyParserMiddleware()); +``` + +This middleware will use the `content-type` header to detect the format of +request data and parse enabled formats. By default only `JSON` parsing is +enabled by default. You can enable XML support by enabling the `xml` +constructor option. When a request is made with a `Content-Type` of +`application/json`, CakePHP will decode the request data and update the +request so that `$request->getData()` contains the parsed body. + +You can also wire in additional deserializers for alternate formats if you +need them, using `BodyParserMiddleware::addParser()`. diff --git a/docs/en/development/routing.md b/docs/en/development/routing.md new file mode 100644 index 0000000000..31c9afb10e --- /dev/null +++ b/docs/en/development/routing.md @@ -0,0 +1,1943 @@ +# Routing + +`class` Cake\\Routing\\**RouteBuilder** + +Routing provides you tools that map URLs to controller actions. By defining +routes, you can separate how your application is implemented from how its URLs +are structured. + +Routing in CakePHP also encompasses the idea of reverse routing, where an array +of parameters can be transformed into a URL string. By using reverse routing, +you can re-factor your application's URL structure without having to update all +your code. + +
+ +routes.php + +
+ +## Quick Tour + +This section will teach you by example the most common uses of the CakePHP +Router. Typically you want to display something as a landing page, so you add +this to your **config/routes.php** file: + +``` php +/** @var \Cake\Routing\RouteBuilder $routes */ +$routes->connect('/', ['controller' => 'Articles', 'action' => 'index']); +``` + +This will execute the index method in the `ArticlesController` when the +homepage of your site is visited. Sometimes you need dynamic routes that will +accept multiple parameters, this would be the case, for example of a route for +viewing an article's content: + +``` php +$routes->connect('/articles/*', ['controller' => 'Articles', 'action' => 'view']); +``` + +The above route will accept any URL looking like `/articles/15` and invoke the +method `view(15)` in the `ArticlesController`. This will not, though, +prevent people from trying to access URLs looking like `/articles/foobar`. If +you wish, you can restrict some parameters to conform to a regular expression: + +``` php +// Using fluent interface +$routes->connect( + '/articles/{id}', + ['controller' => 'Articles', 'action' => 'view'], +) +->setPatterns(['id' => '\d+']) +->setPass(['id']); + +// Using options array +$routes->connect( + '/articles/{id}', + ['controller' => 'Articles', 'action' => 'view'], + ['id' => '\d+', 'pass' => ['id']] +); +``` + +The previous example changed the star matcher by a new placeholder `{id}`. +Using placeholders allows us to validate parts of the URL, in this case we used +the `\d+` regular expression so that only digits are matched. Finally, we told +the Router to treat the `id` placeholder as a function argument to the +`view()` function by specifying the `pass` option. More on using this +option later. + +The CakePHP Router can also reverse match routes. That means that from an +array containing matching parameters, it is capable of generating a URL string: + +``` php +use Cake\Routing\Router; + +echo Router::url(['controller' => 'Articles', 'action' => 'view', 'id' => 15]); +// Will output +/articles/15 +``` + +Routes can also be labelled with a unique name, this allows you to quickly +reference them when building links instead of specifying each of the routing +parameters: + +``` php +// In routes.php +$routes->connect( + '/upgrade', + ['controller' => 'Subscriptions', 'action' => 'create'], + ['_name' => 'upgrade'] +); + +use Cake\Routing\Router; + +echo Router::url(['_name' => 'upgrade']); +// Will output +/upgrade +``` + +To help keep your routing code DRY, the Router has the concept of 'scopes'. +A scope defines a common path segment, and optionally route defaults. Any routes +connected inside a scope will inherit the path/defaults from their wrapping +scopes: + +``` php +$routes->scope('/blog', ['plugin' => 'Blog'], function (RouteBuilder $routes) { + $routes->connect('/', ['controller' => 'Articles']); +}); +``` + +The above route would match `/blog/` and send it to +`Blog\Controller\ArticlesController::index()`. + +The application skeleton comes with a few routes to get you started. Once you've +added your own routes, you can remove the default routes if you don't need them. + +
+ +{controller}, {action}, {plugin} + +
+ +
+ +greedy star, trailing star + +
+ + + +## Connecting Routes + +To keep your code `DRY` you should use 'routing scopes'. Routing +scopes not only let you keep your code DRY, they also help Router optimize its +operation. This method defaults to the `/` scope. To create a scope and connect +some routes we'll use the `scope()` method: + +``` php +// In config/routes.php +use Cake\Routing\RouteBuilder; +use Cake\Routing\Route\DashedRoute; + +$routes->scope('/', function (RouteBuilder $routes) { + // Connect the generic fallback routes. + $routes->fallbacks(DashedRoute::class); +}); +``` + +The `connect()` method takes up to three parameters: the URL template you wish +to match, the default values for your route elements, and the options for the +route. Options frequently include regular expression rules to help the router +match elements in the URL. + +The basic format for a route definition is: + +``` php +$routes->connect( + '/url/template', + ['targetKey' => 'targetValue'], + ['option' => 'matchingRegex'] +); +``` + +The first parameter is used to tell the router what sort of URL you're trying to +control. The URL is a normal slash delimited string, but can also contain +a wildcard (\*) or [Route Elements](#route-elements). Using a wildcard tells the router +that you are willing to accept any additional arguments supplied. Routes without +a \* only match the exact template pattern supplied. + +Once you've specified a URL, you use the last two parameters of `connect()` to +tell CakePHP what to do with a request once it has been matched. The second +parameter defines the route 'target'. This can be defined either as an array, or +as a destination string. A few examples of route targets are: + +``` php +// Array target to an application controller +$routes->connect( + '/users/view/*', + ['controller' => 'Users', 'action' => 'view'] +); +$routes->connect('/users/view/*', 'Users::view'); + +// Array target to a prefixed plugin controller +$routes->connect( + '/admin/cms/articles', + ['prefix' => 'Admin', 'plugin' => 'Cms', 'controller' => 'Articles', 'action' => 'index'] +); +$routes->connect('/admin/cms/articles', 'Cms.Admin/Articles::index'); +``` + +The first route we connect matches URLs starting with `/users/view` and maps +those requests to the `UsersController->view()`. The trailing `/*` tells the +router to pass any additional segments as method arguments. For example, +`/users/view/123` would map to `UsersController->view(123)`. + +The above example also illustrates string targets. String targets provide +a compact way to define a route's destination. String targets have the following +syntax: + +``` text +[Plugin].[Prefix]/[Controller]::[action] +``` + +Some example string targets are: + +``` text +// Application controller +'Articles::view' + +// Application controller with prefix +Admin/Articles::view + +// Plugin controller +Cms.Articles::edit + +// Prefixed plugin controller +Vendor/Cms.Management/Admin/Articles::view +``` + +Earlier we used the greedy star (`/*`) to capture additional path segments, +there is also the trailing star (`/**`). Using a trailing double star, +will capture the remainder of a URL as a single passed argument. This is useful +when you want to use an argument that included a `/` in it: + +``` php +$routes->connect( + '/pages/**', + ['controller' => 'Pages', 'action' => 'show'] +); +``` + +The incoming URL of `/pages/the-example-/-and-proof` would result in a single +passed argument of `the-example-/-and-proof`. + +The second parameter of `connect()` can define any parameters that +compose the default route parameters: + +``` php +$routes->connect( + '/government', + ['controller' => 'Pages', 'action' => 'display', 5] +); +``` + +This example uses the second parameter of `connect()` to +define default parameters. If you built an application that features products for +different categories of customers, you might consider creating a route. This +allows you to link to `/government` rather than `/pages/display/5`. + +A common use for routing is to rename controllers and their actions. Instead of +accessing our users controller at `/users/some-action/5`, we'd like to be able +to access it through `/cooks/some-action/5`. The following route takes care of +that: + +``` php +$routes->connect( + '/cooks/{action}/*', ['controller' => 'Users'] +); +``` + +This is telling the Router that any URL beginning with `/cooks/` should be +sent to the `UsersController`. The action called will depend on the value of +the `{action}` parameter. By using [Route Elements](#route-elements), you can create +variable routes, that accept user input or variables. The above route also uses +the greedy star. The greedy star indicates that this route should accept any +additional positional arguments given. These arguments will be made available in +the [Passed Arguments](#passed-arguments) array. + +When generating URLs, routes are used too. Using +`['controller' => 'Users', 'action' => 'some-action', 5]` as +a URL will output `/cooks/some-action/5` if the above route is the +first match found. + +The routes we've connected so far will match any HTTP verb. If you are building +a REST API you'll often want to map HTTP actions to different controller methods. +The `RouteBuilder` provides helper methods that make defining routes for +specific HTTP verbs simpler: + +``` php +// Create a route that only responds to GET requests. +$routes->get( + '/cooks/{id}', + ['controller' => 'Users', 'action' => 'view'], + 'users:view' +); + +// Create a route that only responds to PUT requests +$routes->put( + '/cooks/{id}', + ['controller' => 'Users', 'action' => 'update'], + 'users:update' +); +``` + +The above routes map the same URL to different controller actions based on the +HTTP verb used. GET requests will go to the 'view' action, while PUT requests +will go to the 'update' action. There are HTTP helper methods for: + +- GET +- POST +- PUT +- PATCH +- DELETE +- OPTIONS +- HEAD + +All of these methods return the route instance allowing you to leverage the +[fluent setters](#route-fluent-methods) to further configure your route. + + + +### Route Elements + +You can specify your own route elements and doing so gives you the +power to define places in the URL where parameters for controller +actions should lie. When a request is made, the values for these +route elements are found in `$this->request->getParam()` in the controller. +When you define a custom route element, you can optionally specify a regular +expression - this tells CakePHP how to know if the URL is correctly formed or +not. If you choose to not provide a regular expression, any non `/` character +will be treated as part of the parameter: + +``` php +$routes->connect( + '/{controller}/{id}', + ['action' => 'view'] +)->setPatterns(['id' => '[0-9]+']); + +$routes->connect( + '/{controller}/{id}', + ['action' => 'view'], + ['id' => '[0-9]+'] +); +``` + +The above example illustrates how to create a quick way to view +models from any controller by crafting a URL that looks like +`/controller-name/{id}`. The URL provided to `connect()` specifies two +route elements: `{controller}` and `{id}`. The `{controller}` element +is a CakePHP default route element, so the router knows how to match and +identify controller names in URLs. The `{id}` element is a custom +route element, and must be further clarified by specifying a +matching regular expression in the third parameter of `connect()`. + +CakePHP does not automatically produce lowercased and dashed URLs when using the +`{controller}` parameter. If you need this, the above example could be +rewritten like so: + +``` php +use Cake\Routing\Route\DashedRoute; + +// Create a builder with a different route class. +$routes->scope('/', function (RouteBuilder $routes) { + $routes->setRouteClass(DashedRoute::class); + $routes->connect('/{controller}/{id}', ['action' => 'view']) + ->setPatterns(['id' => '[0-9]+']); + + $routes->connect( + '/{controller}/{id}', + ['action' => 'view'], + ['id' => '[0-9]+'] + ); +}); +``` + +The `DashedRoute` class will make sure that the `{controller}` and +`{plugin}` parameters are correctly lowercased and dashed. + +> [!NOTE] +> Patterns used for route elements must not contain any capturing +> groups. If they do, Router will not function correctly. + +Once this route has been defined, requesting `/apples/5` would call the `view()` +method of the ApplesController. Inside the `view()` method, you would need to +access the passed ID at `$this->request->getParam('id')`. + +If you have a single controller in your application and you do not want the +controller name to appear in the URL, you can map all URLs to actions in your +controller. For example, to map all URLs to actions of the `home` controller, +e.g have URLs like `/demo` instead of `/home/demo`, you can do the +following: + +``` php +$routes->connect('/{action}', ['controller' => 'Home']); +``` + +If you would like to provide a case insensitive URL, you can use regular +expression inline modifiers: + +``` php +$routes->connect( + '/{userShortcut}', + ['controller' => 'Teachers', 'action' => 'profile', 1], +)->setPatterns(['userShortcut' => '(?i:principal)']); +``` + +One more example, and you'll be a routing pro: + +``` php +$routes->connect( + '/{controller}/{year}/{month}/{day}', + ['action' => 'index'] +)->setPatterns([ + 'year' => '[12][0-9]{3}', + 'month' => '0[1-9]|1[012]', + 'day' => '0[1-9]|[12][0-9]|3[01]' +]); +``` + +This is rather involved, but shows how powerful routes can be. The URL supplied +has four route elements. The first is familiar to us: it's a default route +element that tells CakePHP to expect a controller name. + +Next, we specify some default values. Regardless of the controller, +we want the `index()` action to be called. + +Finally, we specify some regular expressions that will match years, +months and days in numerical form. Note that parenthesis (capturing groups) +are not supported in the regular expressions. You can still specify +alternates, as above, but not grouped with parenthesis. + +Once defined, this route will match `/articles/2007/02/01`, +`/articles/2004/11/16`, handing the requests to +the `index()` actions of their respective controllers, with the date +parameters in `$this->request->getParam()`. + +### Reserved Route Elements + +There are several route elements that have special meaning in +CakePHP, and should not be used unless you want the special meaning + +- `controller` Used to name the controller for a route. +- `action` Used to name the controller action for a route. +- `plugin` Used to name the plugin a controller is located in. +- `prefix` Used for [Prefix Routing](#prefix-routing) +- `_ext` Used for [File extentions routing](#file-extensions). +- `_base` Set to `false` to remove the base path from the generated URL. If + your application is not in the root directory, this can be used to generate + URLs that are 'cake relative'. +- `_scheme` Set to create links on different schemes like webcal or ftp. + Defaults to the current scheme. +- `_host` Set the host to use for the link. Defaults to the current host. +- `_port` Set the port if you need to create links on non-standard ports. +- `_full` If `true` the value of `App.fullBaseUrl` mentioned in + [General Configuration](../development/configuration#general-configuration) will be prepended to generated URLs. +- `#` Allows you to set URL hash fragments. +- `_https` Set to `true` to convert the generated URL to https or `false` + to force http. Prior to 4.5.0 use `_ssl`. +- `_method` Define the HTTP verb/method to use. Useful when working with + [Resource Routes](#resource-routes). +- `_name` Name of route. If you have setup named routes, you can use this key + to specify it. + + + +### Configuring Route Options + +There are a number of route options that can be set on each route. After +connecting a route you can use its fluent builder methods to further configure +the route. These methods replace many of the keys in the `$options` parameter +of `connect()`: + +``` php +$routes->connect( + '/{lang}/articles/{slug}', + ['controller' => 'Articles', 'action' => 'view'], +) +// Allow GET and POST requests. +->setMethods(['GET', 'POST']) + +// Only match on the blog subdomain. +->setHost('blog.example.com') + +// Set the route elements that should be converted to passed arguments +->setPass(['slug']) + +// Set the matching patterns for route elements +->setPatterns([ + 'slug' => '[a-z0-9-_]+', + 'lang' => 'en|fr|es', +]) + +// Also allow JSON file extensions +->setExtensions(['json']) + +// Set lang to be a persistent parameter +->setPersist(['lang']); +``` + +### Setting Default Options for Routes in a Scope + +You can set default options that will be applied to all routes within a scope +using the `setOptions()` method. This is useful when you want to apply the +same options (like `_host`, `_https`, or `_port`) to multiple routes +without repeating them: + +``` php +$routes->scope('/api', function (RouteBuilder $routes) { + // Set default options for all routes in this scope + $routes->setOptions([ + '_host' => 'api.example.com', + '_https' => true + ]); + + // These routes will automatically have _host and _https set + $routes->get('/users', ['controller' => 'Users', 'action' => 'index']); + $routes->get('/posts', ['controller' => 'Posts', 'action' => 'index']); +}); +``` + +Options set via `setOptions()` are: + +- **Inherited by nested scopes** - Child scopes automatically receive the parent's default options +- **Overridable on individual routes** - Options passed to `connect()` or HTTP verb methods take precedence over defaults +- **Merged with route-specific options** - Default options are combined with any options you specify on individual routes + +Example with nested scopes and overrides: + +``` php +$routes->scope('/api', function (RouteBuilder $routes) { + $routes->setOptions(['_host' => 'api.example.com']); + + // This route uses the default host + $routes->get('/public', ['controller' => 'Public', 'action' => 'index']); + + // This route overrides the default host + $routes->get('/internal', [ + 'controller' => 'Internal', + 'action' => 'index', + '_host' => 'internal.example.com' + ]); + + // Nested scope inherits the default host + $routes->scope('/v2', function (RouteBuilder $routes) { + // This also uses api.example.com + $routes->get('/users', ['controller' => 'Users', 'action' => 'index']); + }); +}); +``` + +The `setOptions()` method is particularly useful for: + +- Setting the same `_host` for an entire API scope +- Enforcing `_https => true` for secure sections of your application +- Configuring `_port` for routes that should use non-standard ports + +### Passing Parameters to Action + +When connecting routes using [Route Elements](#route-elements) you may want to have routed +elements be passed arguments instead. The `pass` option indicates which route +elements should also be made available as arguments passed into the controller +functions: + +``` php +// src/Controller/BlogsController.php +public function view($articleId = null, $slug = null) +{ + // Some code here... +} + +// routes.php +$routes->scope('/', function (RouteBuilder $routes) { + $routes->connect( + '/blog/{id}-{slug}', // For example, /blog/3-CakePHP_Rocks + ['controller' => 'Blogs', 'action' => 'view'] + ) + // Define the route elements in the route template + // to prepend as function arguments. Order matters as this + // will pass the `$id` and `$slug` elements as the first and + // second parameters. Any additional passed parameters in your + // route will be added after the setPass() arguments. + ->setPass(['id', 'slug']) + // Define a pattern that `id` must match. + ->setPatterns([ + 'id' => '[0-9]+', + ]); +}); +``` + +Now thanks to the reverse routing capabilities, you can pass in the URL array +like below and CakePHP will know how to form the URL as defined in the routes: + +``` php +// view.php +// This will return a link to /blog/3-CakePHP_Rocks +echo $this->Html->link('CakePHP Rocks', [ + 'controller' => 'Blog', + 'action' => 'view', + 'id' => 3, + 'slug' => 'CakePHP_Rocks' +]); + +// You can also used numerically indexed parameters. +echo $this->Html->link('CakePHP Rocks', [ + 'controller' => 'Blog', + 'action' => 'view', + 3, + 'CakePHP_Rocks' +]); +``` + + + +### Using Path Routing + +We talked about string targets above. The same also works for URL generation using +`Router::pathUrl()`: + +``` php +echo Router::pathUrl('Articles::index'); +// outputs: /articles + +echo Router::pathUrl('MyBackend.Admin/Articles::view', [3]); +// outputs: /admin/my-backend/articles/view/3 +``` + +> [!TIP] +> IDE support for Path Routing autocomplete can be enabled with [CakePHP IdeHelper Plugin](https://github.com/dereuromark/cakephp-ide-helper). + + + +### Using Named Routes + +Sometimes you'll find typing out all the URL parameters for a route too verbose, +or you'd like to take advantage of the performance improvements that named +routes have. When connecting routes you can specify a `_name` option, this +option can be used in reverse routing to identify the route you want to use: + +``` php +// Connect a route with a name. +$routes->connect( + '/login', + ['controller' => 'Users', 'action' => 'login'], + ['_name' => 'login'] +); + +// Name a verb specific route +$routes->post( + '/logout', + ['controller' => 'Users', 'action' => 'logout'], + 'logout' +); + +// Generate a URL using a named route. +$url = Router::url(['_name' => 'logout']); + +// Generate a URL using a named route, +// with some query string args. +$url = Router::url(['_name' => 'login', 'username' => 'jimmy']); +``` + +If your route template contains any route elements like `{controller}` you'll +need to supply those as part of the options to `Router::url()`. + +> [!NOTE] +> Route names must be unique across your entire application. The same +> `_name` cannot be used twice, even if the names occur inside a different +> routing scope. + +When building named routes, you will probably want to stick to some conventions +for the route names. CakePHP makes building up route names easier by allowing +you to define name prefixes in each scope: + +``` php +$routes->scope('/api', ['_namePrefix' => 'api:'], function (RouteBuilder $routes) { + // This route's name will be `api:ping` + $routes->get('/ping', ['controller' => 'Pings'], 'ping'); +}); +// Generate a URL for the ping route +Router::url(['_name' => 'api:ping']); + +// Use namePrefix with plugin() +$routes->plugin('Contacts', ['_namePrefix' => 'contacts:'], function (RouteBuilder $routes) { + // Connect routes. +}); + +// Or with prefix() +$routes->prefix('Admin', ['_namePrefix' => 'admin:'], function (RouteBuilder $routes) { + // Connect routes. +}); +``` + +You can also use the `_namePrefix` option inside nested scopes and it works as +you'd expect: + +``` php +$routes->plugin('Contacts', ['_namePrefix' => 'contacts:'], function (RouteBuilder $routes) { + $routes->scope('/api', ['_namePrefix' => 'api:'], function (RouteBuilder $routes) { + // This route's name will be `contacts:api:ping` + $routes->get('/ping', ['controller' => 'Pings'], 'ping'); + }); +}); + +// Generate a URL for the ping route +Router::url(['_name' => 'contacts:api:ping']); +``` + +Routes connected in named scopes will only have names added if the route is also +named. Nameless routes will not have the `_namePrefix` applied to them. + +
+ +admin routing, prefix routing + +
+ + + +### Prefix Routing + +`static` Cake\\Routing\\RouteBuilder::**prefix**($name, $callback) + +Many applications require an administration section where +privileged users can make changes. This is often done through a +special URL such as `/admin/users/edit/5`. In CakePHP, prefix routing +can be enabled by using the `prefix` scope method: + +``` php +use Cake\Routing\Route\DashedRoute; + +$routes->prefix('Admin', function (RouteBuilder $routes) { + // All routes here will be prefixed with `/admin`, and + // have the `'prefix' => 'Admin'` route element added that + // will be required when generating URLs for these routes + $routes->fallbacks(DashedRoute::class); +}); +``` + +Prefixes are mapped to sub-namespaces in your application's `Controller` +namespace. By having prefixes as separate controllers you can create smaller and +simpler controllers. Behavior that is common to the prefixed and non-prefixed +controllers can be encapsulated using inheritance, +[Components](../controllers/components), or traits. Using our users example, accessing +the URL `/admin/users/edit/5` would call the `edit()` method of our +**src/Controller/Admin/UsersController.php** passing 5 as the first parameter. +The view file used would be **templates/Admin/Users/edit.php** + +You can map the URL /admin to your `index()` action of pages controller using +following route: + +``` php +$routes->prefix('Admin', function (RouteBuilder $routes) { + // Because you are in the admin scope, + // you do not need to include the /admin prefix + // or the Admin route element. + $routes->connect('/', ['controller' => 'Pages', 'action' => 'index']); +}); +``` + +When creating prefix routes, you can set additional route parameters using +the `$options` argument: + +``` php +$routes->prefix('Admin', ['param' => 'value'], function (RouteBuilder $routes) { + // Routes connected here are prefixed with '/admin' and + // have the 'param' routing key set. + $routes->connect('/{controller}'); +}); +``` + +Note the additional route parameters will be added to all the connected routes defined +inside the prefix block. You will need to use all the parameters in the url array to +build the route later, if you don't use them you'll get a `MissingRouteException`. + +Multi word prefixes are by default converted using dasherize inflection, ie `MyPrefix` +would be mapped to `my-prefix` in the URL. Make sure to set a path for such prefixes +if you want to use a different format like for example underscoring: + +``` php +$routes->prefix('MyPrefix', ['path' => '/my_prefix'], function (RouteBuilder $routes) { + // Routes connected here are prefixed with '/my_prefix' + $routes->connect('/{controller}'); +}); +``` + +You can define prefixes inside plugin scopes as well: + +``` php +$routes->plugin('DebugKit', function (RouteBuilder $routes) { + $routes->prefix('Admin', function (RouteBuilder $routes) { + $routes->connect('/{controller}'); + }); +}); +``` + +The above would create a route template like `/debug-kit/admin/{controller}`. +The connected route would have the `plugin` and `prefix` route elements set. + +When defining prefixes, you can nest multiple prefixes if necessary: + +``` php +$routes->prefix('Manager', function (RouteBuilder $routes) { + $routes->prefix('Admin', function (RouteBuilder $routes) { + $routes->connect('/{controller}/{action}'); + }); +}); +``` + +The above would create a route template like `/manager/admin/{controller}/{action}`. +The connected route would have the `prefix` route element set to +`Manager/Admin`. + +The current prefix will be available from the controller methods through +`$this->request->getParam('prefix')` + +When using prefix routes it's important to set the `prefix` option, and to +use the same CamelCased format that is used in the `prefix()` method. Here's +how to build this link using the HTML helper: + +``` php +// Go into a prefixed route. +echo $this->Html->link( + 'Manage articles', + ['prefix' => 'Manager/Admin', 'controller' => 'Articles', 'action' => 'add'] +); + +// Leave a prefix +echo $this->Html->link( + 'View Post', + ['prefix' => false, 'controller' => 'Articles', 'action' => 'view', 5] +); +``` + +
+ +plugin routing + +
+ +### Creating Links to Prefix Routes + +You can create links that point to a prefix, by adding the prefix key to your +URL array: + +``` php +echo $this->Html->link( + 'New admin todo', + ['prefix' => 'Admin', 'controller' => 'TodoItems', 'action' => 'create'] +); +``` + +When using nesting, you need to chain them together: + +``` php +echo $this->Html->link( + 'New todo', + ['prefix' => 'Admin/MyPrefix', 'controller' => 'TodoItems', 'action' => 'create'] +); +``` + +This would link to a controller with the namespace `App\Controller\Admin\MyPrefix` and the file path +`src/Controller/Admin/MyPrefix/TodoItemsController.php`. + +> [!NOTE] +> The prefix is always CamelCased here, even if the routing result is dashed. +> The route itself will do the inflection if necessary. + +### Plugin Routing + +`static` Cake\\Routing\\RouteBuilder::**plugin**($name, $options = [], $callback) + +Routes for [Plugins](../plugins) should be created using the `plugin()` +method. This method creates a new routing scope for the plugin's routes: + +``` php +$routes->plugin('DebugKit', function (RouteBuilder $routes) { + // Routes connected here are prefixed with '/debug-kit' and + // have the plugin route element set to 'DebugKit'. + $routes->connect('/{controller}'); +}); +``` + +When creating plugin scopes, you can customize the path element used with the +`path` option: + +``` php +$routes->plugin('DebugKit', ['path' => '/debugger'], function (RouteBuilder $routes) { + // Routes connected here are prefixed with '/debugger' and + // have the plugin route element set to 'DebugKit'. + $routes->connect('/{controller}'); +}); +``` + +When using scopes you can nest plugin scopes within prefix scopes: + +``` php +$routes->prefix('Admin', function (RouteBuilder $routes) { + $routes->plugin('DebugKit', function (RouteBuilder $routes) { + $routes->connect('/{controller}'); + }); +}); +``` + +The above would create a route that looks like `/admin/debug-kit/{controller}`. +It would have the `prefix`, and `plugin` route elements set. The +[Plugin Routes](../plugins#plugin-routes) section has more information on building plugin routes. + +### Creating Links to Plugin Routes + +You can create links that point to a plugin, by adding the plugin key to your +URL array: + +``` php +echo $this->Html->link( + 'New todo', + ['plugin' => 'Todo', 'controller' => 'TodoItems', 'action' => 'create'] +); +``` + +Conversely if the active request is a plugin request and you want to create +a link that has no plugin you can do the following: + +``` php +echo $this->Html->link( + 'New todo', + ['plugin' => null, 'controller' => 'Users', 'action' => 'profile'] +); +``` + +By setting `'plugin' => null` you tell the Router that you want to +create a link that is not part of a plugin. + +### SEO-Friendly Routing + +Some developers prefer to use dashes in URLs, as it's perceived to give +better search engine rankings. The `DashedRoute` class can be used in your +application with the ability to route plugin, controller, and camelized action +names to a dashed URL. + +For example, if we had a `ToDo` plugin, with a `TodoItems` controller, and a +`showItems()` action, it could be accessed at `/to-do/todo-items/show-items` +with the following router connection: + +``` php +use Cake\Routing\Route\DashedRoute; + +$routes->plugin('ToDo', ['path' => 'to-do'], function (RouteBuilder $routes) { + $routes->fallbacks(DashedRoute::class); +}); +``` + +### Matching Specific HTTP Methods + +Routes can match specific HTTP methods using the HTTP verb helper methods: + +``` php +$routes->scope('/', function (RouteBuilder $routes) { + // This route only matches on POST requests. + $routes->post( + '/reviews/start', + ['controller' => 'Reviews', 'action' => 'start'] + ); + + // Match multiple verbs + $routes->connect( + '/reviews/start', + [ + 'controller' => 'Reviews', + 'action' => 'start', + ] + )->setMethods(['POST', 'PUT']); +}); +``` + +You can match multiple HTTP methods by using an array. Because the `_method` +parameter is a routing key, it participates in both URL parsing and URL +generation. To generate URLs for method specific routes you'll need to include +the `_method` key when generating the URL: + +``` php +$url = Router::url([ + 'controller' => 'Reviews', + 'action' => 'start', + '_method' => 'POST', +]); +``` + +### Matching Specific Hostnames + +Routes can use the `_host` option to only match specific hosts. You can use +the `*.` wildcard to match any subdomain: + +``` php +$routes->scope('/', function (RouteBuilder $routes) { + // This route only matches on http://images.example.com + $routes->connect( + '/images/default-logo.png', + ['controller' => 'Images', 'action' => 'default'] + )->setHost('images.example.com'); + + // This route only matches on http://*.example.com + $routes->connect( + '/images/old-logo.png', + ['controller' => 'Images', 'action' => 'oldLogo'] + )->setHost('*.example.com'); +}); +``` + +The `_host` option is also used in URL generation. If your `_host` option +specifies an exact domain, that domain will be included in the generated URL. +However, if you use a wildcard, then you will need to provide the `_host` +parameter when generating URLs: + +``` php +// If you have this route +$routes->connect( + '/images/old-logo.png', + ['controller' => 'Images', 'action' => 'oldLogo'] +)->setHost('images.example.com'); + +// You need this to generate a url +echo Router::url([ + 'controller' => 'Images', + 'action' => 'oldLogo', + '_host' => 'images.example.com', +]); +``` + +
+ +file extensions + +
+ + + +### Routing File Extensions + +`method` Cake\\Routing\\RouteBuilder::**setExtensions**(array|string $extensions) + +To handle different file extensions in your URLs, you can define the extensions +using the `Cake\Routing\RouteBuilder::setExtensions()` method: + +``` php +$routes->scope('/', function (RouteBuilder $routes) { + $routes->setExtensions(['json', 'xml']); +}); +``` + +This will enable the named extensions for all routes that are being connected in +that scope **after** the `setExtensions()` call, including those that are being +connected in nested scopes. + +> [!NOTE] +> Setting the extensions should be the first thing you do in a scope, as the +> extensions will only be applied to routes connected **after** the extensions +> are set. +> +> Also be aware that re-opened scopes will **not** inherit extensions defined in +> previously opened scopes. + +By using extensions, you tell the router to remove any matching file extensions +from the URL, and then parse what remains. If you want to create a URL such as +/page/title-of-page.html you would create your route using: + +``` php +$routes->scope('/page', function (RouteBuilder $routes) { + $routes->setExtensions(['json', 'xml', 'html']); + $routes->connect( + '/{title}', + ['controller' => 'Pages', 'action' => 'view'] + )->setPass(['title']); +}); +``` + +Then to create links which map back to the routes simply use: + +``` php +$this->Html->link( + 'Link title', + ['controller' => 'Pages', 'action' => 'view', 'title' => 'super-article', '_ext' => 'html'] +); +``` + + + +## Route Scoped Middleware + +While Middleware can be applied to your entire application, applying middleware +to specific routing scopes offers more flexibility, as you can apply middleware +only where it is needed allowing your middleware to not concern itself with +how/where it is being applied. + +> [!NOTE] +> Applied scoped middleware will be run by [RoutingMiddleware](../controllers/middleware#routing-middleware), +> normally at the end of your application's middleware queue. + +Before middleware can be applied to a scope, it needs to be +registered into the route collection: + +``` php +// in config/routes.php +use Cake\Http\Middleware\CsrfProtectionMiddleware; +use Cake\Http\Middleware\EncryptedCookieMiddleware; + +$routes->registerMiddleware('csrf', new CsrfProtectionMiddleware()); +$routes->registerMiddleware('cookies', new EncryptedCookieMiddleware()); +``` + +Once registered, scoped middleware can be applied to specific +scopes: + +``` php +$routes->scope('/cms', function (RouteBuilder $routes) { + // Enable CSRF & cookies middleware + $routes->applyMiddleware('csrf', 'cookies'); + $routes->get('/articles/{action}/*', ['controller' => 'Articles']); +}); +``` + +In situations where you have nested scopes, inner scopes will inherit the +middleware applied in the containing scope: + +``` php +$routes->scope('/api', function (RouteBuilder $routes) { + $routes->applyMiddleware('ratelimit', 'auth.api'); + $routes->scope('/v1', function (RouteBuilder $routes) { + $routes->applyMiddleware('v1compat'); + // Define routes here. + }); +}); +``` + +In the above example, the routes defined in `/v1` will have 'ratelimit', +'auth.api', and 'v1compat' middleware applied. If you re-open a scope, the +middleware applied to routes in each scope will be isolated: + +``` php +$routes->scope('/blog', function (RouteBuilder $routes) { + $routes->applyMiddleware('auth'); + // Connect the authenticated actions for the blog here. +}); +$routes->scope('/blog', function (RouteBuilder $routes) { + // Connect the public actions for the blog here. +}); +``` + +In the above example, the two uses of the `/blog` scope do not share +middleware. However, both of these scopes will inherit middleware defined in +their enclosing scopes. + +### Grouping Middleware + +To help keep your route code `DRY (Do not Repeat Yourself)` middleware can +be combined into groups. Once combined groups can be applied like middleware +can: + +``` php +$routes->registerMiddleware('cookie', new EncryptedCookieMiddleware()); +$routes->registerMiddleware('auth', new AuthenticationMiddleware()); +$routes->registerMiddleware('csrf', new CsrfProtectionMiddleware()); +$routes->middlewareGroup('web', ['cookie', 'auth', 'csrf']); + +// Apply the group +$routes->applyMiddleware('web'); +``` + + + +## RESTful Routing + +Router helps generate RESTful routes for your controllers. RESTful routes are +helpful when you are creating API endpoints for your application. If we wanted +to allow REST access to a recipe controller, we'd do something like this: + +``` php +// In config/routes.php... + +$routes->scope('/', function (RouteBuilder $routes) { + $routes->setExtensions(['json']); + $routes->resources('Recipes'); +}); +``` + +The first line sets up a number of default routes for REST +access where method specifies the desired result format, for example, xml, +json and rss. These routes are HTTP Request Method sensitive. + +| HTTP format | URL.format | Controller action invoked | +|-------------|---------------------|--------------------------------| +| GET | /recipes.format | RecipesController::index() | +| GET | /recipes/123.format | RecipesController::view(123) | +| POST | /recipes.format | RecipesController::add() | +| PUT | /recipes/123.format | RecipesController::edit(123) | +| PATCH | /recipes/123.format | RecipesController::edit(123) | +| DELETE | /recipes/123.format | RecipesController::delete(123) | + +> [!NOTE] +> The default for pattern for resource IDs only matches integers or UUIDs. +> If your IDs are different you will have to supply a regular expression pattern +> via the `id` option, for example, `$builder->resources('Recipes', ['id' => '.*'])`. + +The HTTP method being used is detected from a few different sources. +The sources in order of preference are: + +1. The `_method` POST variable +2. The `X_HTTP_METHOD_OVERRIDE` header. +3. The `REQUEST_METHOD` header + +The `_method` POST variable is helpful in using a browser as a +REST client (or anything else that can do POST). Just set +the value of `_method` to the name of the HTTP request method you +wish to emulate. + +### Creating Nested Resource Routes + +Once you have connected resources in a scope, you can connect routes for +sub-resources as well. Sub-resource routes will be prepended by the original +resource name and a id parameter. For example: + +``` php +$routes->scope('/api', function (RouteBuilder $routes) { + $routes->resources('Articles', function (RouteBuilder $routes) { + $routes->resources('Comments'); + }); +}); +``` + +Will generate resource routes for both `articles` and `comments`. The +comments routes will look like: + +``` text +/api/articles/{article_id}/comments +/api/articles/{article_id}/comments/{id} +``` + +You can get the `article_id` in `CommentsController` by: + +``` php +$this->request->getParam('article_id'); +``` + +By default resource routes map to the same prefix as the containing scope. If +you have both nested and non-nested resource controllers you can use a different +controller in each context by using prefixes: + +``` php +$routes->scope('/api', function (RouteBuilder $routes) { + $routes->resources('Articles', function (RouteBuilder $routes) { + $routes->resources('Comments', ['prefix' => 'Articles']); + }); +}); +``` + +The above would map the 'Comments' resource to the +`App\Controller\Articles\CommentsController`. Having separate controllers lets +you keep your controller logic simpler. The prefixes created this way are +compatible with [Prefix Routing](#prefix-routing). + +> [!NOTE] +> While you can nest resources as deeply as you require, it is not recommended +> to nest more than 2 resources together. + +### Limiting the Routes Created + +By default CakePHP will connect 6 routes for each resource. If you'd like to +only connect specific resource routes you can use the `only` option: + +``` php +$routes->resources('Articles', [ + 'only' => ['index', 'view'] +]); +``` + +Would create read only resource routes. The route names are `create`, +`update`, `view`, `index`, and `delete`. + +The default **route name and controller action used** are as follows: + +| Route name | Controller action used | +|------------|------------------------| +| create | add | +| update | edit | +| view | view | +| index | index | +| delete | delete | + +### Changing the Controller Actions Used + +You may need to change the controller action names that are used when connecting +routes. For example, if your `edit()` action is called `put()` you can +use the `actions` key to rename the actions used: + +``` php +$routes->resources('Articles', [ + 'actions' => ['update' => 'put', 'create' => 'add'] +]); +``` + +The above would use `put()` for the `edit()` action, and `add()` +instead of `create()`. + +### Mapping Additional Resource Routes + +You can map additional resource methods using the `map` option: + +``` php +$routes->resources('Articles', [ + 'map' => [ + 'deleteAll' => [ + 'action' => 'deleteAll', + 'method' => 'DELETE' + ] + ] +]); +// This would connect /articles/deleteAll +``` + +In addition to the default routes, this would also connect a route for +/articles/delete-all. By default the path segment will match the key name. You +can use the 'path' key inside the resource definition to customize the path +name: + +``` php +$routes->resources('Articles', [ + 'map' => [ + 'updateAll' => [ + 'action' => 'updateAll', + 'method' => 'PUT', + 'path' => '/update-many', + ], + ], +]); +// This would connect /articles/update-many +``` + +If you define 'only' and 'map', make sure that your mapped methods are also in +the 'only' list. + +### Prefixed Resource Routing + +Resource routes can be connected to controllers in routing prefixes by +connecting routes within a prefixed scope or by using the `prefix` option: + +``` php +$routes->resources('Articles', [ + 'prefix' => 'Api', +]); +``` + + + +### Custom Route Classes for Resource Routes + +You can provide `connectOptions` key in the `$options` array for +`resources()` to provide custom setting used by `connect()`: + +``` php +$routes->scope('/', function (RouteBuilder $routes) { + $routes->resources('Books', [ + 'connectOptions' => [ + 'routeClass' => 'ApiRoute', + ] + ]; +}); +``` + +### URL Inflection for Resource Routes + +By default, multi-worded controllers' URL fragments are the dashed +form of the controller's name. For example, `BlogPostsController`'s URL fragment +would be **/blog-posts**. + +You can specify an alternative inflection type using the `inflect` option: + +``` php +$routes->scope('/', function (RouteBuilder $routes) { + $routes->resources('BlogPosts', [ + 'inflect' => 'underscore' // Will use ``Inflector::underscore()`` + ]); +}); +``` + +The above will generate URLs styled like: **/blog_posts**. + +### Changing the Path Element + +By default resource routes use an inflected form of the resource name for the +URL segment. You can set a custom URL segment with the `path` option: + +``` php +$routes->scope('/', function (RouteBuilder $routes) { + $routes->resources('BlogPosts', ['path' => 'posts']); +}); +``` + +
+ +passed arguments + +
+ + + +## Passed Arguments + +Passed arguments are additional arguments or path segments that are +used when making a request. They are often used to pass parameters +to your controller methods. : + + http://localhost/calendars/view/recent/mark + +In the above example, both `recent` and `mark` are passed arguments to +`CalendarsController::view()`. Passed arguments are given to your controllers +in three ways. First as arguments to the action method called, and secondly they +are available in `$this->request->getParam('pass')` as a numerically indexed +array. When using custom routes you can force particular parameters to go into +the passed arguments as well. + +If you were to visit the previously mentioned URL, and you +had a controller action that looked like: + +``` php +class CalendarsController extends AppController +{ + public function view($arg1, $arg2) + { + debug(func_get_args()); + } +} +``` + +You would get the following output: + +``` text +Array +( + [0] => recent + [1] => mark +) +``` + +This same data is also available at `$this->request->getParam('pass')` in your +controllers, views, and helpers. The values in the pass array are numerically +indexed based on the order they appear in the called URL: + +``` php +debug($this->request->getParam('pass')); +``` + +Either of the above would output: + +``` text +Array +( + [0] => recent + [1] => mark +) +``` + +When generating URLs, using a `routing array` you add passed +arguments as values without string keys in the array: + +``` php +['controller' => 'Articles', 'action' => 'view', 5] +``` + +Since `5` has a numeric key, it is treated as a passed argument. + +## Generating URLs + +`static` Cake\\Routing\\RouteBuilder::**url**($url = null, $full = false) + +`static` Cake\\Routing\\RouteBuilder::**reverse**($params, $full = false) + +Generating URLs or Reverse routing is a feature in CakePHP that is used to +allow you to change your URL structure without having to modify all your code. + +If you create URLs using strings like: + +``` php +$this->Html->link('View', '/articles/view/' . $id); +``` + +And then later decide that `/articles` should really be called +'posts' instead, you would have to go through your entire +application renaming URLs. However, if you defined your link like: + +``` php +//`link()` uses Router::url() internally and accepts a routing array + +$this->Html->link( + 'View', + ['controller' => 'Articles', 'action' => 'view', $id] +); +``` + +or: + +``` php +//'Router::reverse()' operates on the request parameters array +//and will produce a url string, valid input for `link()` + +$requestParams = Router::getRequest()->getAttribute('params'); +$this->Html->link('View', Router::reverse($requestParams)); +``` + +Then when you decided to change your URLs, you could do so by defining a +route. This would change both the incoming URL mapping, as well as the +generated URLs. + +The choice of technique is determined by how well you can predict the routing +array elements. + +### Using `Router::url()` + +`Router::url()` allows you to use `routing arrays ` in +situations where the array elements required are fixed or easily deduced. + +It will provide reverse routing when the destination url is well defined: + +``` php +$this->Html->link( + 'View', + ['controller' => 'Articles', 'action' => 'view', $id] +); +``` + +It is also useful when the destination is unknown but follows a well +defined pattern: + +``` php +$this->Html->link( + 'View', + ['controller' => $controller, 'action' => 'view', $id] +); +``` + +Elements with numeric keys are treated as [Passed Arguments](#passed-arguments). + +When using routing arrays, you can define both query string parameters and +document fragments using special keys: + +``` php +$routes->url([ + 'controller' => 'Articles', + 'action' => 'index', + '?' => ['page' => 1], + '#' => 'top' +]); + +// Will generate a URL like. +/articles/index?page=1#top +``` + +You can also use any of the special route elements when generating URLs: + +- `_ext` Used for [File Extensions](#file-extensions) routing. +- `_base` Set to `false` to remove the base path from the generated URL. If + your application is not in the root directory, this can be used to generate + URLs that are 'cake relative'. +- `_scheme` Set to create links on different schemes like `webcal` or + `ftp`. Defaults to the current scheme. +- `_host` Set the host to use for the link. Defaults to the current host. +- `_port` Set the port if you need to create links on non-standard ports. +- `_method` Define the HTTP verb the URL is for. +- `_full` If `true` the value of `App.fullBaseUrl` mentioned in + [General Configuration](../development/configuration#general-configuration) will be prepended to generated URLs. +- `_https` Set to `true` to convert the generated URL to https or `false` + to force http. Prior to 4.5.0 use `_ssl` +- `_name` Name of route. If you have setup named routes, you can use this key + to specify it. + +### Using `Router::reverse()` + +`Router::reverse()` allows you to use the [Request Parameters](../controllers/request-response#request-parameters) in cases +where the current URL with some modification is the basis for the destination +and the elements of the current URL are unpredictable. + +As an example, imagine a blog that allowed users to create **Articles** and +**Comments**, and to mark both as either *published* or *draft*. Both the index +page URLs might include the user id. The **Comments** URL might also include +an article id to identify what article the comment refers to. + +Here are urls for this scenario: + + /articles/index/42 + /comments/index/42/18 + +When the author uses these pages, it would be convenient to include links +that allow the page to be displayed with all results, published only, +or draft only. + +To keep the code DRY, it would be best to include the links through +an element: + +``` php +// element/filter_published.php + +$params = $this->getRequest()->getAttribute('params'); + +/* prepare url for Draft */ +$params = Hash::insert($params, '?.published', 0); +echo $this->Html->link(__('Draft'), Router::reverse($params)); + +/* Prepare url for Published */ +$params = Hash::insert($params, '?.published', 1); +echo $this->Html->link(__('Published'), Router::reverse($params)); + +/* Prepare url for All */ +$params = Hash::remove($params, '?.published'); +echo $this->Html->link(__('All'), Router::reverse($params)); +``` + +The links generated by these method calls would include one or two pass +parameters depending on the structure of the current URL. And the code +would work for any future URL, for example, if you started using +pathPrefixes or if you added more pass parameters. + +### Routing Arrays vs Request Parameters + +The significant difference between the two arrays and their use in these +reverse routing methods is in the way they include pass parameters. + +Routing arrays include pass parameters as un-keyed values in the array: + +``` php +$url = [ + 'controller' => 'Articles', + 'action' => 'View', + $id, //a pass parameter + 'page' => 3, //a query argument +]; +``` + +Request parameters include pass parameters on the 'pass' key of the array: + +``` php +$url = [ + 'controller' => 'Articles', + 'action' => 'View', + 'pass' => [$id], //the pass parameters + '?' => ['page' => 3], //the query arguments +]; +``` + +So it is possible, if you wish, to convert the request parameters into +a routing array or vice versa. + + + +## Generating Asset URLs + +The `Asset` class provides methods for generating URLs to your application's +css, javascript, images and other static asset files: + +``` php +use Cake\Routing\Asset; + +// Generate a URL to APP/webroot/js/app.js +$js = Asset::scriptUrl('app.js'); + +// Generate a URL to APP/webroot/css/app.css +$css = Asset::cssUrl('app.css'); + +// Generate a URL to APP/webroot/image/logo.png +$img = Asset::imageUrl('logo.png'); + +// Generate a URL to APP/webroot/files/upload/photo.png +$file = Asset::url('files/upload/photo.png'); +``` + +The above methods also accept an array of options as their second parameter: + +- `fullBase` Append the full URL with domain name. +- `pathPrefix` Path prefix for relative URLs. +- `plugin` You can provide `false` to prevent paths from being treated as + a plugin asset. +- `timestamp` Overrides the value of `Asset.timestamp` in Configure. Set to + `false` to skip timestamp generation. Set to `true` to apply timestamps + when debug is true. Set to `'force'` to always enable timestamping + regardless of debug value. + +``` php +// Generates http://example.org/img/logo.png +$img = Asset::url('logo.png', ['fullBase' => true]); + +// Generates /img/logo.png?1568563625 +// Where the timestamp is the last modified time of the file. +$img = Asset::url('logo.png', ['timestamp' => true]); +``` + +To generate asset URLs for files in plugins use `plugin syntax`: + +``` php +// Generates `/debug_kit/img/cake.png` +$img = Asset::imageUrl('DebugKit.cake.png'); +``` + + + +## Redirect Routing + +Redirect routing allows you to issue HTTP status 30x redirects for +incoming routes, and point them at different URLs. This is useful +when you want to inform client applications that a resource has moved +and you don't want to expose two URLs for the same content. + +Redirection routes are different from normal routes as they perform an actual +header redirection if a match is found. The redirection can occur to +a destination within your application or an outside location: + +``` php +$routes->scope('/', function (RouteBuilder $routes) { + $routes->redirect( + '/home/*', + ['controller' => 'Articles', 'action' => 'view'], + ['persist' => true] + // Or ['persist'=>['id']] for default routing where the + // view action expects $id as an argument. + ); +}) +``` + +Redirects `/home/*` to `/articles/view` and passes the parameters to +`/articles/view`. Using an array as the redirect destination allows +you to use other routes to define where a URL string should be +redirected to. You can redirect to external locations using +string URLs as the destination: + +``` php +$routes->scope('/', function (RouteBuilder $routes) { + $routes->redirect('/articles/*', 'http://google.com', ['status' => 302]); +}); +``` + +This would redirect `/articles/*` to `http://google.com` with a +HTTP status of 302. + + + +## Entity Routing + +Entity routing allows you to use an entity, an array or object implement +`ArrayAccess` as the source of routing parameters. This allows you to refactor +routes more easily, and generate URLs with less code. For example, if you start +off with a route that looks like: + +``` php +$routes->get( + '/view/{id}', + ['controller' => 'Articles', 'action' => 'view'], + 'articles:view' +); +``` + +You can generate URLs to this route using: + +``` php +// $article is an entity in the local scope. +Router::url(['_name' => 'articles:view', 'id' => $article->id]); +``` + +Later on, you may want to expose the article slug in the URL for SEO purposes. +In order to do this you would need to update everywhere you generate a URL to +the `articles:view` route, which could take some time. If we use entity routes +we pass the entire article entity into URL generation allowing us to skip any +rework when URLs require more parameters: + +``` php +use Cake\Routing\Route\EntityRoute; + +// Create entity routes for the rest of this scope. +$routes->setRouteClass(EntityRoute::class); + +// Create the route just like before. +$routes->get( + '/view/{id}/{slug}', + ['controller' => 'Articles', 'action' => 'view'], + 'articles:view' +); +``` + +Now we can generate URLs using the `_entity` key: + +``` php +Router::url(['_name' => 'articles:view', '_entity' => $article]); +``` + +This will extract both the `id` property and the `slug` property out of the +provided entity. + + + +## Custom Route Classes + +Custom route classes allow you to extend and change how individual routes parse +requests and handle reverse routing. Route classes have a few conventions: + +- Route classes are expected to be found in the `Routing\Route` namespace of + your application or plugin. +- Route classes should extend `Cake\Routing\Route\Route`. +- Route classes should implement one or both of `match()` and/or `parse()`. + +The `parse()` method is used to parse an incoming URL. It should generate an +array of request parameters that can be resolved into a controller & action. +Return `null` from this method to indicate a match failure. + +The `match()` method is used to match an array of URL parameters and create a +string URL. If the URL parameters do not match the route `false` should be +returned. + +You can use a custom route class when making a route by using the `routeClass` +option: + +``` php +$routes->connect( + '/{slug}', + ['controller' => 'Articles', 'action' => 'view'], + ['routeClass' => 'SlugRoute'] +); + +// Or by setting the routeClass in your scope. +$routes->scope('/', function (RouteBuilder $routes) { + $routes->setRouteClass('SlugRoute'); + $routes->connect( + '/{slug}', + ['controller' => 'Articles', 'action' => 'view'] + ); +}); +``` + +This route would create an instance of `SlugRoute` and allow you +to implement custom parameter handling. You can use plugin route classes using +standard `plugin syntax`. + +### Default Route Class + +`static` Cake\\Routing\\RouteBuilder::**setRouteClass**($routeClass = null) + +If you want to use an alternate route class for your routes besides the +default `Route`, you can do so by calling `RouteBuilder::setRouteClass()` +before setting up any routes and avoid having to specify the `routeClass` +option for each route. For example using: + +``` php +use Cake\Routing\Route\DashedRoute; + +$routes->setRouteClass(DashedRoute::class); +``` + +will cause all routes connected after this to use the `DashedRoute` route class. +Calling the method without an argument will return current default route class. + +### Fallbacks Method + +`method` Cake\\Routing\\RouteBuilder::**fallbacks**($routeClass = null) + +The fallbacks method is a simple shortcut for defining default routes. The +method uses the passed routing class for the defined rules or if no class is +provided the class returned by `RouteBuilder::setRouteClass()` is used. + +Calling fallbacks like so: + +``` php +use Cake\Routing\Route\DashedRoute; + +$routes->fallbacks(DashedRoute::class); +``` + +Is equivalent to the following explicit calls: + +``` php +use Cake\Routing\Route\DashedRoute; + +$routes->connect('/{controller}', ['action' => 'index'], ['routeClass' => DashedRoute::class]); +$routes->connect('/{controller}/{action}/*', [], ['routeClass' => DashedRoute::class]); +``` + +> [!NOTE] +> Using the default route class (`Route`) with fallbacks, or any route +> with `{plugin}` and/or `{controller}` route elements will result in +> inconsistent URL case. + +> [!WARNING] +> Fallback route templates are very generic and allow URLs to be generated +> and parsed for controllers & actions that do not exist. Fallback URLs can +> also introduce ambiguity and duplication in your URLs. +> +> As your application grows, it is recommended to move away from fallback URLs +> and explicitly define the routes in your application. + +## Creating Persistent URL Parameters + +You can hook into the URL generation process using URL filter functions. Filter +functions are called *before* the URLs are matched against the routes, this +allows you to prepare URLs before routing. + +Callback filter functions should expect the following parameters: + +- `$params` The URL parameter array being processed. +- `$request` The current request (`Cake\Http\ServerRequest` instance). + +The URL filter function should *always* return the parameters even if unmodified. + +URL filters allow you to implement features like persistent parameters: + +``` php +Router::addUrlFilter(function (array $params, ServerRequest $request) { + if ($request->getParam('lang') && !isset($params['lang'])) { + $params['lang'] = $request->getParam('lang'); + } + + return $params; +}); +``` + +Filter functions are applied in the order they are connected. + +Another use case is changing a certain route on runtime (plugin routes for +example): + +``` php +Router::addUrlFilter(function (array $params, ServerRequest $request) { + if (empty($params['plugin']) || $params['plugin'] !== 'MyPlugin' || empty($params['controller'])) { + return $params; + } + if ($params['controller'] === 'Languages' && $params['action'] === 'view') { + $params['controller'] = 'Locations'; + $params['action'] = 'index'; + $params['language'] = $params[0]; + unset($params[0]); + } + + return $params; +}); +``` + +This will alter the following route: + +``` php +Router::url(['plugin' => 'MyPlugin', 'controller' => 'Languages', 'action' => 'view', 'es']); +``` + +into this: + +``` php +Router::url(['plugin' => 'MyPlugin', 'controller' => 'Locations', 'action' => 'index', 'language' => 'es']); +``` + +> [!WARNING] +> If you are using the caching features of [Routing Middleware](../controllers/middleware#routing-middleware) you must +> define the URL filters in your application `bootstrap()` as filters are +> not part of the cached data. diff --git a/docs/en/development/sessions.md b/docs/en/development/sessions.md new file mode 100644 index 0000000000..a462517bef --- /dev/null +++ b/docs/en/development/sessions.md @@ -0,0 +1,496 @@ +# Sessions + +CakePHP provides a wrapper and suite of utility features on top of PHP's native +`session` extension. Sessions allow you to identify unique users across +requests and store persistent data for specific users. Unlike Cookies, session +data is not available on the client side. Usage of `$_SESSION` is generally +avoided in CakePHP, and instead usage of the Session classes is preferred. + + + +## Session Configuration + +Session configuration is generally defined in **/config/app.php**. The available +options are: + +- `Session.timeout` - The number of *minutes* a session can remain 'idle'. If + no request is received for `timeout` minutes, CakePHP's session + handler will expire the session. You can set this option to `0` to disable + server side idle timeouts. +- `Session.defaults` - Allows you to use the built-in default session + configurations as a base for your session configuration. See below for the + built-in defaults. +- `Session.handler` - Allows you to define a custom session handler. The core + database and cache session handlers use this. See below for additional + information on Session handlers. +- `Session.ini` - Allows you to set additional session ini settings for your + config. This combined with `Session.handler` replace the custom session + handling features of previous versions +- `Session.cookie` - The name of the cookie to use. Defaults to value set for + `session.name` php.ini config. +- `Session.cookiePath` - The url path for which session cookie is set. Maps to + the `session.cookie_path` php.ini config. Defaults to base path of app. + +CakePHP's defaults `session.cookie_secure` to `true`, when your application +is on an SSL protocol. If your application serves from both SSL and non-SSL +protocols, then you might have problems with sessions being lost. If you need +access to the session on both SSL and non-SSL domains you will want to disable +this: + +``` php +Configure::write('Session', [ + 'defaults' => 'php', + 'ini' => [ + 'session.cookie_secure' => false + ] +]); +``` + +CakePHP also sets the [SameSite](https://owasp.org/www-community/SameSite) attribute to `Lax` +by default for session cookies, which helps protect against CSRF attacks. +You can change the default value by setting `session.cookie_samesite` php.ini config: + +``` php +Configure::write('Session', [ + 'defaults' => 'php', + 'ini' => [ + 'session.cookie_samesite' => 'Strict', + ], +]); +``` + +The session cookie path defaults to app's base path. To change this you can use +the `session.cookie_path` ini value. For example if you want your session to +persist across all subdomains you can do: + +``` php +Configure::write('Session', [ + 'defaults' => 'php', + 'ini' => [ + 'session.cookie_path' => '/', + 'session.cookie_domain' => '.yourdomain.com', + ], +]); +``` + +By default PHP sets the session cookie to expire as soon as the browser is +closed, regardless of the configured `Session.timeout` value. The cookie +timeout is controlled by the `session.cookie_lifetime` ini value and can be +configured using: + +``` php +Configure::write('Session', [ + 'defaults' => 'php', + 'ini' => [ + // Invalidate the cookie after 30 minutes + 'session.cookie_lifetime' => 1800 + ] +]); +``` + +The difference between `Session.timeout` and the `session.cookie_lifetime` +value is that the latter relies on the client telling the truth about the +cookie. If you require stricter timeout checking, without relying on what the +client reports, you should use `Session.timeout`. + +Please note that `Session.timeout` corresponds to the total time of +inactivity for a user (i.e. the time without visiting any page where the session +is used), and does not limit the total amount of minutes a user can stay active +on the site. + +## Built-in Session Handlers & Configuration + +CakePHP comes with several built-in session configurations. You can either use +these as the basis for your session configuration, or you can create a fully +custom solution. To use defaults, simply set the 'defaults' key to the name of +the default you want to use. You can then override any sub setting by declaring +it in your Session config: + +``` php +Configure::write('Session', [ + 'defaults' => 'php' +]); +``` + +The above will use the built-in 'php' session configuration. You could augment +part or all of it by doing the following: + +``` php +Configure::write('Session', [ + 'defaults' => 'php', + 'cookie' => 'my_app', + 'timeout' => 4320 // 3 days +]); +``` + +The above overrides the timeout and cookie name for the 'php' session +configuration. The built-in configurations are: + +- `php` - Saves sessions with the standard settings in your php.ini file. +- `cake` - Saves sessions as files inside `tmp/sessions/`. This is a + good option when on hosts that don't allow you to write outside your own home + dir. +- `database` - Use the built-in database sessions. See below for more + information. +- `cache` - Use the built-in cache sessions. See below for more information. + +### Session Handlers + +Session handlers can also be defined in the session config array. By defining +the 'handler.engine' config key, you can name the class name, or provide +a handler instance. The class/object must implement the +native PHP `SessionHandlerInterface`. Implementing this interface will allow +`Session` to automatically map the methods for the handler. Both the core +Cache and Database session handlers use this method for saving sessions. +Additional settings for the handler should be placed inside the handler array. +You can then read those values out from inside your handler: + +``` text +'Session' => [ + 'handler' => [ + 'engine' => 'DatabaseSession', + 'model' => 'CustomSessions', + ], +] +``` + +The above shows how you could setup the Database session handler with an +application model. When using class names as your handler.engine, CakePHP will +expect to find your class in the `Http\Session` namespace. For example, if +you had an `AppSessionHandler` class, the file should be +**src/Http/Session/AppSessionHandler.php**, and the class name should be +`App\Http\Session\AppSessionHandler`. You can also use session handlers +from inside plugins. By setting the engine to `MyPlugin.PluginSessionHandler`. + +### Database Sessions + +If you need to use a database to store your session data, configure as follows: + +``` text +'Session' => [ + 'defaults' => 'database' +] +``` + +This configuration requires a database table, having this schema: + +``` sql +CREATE TABLE `sessions` ( + `id` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL, + `created` datetime DEFAULT CURRENT_TIMESTAMP, -- Optional + `modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- Optional + `data` blob DEFAULT NULL, -- for PostgreSQL use bytea instead of blob + `expires` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +You can find a copy of the schema for the sessions table in the [application skeleton](https://github.com/cakephp/app) in **config/schema/sessions.sql**. + +You can also use your own `Table` class to handle the saving of the sessions: + +``` text +'Session' => [ + 'defaults' => 'database', + 'handler' => [ + 'engine' => 'DatabaseSession', + 'model' => 'CustomSessions', + ], +] +``` + +The above will tell Session to use the built-in 'database' defaults, and +specify that a Table called `CustomSessions` will be the delegate for saving +session information to the database. + + + +### Cache Sessions + +The Cache class can be used to store sessions as well. This allows you to store +sessions in a cache like APCu, or Memcached. There are some caveats to +using cache sessions, in that if you exhaust the cache space, sessions will +start to expire as records are evicted. + +To use Cache based sessions you can configure you Session config like: + +``` php +Configure::write('Session', [ + 'defaults' => 'cache', + 'handler' => [ + 'config' => 'session', + ], +]); +``` + +This will configure Session to use the `CacheSession` class as the +delegate for saving the sessions. You can use the 'config' key which cache +configuration to use. The default cache configuration is `'default'`. + +### Session Locking + +The app skeleton comes preconfigured with a session config like this: + +``` text +'Session' => [ + 'defaults' => 'php', +], +``` + +This means CakePHP will handle sessions via what is configured in your `php.ini`. +In most cases this will be the default configuration so PHP will save any newly +created session as a file in e.g. `/var/lib/php/session` + +But this also means any computationally heavy task like querying a large dataset +combined with an active session will **lock that session file** - therefore +blocking users to e.g. open a second tab of your app to do something else +in the meantime. + +To prevent this behavior you will have to change the way how sessions are being +handled in CakePHP by using a different session handler like [Sessions Cache Sessions](#sessions-cache-sessions) +combined with the [Redis Engine](../core-libraries/caching#caching-redisengine) or another cache engine. + +> [!TIP] +> If you want to read more about Session Locking see [here](https://ma.ttias.be/php-session-locking-prevent-sessions-blocking-in-requests/) + +## Setting ini directives + +The built-in defaults attempt to provide a common base for session +configuration. You may need to tweak specific ini flags as well. CakePHP +exposes the ability to customize the ini settings for both default +configurations, as well as custom ones. The `ini` key in the session settings, +allows you to specify individual configuration values. For example you can use +it to control settings like `session.gc_divisor`: + +``` php +Configure::write('Session', [ + 'defaults' => 'php', + 'ini' => [ + 'session.cookie_name' => 'MyCookie', + 'session.cookie_lifetime' => 1800, // Valid for 30 minutes + 'session.gc_divisor' => 1000, + 'session.cookie_httponly' => true + ] +]); +``` + +## Creating a Custom Session Handler + +Creating a custom session handler is straightforward in CakePHP. In this +example we'll create a session handler that stores sessions both in the Cache +(APC) and the database. This gives us the best of fast IO of APC, +without having to worry about sessions evaporating when the cache fills up. + +First we'll need to create our custom class and put it in +**src/Http/Session/ComboSession.php**. The class should look +something like: + +``` php +namespace App\Http\Session; + +use Cake\Cache\Cache; +use Cake\Core\Configure; +use Cake\Http\Session\DatabaseSession; + +class ComboSession extends DatabaseSession +{ + protected $cacheKey; + + public function __construct() + { + $this->cacheKey = Configure::read('Session.handler.cache'); + parent::__construct(); + } + + // Read data from the session. + public function read($id): string + { + $result = Cache::read($id, $this->cacheKey); + if ($result) { + return $result; + } + + return parent::read($id); + } + + // Write data into the session. + public function write($id, $data): bool + { + Cache::write($id, $data, $this->cacheKey); + + return parent::write($id, $data); + } + + // Destroy a session. + public function destroy($id): bool + { + Cache::delete($id, $this->cacheKey); + + return parent::destroy($id); + } + + // Removes expired sessions. + public function gc($expires = null): bool + { + return parent::gc($expires); + } +} +``` + +Our class extends the built-in `DatabaseSession` so we don't have to duplicate +all of its logic and behavior. We wrap each operation with +a `Cake\Cache\Cache` operation. This lets us fetch sessions from +the fast cache, and not have to worry about what happens when we fill the cache. +In **config/app.php** make the session block look like: + +``` text +'Session' => [ + 'defaults' => 'database', + 'handler' => [ + 'engine' => 'ComboSession', + 'model' => 'Session', + 'cache' => 'apc', + ], +], +// Make sure to add a apc cache config +'Cache' => [ + 'apc' => ['engine' => 'Apc'] +] +``` + +Now our application will start using our custom session handler for reading and +writing session data. + +`class` **Session** + + + +## Accessing the Session Object + +You can access the session data any place you have access to a request object. +This means the session is accessible from: + +- Controllers +- Views +- Helpers +- Cells +- Components + +A basic example of session usage in controllers, views and cells would be: + +``` php +$name = $this->request->getSession()->read('User.name'); + +// If you are accessing the session multiple times, +// you will probably want a local variable. +$session = $this->request->getSession(); +$name = $session->read('User.name'); +``` + +In helpers, use `$this->getView()->getRequest()` to get the request object; +In components, use `$this->getController()->getRequest()`. + +## Reading & Writing Session Data + +`method` Session::**read**($key, $default = null): mixed + +You can read values from the session using `Hash::extract()` +compatible syntax: + +``` php +$session->read('Config.language', 'en'); +``` + +`method` Session::**readOrFail**($key): mixed + +The same as convenience wrapper around non-nullable return value: + +``` php +$session->readOrFail('Config.language'); +``` + +This is useful, when you know this key has to be set and you don't want to have to check +for the existence in code itself. + +`method` Session::**write**($key, $value): void + +`$key` should be the dot separated path you wish to write `$value` to: + +``` php +$session->write('Config.language', 'en'); +``` + +You may also specify one or multiple hashes like so: + +``` php +$session->write([ + 'Config.theme' => 'blue', + 'Config.language' => 'en', +]); +``` + +`method` Session::**delete**($key): void + +When you need to delete data from the session, you can use `delete()`: + +``` php +$session->delete('Some.value'); +``` + +
+ + + +
+ +When you need to read and delete data from the session, you can use +`consume()`: + +``` php +$session->consume('Some.value'); +``` + +`method` Session::**check**($key): bool + +If you want to see if data exists in the session, you can use `check()`: + +``` php +if ($session->check('Config.language')) { + // Config.language exists and is not null. +} +``` + +## Destroying the Session + +`method` Session::**destroy**(): void + +Destroying the session is useful when users log out. To destroy a session, use +the `destroy()` method: + +``` php +$session->destroy(); +``` + +Destroying a session will remove all serverside data in the session, but will +**not** remove the session cookie. + +## Rotating Session Identifiers + +`method` Session::**renew**(): void + +While the `Authentication Plugin` automatically renews the session id when users login and +logout, you may need to rotate the session id's manually. To do this use the +`renew()` method: + +``` php +$session->renew(); +``` + +## Flash Messages + +Flash messages are small messages displayed to end users once. They are often +used to present error messages, or confirm that actions took place successfully. + +To set and display flash messages you should use +[FlashComponent](../controllers/components/flash) and +[FlashHelper](../views/helpers/flash) diff --git a/docs/en/development/testing.md b/docs/en/development/testing.md new file mode 100644 index 0000000000..c25ea6daee --- /dev/null +++ b/docs/en/development/testing.md @@ -0,0 +1,2128 @@ +# Testing + +CakePHP comes with comprehensive testing support built-in. CakePHP comes with +integration for [PHPUnit](https://phpunit.de). In addition to the features +offered by PHPUnit, CakePHP offers some additional features to make testing +easier. This section will cover installing PHPUnit, and getting started with +Unit Testing, and how you can use the extensions that CakePHP offers. + +## Installing PHPUnit + +CakePHP uses PHPUnit as its underlying test framework. PHPUnit is the de-facto +standard for unit testing in PHP. It offers a deep and powerful set of features +for making sure your code does what you think it does. PHPUnit can be installed +through using either a [PHAR package](https://phpunit.de/#download) or +[Composer](https://getcomposer.org). + +### Install PHPUnit with Composer + +To install PHPUnit with Composer: + +``` bash +$ php composer.phar require --dev phpunit/phpunit:"^10.1" +``` + +This will add the dependency to the `require-dev` section of your +`composer.json`, and then install PHPUnit along with any dependencies. + +You can now run PHPUnit using: + +``` bash +$ vendor/bin/phpunit +``` + +### Using the PHAR File + +After you have downloaded the **phpunit.phar** file, you can use it to run your +tests: + +``` bash +php phpunit.phar +``` + +> [!TIP] +> As a convenience you can make phpunit.phar available globally +> on Unix or Linux with the following: +> +> ``` bash +> chmod +x phpunit.phar +> sudo mv phpunit.phar /usr/local/bin/phpunit +> phpunit --version +> ``` +> +> Please refer to the PHPUnit documentation for instructions regarding +> [Globally installing the PHPUnit PHAR on Windows](https://phpunit.de/manual/current/en/installation.html#installation.phar.windows). + +## Test Database Setup + +Remember to have debug enabled in your **config/app_local.php** file before running +any tests. Before running any tests you should be sure to add a `test` +datasource configuration to **config/app_local.php**. This configuration is used by +CakePHP for fixture tables and data: + +``` php +'Datasources' => [ + 'test' => [ + 'datasource' => 'Cake\Database\Driver\Mysql', + 'persistent' => false, + 'host' => 'dbhost', + 'username' => 'dblogin', + 'password' => 'dbpassword', + 'database' => 'test_database', + ], +], +``` + +> [!NOTE] +> It's a good idea to make the test database and your actual database +> different databases. This will prevent embarrassing mistakes later. + +## Checking the Test Setup + +After installing PHPUnit and setting up your `test` datasource configuration +you can make sure you're ready to write and run your own tests by running your +application's tests: + +``` bash +# For phpunit.phar +$ php phpunit.phar + +# For Composer installed phpunit +$ vendor/bin/phpunit +``` + +The above should run any tests you have, or let you know that no tests were run. +To run a specific test you can supply the path to the test as a parameter to +PHPUnit. For example, if you had a test case for ArticlesTable class you could +run it with: + +``` bash +$ vendor/bin/phpunit tests/TestCase/Model/Table/ArticlesTableTest +``` + +You should see a green bar with some additional information about the tests run, +and number passed. + +> [!NOTE] +> If you are on a Windows system you probably won't see any colors. + +## Test Case Conventions + +Like most things in CakePHP, test cases have some conventions. Concerning +tests: + +1. PHP files containing tests should be in your + `tests/TestCase/[Type]` directories. +2. The filenames of these files should end in **Test.php** instead + of just .php. +3. The classes containing tests should extend `Cake\TestSuite\TestCase`, + `Cake\TestSuite\IntegrationTestCase` or `\PHPUnit\Framework\TestCase`. +4. Like other classnames, the test case classnames should match the filename. + **RouterTest.php** should contain `class RouterTest extends TestCase`. +5. The name of any method containing a test (i.e. containing an + assertion) should begin with `test`, as in `testPublished()`. + You can also use the `@test` annotation to mark methods as test methods. + +## Creating Your First Test Case + +In the following example, we'll create a test case for a very simple helper +method. The helper we're going to test will be formatting progress bar HTML. +Our helper looks like: + +``` php +namespace App\View\Helper; + +use Cake\View\Helper; + +class ProgressHelper extends Helper +{ + public function bar($value) + { + $width = round($value / 100, 2) * 100; + + return sprintf( + '
+
+
', $width); + } +} +``` + +This is a very simple example, but it will be useful to show how you can create +a simple test case. After creating and saving our helper, we'll create the test +case file in **tests/TestCase/View/Helper/ProgressHelperTest.php**. In that file +we'll start with the following: + +``` php +namespace App\Test\TestCase\View\Helper; + +use App\View\Helper\ProgressHelper; +use Cake\TestSuite\TestCase; +use Cake\View\View; + +class ProgressHelperTest extends TestCase +{ + public function setUp(): void + { + } + + public function testBar(): void + { + } +} +``` + +We'll flesh out this skeleton in a minute. We've added two methods to start +with. First is `setUp()`. This method is called before every *test* method +in a test case class. Setup methods should initialize the objects needed for the +test, and do any configuration needed. In our setup method we'll add the +following: + +``` php +public function setUp(): void +{ + parent::setUp(); + $View = new View(); + $this->Progress = new ProgressHelper($View); +} +``` + +Calling the parent method is important in test cases, as `TestCase::setUp()` +does a number things like backing up the values in +`Cake\Core\Configure` and, storing the paths in +`Cake\Core\App`. + +Next, we'll fill out the test method. We'll use some assertions to ensure that +our code creates the output we expect: + +``` php +public function testBar(): void +{ + $result = $this->Progress->bar(90); + $this->assertStringContainsString('width: 90%', $result); + $this->assertStringContainsString('progress-bar', $result); + + $result = $this->Progress->bar(33.3333333); + $this->assertStringContainsString('width: 33%', $result); +} +``` + +The above test is a simple one but shows the potential benefit of using test +cases. We use `assertStringContainsString()` to ensure that our helper is returning a +string that contains the content we expect. If the result did not contain the +expected content the test would fail, and we would know that our code is +incorrect. + +By using test cases you can describe the relationship between a set of +known inputs and their expected output. This helps you be more confident of the +code you're writing as you can ensure that the code you wrote fulfills the +expectations and assertions your tests make. Additionally because tests are +code, they can be re-run whenever you make a change. This helps prevent +the creation of new bugs. + +> [!NOTE] +> EventManager is refreshed for each test method. This means that when running +> multiple tests at once, you will lose your event listeners that were +> registered in config/bootstrap.php as the bootstrap is only executed once. + + + +## Running Tests + +Once you have PHPUnit installed and some test cases written, you'll want to run +the test cases very frequently. It's a good idea to run tests before committing +any changes to help ensure you haven't broken anything. + +By using `phpunit` you can run your application tests. To run your +application's tests you can simply run: + +``` bash +vendor/bin/phpunit + +php phpunit.phar +``` + +If you have cloned the [CakePHP source from GitHub](https://github.com/cakephp/cakephp) +and wish to run CakePHP's unit-tests don't forget to execute the following `Composer` +command prior to running `phpunit` so that any dependencies are installed: + +``` bash +composer install +``` + +From your application's root directory. To run tests for a plugin that is part +of your application source, first `cd` into the plugin directory, then use +`phpunit` command that matches how you installed phpunit: + +``` bash +cd plugins + +../vendor/bin/phpunit + +php ../phpunit.phar +``` + +To run tests on a standalone plugin, you should first install the project in +a separate directory and install its dependencies: + +``` bash +git clone git://github.com/cakephp/debug_kit.git +cd debug_kit +php ~/composer.phar install +vendor/bin/phpunit +``` + +### Filtering Test Cases + +When you have larger test cases, you will often want to run a subset of the test +methods when you are trying to work on a single failing case. With the +CLI runner you can use an option to filter test methods: + +``` bash +$ phpunit --filter testSave tests/TestCase/Model/Table/ArticlesTableTest +``` + +The filter parameter is used as a case-sensitive regular expression for +filtering which test methods to run. + +### Generating Code Coverage + +You can generate code coverage reports from the command line using PHPUnit's +built-in code coverage tools. PHPUnit will generate a set of static HTML files +containing the coverage results. You can generate coverage for a test case by +doing the following: + +``` bash +$ phpunit --coverage-html webroot/coverage tests/TestCase/Model/Table/ArticlesTableTest +``` + +This will put the coverage results in your application's webroot directory. You +should be able to view the results by going to +`http://localhost/your_app/coverage`. + +You can also use `phpdbg` to generate coverage instead of xdebug. +`phpdbg` is generally faster at generating coverage: + +``` bash +$ phpdbg -qrr phpunit --coverage-html webroot/coverage tests/TestCase/Model/Table/ArticlesTableTest +``` + +### Combining Test Suites for Plugins + +Often times your application will be composed of several plugins. In these +situations it can be pretty tedious to run tests for each plugin. You can make +running tests for each of the plugins that compose your application by adding +additional `` sections to your application's **phpunit.xml.dist** +file: + +``` xml + + + tests/TestCase/ + + + + + plugins/Forum/tests/TestCase/ + + +``` + +Any additional test suites added to the `` element will +automatically be run when you use `phpunit`. + +If you are using `` to use fixtures from plugins that you have +installed with composer, the plugin's `composer.json` file should add the +fixture namespace to the autoload section. Example: + +``` json +"autoload-dev": { + "psr-4": { + "PluginName\\Test\\Fixture\\": "tests/Fixture/" + } +}, +``` + +## Test Case Lifecycle Callbacks + +Test cases have a number of lifecycle callbacks you can use when doing testing: + +- `setUp` is called before every test method. Should be used to create the + objects that are going to be tested, and initialize any data for the test. + Always remember to call `parent::setUp()` +- `tearDown` is called after every test method. Should be used to cleanup after + the test is complete. Always remember to call `parent::tearDown()`. +- `setupBeforeClass` is called once before test methods in a case are started. + This method must be *static*. +- `tearDownAfterClass` is called once after test methods in a case are started. + This method must be *static*. + + + +## Fixtures + +When testing code that depends on models and the database, one can use +**fixtures** as a way to create initial state for your application's tests. +By using fixture data you can reduce repetitive setup steps in your tests. +Fixtures are well suited to data that is common or shared amongst many or all of +your tests. Data that is only needed in a subset of tests should be created in +tests as needed. + +CakePHP uses the connection named `test` in your **config/app.php** +configuration file. If this connection is not usable, an exception will be +raised and you will not be able to use database fixtures. + +CakePHP performs the following during the course of a test run: + +1. Creates tables for each of the fixtures needed. +2. Populates tables with data. +3. Runs test methods. +4. Empties the fixture tables. + +The schema for fixtures is created at the beginning of a test run via migrations +or a SQL dump file. + +### Test Connections + +By default CakePHP will alias each connection in your application. Each +connection defined in your application's bootstrap that does not start with +`test_` will have a `test_` prefixed alias created. Aliasing connections +ensures, you don't accidentally use the wrong connection in test cases. +Connection aliasing is transparent to the rest of your application. For example +if you use the 'default' connection, instead you will get the `test` +connection in test cases. If you use the 'replica' connection, the test suite +will attempt to use 'test_replica'. + + + +### PHPUnit Configuration + +Before you can use fixtures you should double check that your `phpunit.xml` +contains the fixture extension: + +``` xml + + + + + +``` + +The extension is included in your application and plugins generated by `bake` +by default. + + + +### Creating Schema in Tests + +You can generate test database schema either via CakePHP's migrations, loading +a SQL dump file or using another external schema management tool. You should +create your schema in your application's `tests/bootstrap.php` file. + +### Creating Schema with Migrations + +If you use CakePHP's [migrations plugin](https://book.cakephp.org/migrations) to manage your +application's schema, you can reuse those migrations to generate your test +database schema as well: + +``` php +// in tests/bootstrap.php +use Migrations\TestSuite\Migrator; + +$migrator = new Migrator(); + +// Simple setup for with no plugins +$migrator->run(); + +// Run migrations for a plugin +$migrator->run(['plugin' => 'Contacts']); + +// Run the Documents migrations on the test_docs connection. +$migrator->run(['plugin' => 'Documents', 'connection' => 'test_docs']); +``` + +If you need to run multiple sets of migrations, those can be run as follows: + +``` php +$migrator->runMany([ + // Run app migrations on test connection. + ['connection' => 'test'], + // Run Contacts migrations on test connection. + ['plugin' => 'Contacts'], + // Run Documents migrations on test_docs connection. + ['plugin' => 'Documents', 'connection' => 'test_docs'] +]); +``` + +Using `runMany()` will ensure that plugins that share a database don't drop +tables as each set of migrations is run. + +The migrations plugin will only run unapplied migrations, and will reset +migrations if your current migration head differs from the applied migrations. + +You can also configure how migrations should be run in tests in your datasources +configuration. See the [migrations docs](../migrations) for more information. + +### Creating Schema with Abstract Schema + +For plugins that need to define schema in tests, but don't need or want to have +dependencies on migrations, you can define schema as a structured array of +tables. This format is not recommended for application development as it can be +time-consuming to maintain. + +Each table can define `columns`, `constraints`, and `indexes`. +An example table would be: + +``` text +return [ + 'articles' => [ + 'columns' => [ + 'id' => [ + 'type' => 'integer', + ], + 'author_id' => [ + 'type' => 'integer', + 'null' => true, + ], + 'title' => [ + 'type' => 'string', + 'null' => true, + ], + 'body' => 'text', + 'published' => [ + 'type' => 'string', + 'length' => 1, + 'default' => 'N', + ], + ], + 'constraints' => [ + 'primary' => [ + 'type' => 'primary', + 'columns' => [ + 'id', + ], + ], + ], + ], + // More tables +]; +``` + +The options available to `columns`, `indexes` and `constraints` match the +attributes that are available in CakePHP's schema reflection APIs. Tables are +created incrementally and you must take care to ensure that tables are created +before foreign key references are made. Once you have created your schema file +you can load it in your `tests/bootstrap.php` with: + +``` php +$loader = new SchemaLoader(); +$loader->loadInternalFile($pathToSchemaFile); +``` + +### Creating Schema with SQL Dump Files + +To load a SQL dump file you can use the following: + +``` php +// in tests/bootstrap.php +use Cake\TestSuite\Fixture\SchemaLoader; + +// Load one or more SQL files. +(new SchemaLoader())->loadSqlFiles('path/to/schema.sql', 'test'); +``` + +At the beginning of each test run `SchemaLoader` will drop all tables in the +connection and rebuild tables based on the provided schema file. + +### Creating Fixtures + +Fixtures defines the records that will be inserted into the test database at the +beginning of each test. Let's create our first fixture, that will be +used to test our own Article model. Create a file named **ArticlesFixture.php** +in your **tests/Fixture** directory, with the following content: + +``` php +namespace App\Test\Fixture; + +use Cake\TestSuite\Fixture\TestFixture; + +class ArticlesFixture extends TestFixture +{ + // Optional. Set this property to load fixtures + // to a different test datasource + public $connection = 'test'; + + // Optional. Lets you define which table alias is used when + // reflecting schema and inserting rows. Inferred from the + // class name by default. Added in 5.3.0 + public $tableAlias = 'Articles'; + + // Optional. Lets you define the table name for a fixture. + // If defined, this table name will be camelized to create + // $tableAlias. + public $table = 'articles'; + + public $records = [ + [ + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => '1', + 'created' => '2007-03-18 10:39:23', + 'modified' => '2007-03-18 10:41:31' + ], + [ + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => '1', + 'created' => '2007-03-18 10:41:23', + 'modified' => '2007-03-18 10:43:31' + ], + [ + 'title' => 'Third Article', + 'body' => 'Third Article Body', + 'published' => '1', + 'created' => '2007-03-18 10:43:23', + 'modified' => '2007-03-18 10:45:31' + ] + ]; + } +``` + +> [!NOTE] +> It is recommended to not manually add values to auto incremental columns, +> as it interferes with the sequence generation in PostgreSQL and SQLServer. + +The `$connection` property defines the datasource of which the fixture will +use. If your application uses multiple datasources, you should make the +fixtures match the model's datasources but prefixed with `test_`. +For example if your model uses the `mydb` datasource, your fixture should use +the `test_mydb` datasource. If the `test_mydb` connection doesn't exist, +your models will use the default `test` datasource. Fixture datasources must +be prefixed with `test` to reduce the possibility of accidentally truncating +all your application's data when running tests. + +We can define a set of records that will be populated after the fixture table is +created. The format is fairly straight forward, `$records` is an array of +records. Each item in `$records` should be a single row. Inside each row, +should be an associative array of the columns and values for the row. Just keep +in mind that each record in the `$records` array must have the same keys as +rows are bulk inserted. + +As you evolve your schema your fixture records may accumulate unused or +unsupported fields. You can enable `strictFields` on a fixture to have errors +raised when a record contains fields that are not defined in the schema: + +``` php +namespace App\Test\Fixture; + +use Cake\TestSuite\Fixture\TestFixture; + +class ArticlesFixture extends TestFixture +{ + protected $strictFields = true; + + // rest of fixture +} +``` + +The `strictFields` mode can be useful in catching typos or when you want to +enforce stricter maintenance of test data. + +::: info Added in version 5.2.0 +`TestFixture::$strictFields` was added. +::: + +### Dynamic Data + +To use functions or other dynamic data in your fixture records you can define +your records in the fixture's `init()` method: + +``` php +namespace App\Test\Fixture; + +use Cake\TestSuite\Fixture\TestFixture; + +class ArticlesFixture extends TestFixture +{ + public function init(): void + { + $this->records = [ + [ + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => '1', + 'created' => date('Y-m-d H:i:s'), + 'modified' => date('Y-m-d H:i:s'), + ], + ]; + parent::init(); + } +} +``` + +> [!NOTE] +> When overriding `init()` remember to always call `parent::init()`. + +### Loading Fixtures in your Test Cases + +After you've created your fixtures, you'll want to use them in your test cases. +In each test case you should load the fixtures you will need. You should load a +fixture for every model that will have a query run against it. To load fixtures +you define the `$fixtures` property in your model: + +``` php +class ArticlesTest extends TestCase +{ + protected $fixtures = ['app.Articles', 'app.Comments']; +} +``` + +As of 4.1.0 you can use `getFixtures()` to define your fixture list with +a method: + +``` php +public function getFixtures(): array +{ + return [ + 'app.Articles', + 'app.Comments', + ]; +} +``` + +The above will load the Article and Comment fixtures from the application's +Fixture directory. You can also load fixtures from CakePHP core, or plugins: + +``` php +class ArticlesTest extends TestCase +{ + protected $fixtures = [ + 'plugin.DebugKit.Articles', + 'plugin.MyVendorName/MyPlugin.Messages', + 'core.Comments', + ]; +} +``` + +Using the `core` prefix will load fixtures from CakePHP, and using a plugin +name as the prefix, will load the fixture from the named plugin. + +You can load fixtures in subdirectories. Using multiple directories can make it +easier to organize your fixtures if you have a larger application. To load +fixtures in subdirectories, simply include the subdirectory name in the fixture +name: + +``` php +class ArticlesTest extends CakeTestCase +{ + protected $fixtures = ['app.Blog/Articles', 'app.Blog/Comments']; +} +``` + +In the above example, both fixtures would be loaded from +`tests/Fixture/Blog/`. + +You can also directly include fixtures by FQCN: + +``` php +public function getFixtures(): array +{ + return [ + UsersFixture::class, + ArticlesFixture::class, + ]; +} +``` + + + +### Fixture State Managers + +By default CakePHP resets fixture state at the end of each test by truncating +all the tables in the database. This operation can become expensive as your +application grows. By using `TransactionStrategy` each test method will be run +inside a transaction that is rolled back at the end of the test. This can yield +improved performance but requires your tests not heavily rely on static fixture +data, as auto-increment values are not reset before each test. + +The fixture state management strategy can be defined within the test case: + +``` php +use Cake\TestSuite\TestCase; +use Cake\TestSuite\Fixture\FixtureStrategyInterface; +use Cake\TestSuite\Fixture\TransactionStrategy; + +class ArticlesTableTest extends TestCase +{ + /** + * Create the fixtures strategy used for this test case. + * You can use a base class/trait to change multiple classes. + */ + protected function getFixtureStrategy(): FixtureStrategyInterface + { + return new TransactionStrategy(); + } +} +``` + +To switch out the general default strategy, use Configure key `TestSuite.fixtureStrategy` in your `app.php`: + +``` php +'TestSuite' => [ + 'fixtureStrategy' => \Cake\TestSuite\Fixture\TransactionStrategy::class, +], +``` + +The recommended strategy for medium and large applications is the `TransactionStrategy`, as using rollbacks to undo changes from tests is simpler to maintain, and reduces the chances of cross-contamination and side-effects between tests. + +### Fixture Factories + +As your application grows, so does the number and the size of your test +fixtures. You might find it difficult to maintain them and to keep track of +their content. The [fixture factories plugin](https://github.com/vierge-noire/cakephp-fixture-factories) proposes an +alternative for large sized applications. + +The plugin uses the [test suite light plugin](https://github.com/vierge-noire/cakephp-test-suite-light) +in order to truncate all dirty tables before each test. + +The following command will help you bake your factories: + + bin/cake bake fixture_factory -h + +Once your factories are +[tuned](https://github.com/vierge-noire/cakephp-fixture-factories/blob/main/docs/factories.md), +you are ready to create test fixtures in no time. + +Unnecessary interaction with the database will slow down your tests as well as +your application. You can create test fixtures without persisting them which can +be useful for testing methods without DB interaction: + +``` php +$article = ArticleFactory::make()->getEntity(); +``` + +In order to persist: + +``` php +$article = ArticleFactory::make()->persist(); +``` + +The factories help creating associated fixtures too. +Assuming that articles belongs to many authors, we can now, for example, +create 5 articles each with 2 authors: + +``` php +$articles = ArticleFactory::make(5)->with('Authors', 2)->getEntities(); +``` + +Note that the fixture factories do not require any fixture creation or +declaration. Still, they are fully compatible with the fixtures that come with +cakephp. You will find additional insights and documentation [here](https://github.com/vierge-noire/cakephp-fixture-factories). + +## Loading Routes in Tests + +If you are testing mailers, controller components or other classes that require +routes and resolving URLs, you will need to load routes. During +the `setUp()` of a class or during individual test methods you can use +`loadRoutes()` to ensure your application routes are loaded: + +``` php +public function setUp(): void +{ + parent::setUp(); + $this->loadRoutes(); +} +``` + +This method will build an instance of your `Application` and call the +`routes()` method on it. If your `Application` class requires specialized +constructor parameters you can provide those to `loadRoutes($constructorArgs)`. + +### Creating Routes in Tests + +Sometimes it may be be necessary to dynamically add routes in tests, for example +when developing plugins, or applications that are extensible. + +Just like loading existing application routes, this can be done during `setup()` +of a test method, and/or in the individual test methods themselves: + +``` php +use Cake\Routing\Route\DashedRoute; +use Cake\Routing\RouteBuilder; +use Cake\Routing\Router; +use Cake\TestSuite\TestCase; + +class PluginHelperTest extends TestCase +{ + protected RouteBuilder $routeBuilder; + + public function setUp(): void + { + parent::setUp(); + + $this->routeBuilder = Router::createRouteBuilder('/'); + $this->routeBuilder->scope('/', function (RouteBuilder $routes) { + $routes->setRouteClass(DashedRoute::class); + $routes->get( + '/test/view/{id}', + ['controller' => 'Tests', 'action' => 'view'] + ); + // ... + }); + + // ... + } +} +``` + +This will create a new route builder instance that will merge connected routes +into the same route collection used by all other route builder instances that +may already exist, or are yet to be created in the environment. + +### Loading Plugins in Tests + +If your application would dynamically load plugins, you can use +`loadPlugins()` to load one or more plugins during tests: + +``` php +public function testMethodUsingPluginResources() +{ + $this->loadPlugins(['Company/Cms']); + // Test logic that requires Company/Cms to be loaded. +} +``` + +## Testing Table Classes + +Let's say we already have our Articles Table class defined in +**src/Model/Table/ArticlesTable.php**, and it looks like: + +``` php +namespace App\Model\Table; + +use Cake\ORM\Table; +use Cake\ORM\Query\SelectQuery; + +class ArticlesTable extends Table +{ + public function findPublished(SelectQuery $query): SelectQuery + { + $query->where([ + $this->getAlias() . '.published' => 1 + ]); + + return $query; + } +} +``` + +We now want to set up a test that will test this table class. Let's now create +a file named **ArticlesTableTest.php** in your **tests/TestCase/Model/Table** directory, +with the following contents: + +``` php +namespace App\Test\TestCase\Model\Table; + +use App\Model\Table\ArticlesTable; +use Cake\TestSuite\TestCase; + +class ArticlesTableTest extends TestCase +{ + protected $fixtures = ['app.Articles']; +} +``` + +In our test cases' variable `$fixtures` we define the set of fixtures that +we'll use. You should remember to include all the fixtures that will have +queries run against them. + +### Creating a Test Method + +Let's now add a method to test the function `published()` in the Articles +table. Edit the file **tests/TestCase/Model/Table/ArticlesTableTest.php** so it +now looks like this: + +``` php +namespace App\Test\TestCase\Model\Table; + +use App\Model\Table\ArticlesTable; +use Cake\TestSuite\TestCase; + +class ArticlesTableTest extends TestCase +{ + protected $fixtures = ['app.Articles']; + + public function setUp(): void + { + parent::setUp(); + $this->Articles = $this->getTableLocator()->get('Articles'); + } + + public function testFindPublished(): void + { + $query = $this->Articles->find('published')->select(['id', 'title']); + $this->assertInstanceOf('Cake\ORM\Query\SelectQuery', $query); + $result = $query->enableHydration(false)->toArray(); + $expected = [ + ['id' => 1, 'title' => 'First Article'], + ['id' => 2, 'title' => 'Second Article'], + ['id' => 3, 'title' => 'Third Article'] + ]; + + $this->assertEquals($expected, $result); + } +} +``` + +You can see we have added a method called `testFindPublished()`. We start by +creating an instance of our `ArticlesTable` class, and then run our +`find('published')` method. In `$expected` we set what we expect should be +the proper result (that we know since we have defined which records are +initially populated to the article table.) We test that the result equals our +expectation by using the `assertEquals()` method. See the [Running Tests](#running-tests) +section for more information on how to run your test case. + +Using the fixture factories, the test would now look like this: + +``` php +namespace App\Test\TestCase\Model\Table; + +use App\Test\Factory\ArticleFactory; +use Cake\TestSuite\TestCase; + +class ArticlesTableTest extends TestCase +{ + public function testFindPublished(): void + { + // Persist 3 published articles + $articles = ArticleFactory::make(['published' => 1], 3)->persist(); + // Persist 2 unpublished articles + ArticleFactory::make(['published' => 0], 2)->persist(); + + $result = ArticleFactory::find('published')->find('list')->toArray(); + + $expected = [ + $articles[0]->id => $articles[0]->title, + $articles[1]->id => $articles[1]->title, + $articles[2]->id => $articles[2]->title, + ]; + + $this->assertEquals($expected, $result); + } +} +``` + +No fixtures need to be loaded. The 5 articles created will exist only in this test. The +static method `::find()` will query the database without using the table `ArticlesTable` +and it's events. + +### Mocking Model Methods + +There will be times you'll want to mock methods on models when testing them. You +should use `getMockForModel` to create testing mocks of table classes. It +avoids issues with reflected properties that normal mocks have: + +``` php +public function testSendingEmails(): void +{ + $model = $this->getMockForModel('EmailVerification', ['send']); + $model->expects($this->once()) + ->method('send') + ->will($this->returnValue(true)); + + $model->verifyEmail('test@example.com'); +} +``` + +In your `tearDown()` method be sure to remove the mock with: + +``` php +$this->getTableLocator()->clear(); +``` + + + +## Controller Integration Testing + +While you can test controller classes in a similar fashion to Helpers, Models, +and Components, CakePHP offers a specialized `IntegrationTestTrait` trait. +Using this trait in your controller test cases allows you to +test controllers from a high level. + +If you are unfamiliar with integration testing, it is a testing approach that +allows you to test multiple units in concert. The integration testing +features in CakePHP simulate an HTTP request being handled by your application. +For example, testing your controller will also exercise any components, models +and helpers that would be involved in handling a given request. This gives you a +more high level test of your application and all its working parts. + +Say you have a typical ArticlesController, and its corresponding model. The +controller code looks like: + +``` php +namespace App\Controller; + +use App\Controller\AppController; + +class ArticlesController extends AppController +{ + public function index($short = null) + { + if ($this->request->is('post')) { + $article = $this->Articles->newEntity($this->request->getData()); + if ($this->Articles->save($article)) { + // Redirect as per PRG pattern + return $this->redirect(['action' => 'index']); + } + } + if (!empty($short)) { + $result = $this->Articles->find('all', fields: ['id', 'title'])->all(); + } else { + $result = $this->Articles->find()->all(); + } + + $this->set([ + 'title' => 'Articles', + 'articles' => $result + ]); + } +} +``` + +Create a file named **ArticlesControllerTest.php** in your +**tests/TestCase/Controller** directory and put the following inside: + +``` php +namespace App\Test\TestCase\Controller; + +use Cake\TestSuite\IntegrationTestTrait; +use Cake\TestSuite\TestCase; + +class ArticlesControllerTest extends TestCase +{ + use IntegrationTestTrait; + + protected $fixtures = ['app.Articles']; + + public function testIndex(): void + { + $this->get('/articles'); + + $this->assertResponseOk(); + // More asserts. + } + + public function testIndexQueryData(): void + { + $this->get('/articles?page=1'); + + $this->assertResponseOk(); + // More asserts. + } + + public function testIndexShort(): void + { + $this->get('/articles/index/short'); + + $this->assertResponseOk(); + $this->assertResponseContains('Articles'); + // More asserts. + } + + public function testIndexPostData(): void + { + $data = [ + 'user_id' => 1, + 'published' => 1, + 'slug' => 'new-article', + 'title' => 'New Article', + 'body' => 'New Body' + ]; + $this->post('/articles', $data); + + $this->assertResponseSuccess(); + $articles = $this->getTableLocator()->get('Articles'); + $query = $articles->find()->where(['title' => $data['title']]); + $this->assertEquals(1, $query->count()); + } +} +``` + +This example shows a few of the request sending methods and a few of the +assertions that `IntegrationTestTrait` provides. Before you can do any +assertions you'll need to dispatch a request. You can use one of the following +methods to send a request: + +- `get()` Sends a GET request. +- `post()` Sends a POST request. +- `put()` Sends a PUT request. +- `delete()` Sends a DELETE request. +- `patch()` Sends a PATCH request. +- `options()` Sends an OPTIONS request. +- `head()` Sends a HEAD request. + +All of the methods except `get()` and `delete()` accept a second parameter +that allows you to send a request body. After dispatching a request you can use +the various assertions provided by `IntegrationTestTrait` or PHPUnit to +ensure your request had the correct side-effects. + +### Setting up the Request + +The `IntegrationTestTrait` trait comes with a number of helpers to +to configure the requests you will send to your application under test: + +``` php +// Set cookies +$this->cookie('name', 'Uncle Bob'); + +// Set session data +$this->session(['Auth.User.id' => 1]); + +// Configure headers and merge with the existing request +$this->configRequest([ + 'headers' => ['Accept' => 'application/json'] +]); + +// Replace the existing request. Added in 5.1.0 +$this->replaceRequest([ + 'headers' => ['Accept' => 'application/json'] +]); +``` + +The state set by these helper methods is reset in the `tearDown()` method. + +::: info Added in version 5.1.0 +`replaceRequest()` was added. +::: + +### Testing Actions Protected by CsrfProtectionMiddleware or FormProtectionComponent + +When testing actions protected by either `CsrfProtectionMiddleware` or `FormProtectionComponent` you +can enable automatic token generation to ensure your tests won't fail due to +token mismatches: + +``` php +public function testAdd(): void +{ + $this->enableCsrfToken(); + $this->enableSecurityToken(); + $this->post('/posts/add', ['title' => 'Exciting news!']); +} +``` + +It is also important to enable debug in tests that use tokens to prevent the +`FormProtectionComponent` from thinking the debug token is being used in a non-debug +environment. When testing with other methods like `requireSecure()` you +can use `configRequest()` to set the correct environment variables: + +``` php +// Fake out SSL connections. +$this->configRequest([ + 'environment' => ['HTTPS' => 'on'] +]); +``` + +If your action requires unlocked fields you can declare them with +`setUnlockedFields()`: + +``` php +$this->setUnlockedFields(['dynamic_field']); +``` + +### Integration Testing PSR-7 Middleware + +Integration testing can also be used to test your entire PSR-7 application and +[Middleware](../controllers/middleware). By default `IntegrationTestTrait` will +auto-detect the presence of an `App\Application` class and automatically +enable integration testing of your Application. + +You can customize the application class name used, and the constructor +arguments, by using the `configApplication()` method: + +``` php +public function setUp(): void +{ + $this->configApplication('App\App', [CONFIG]); +} +``` + +You should also take care to try and use [Application Bootstrap](../development/application#application-bootstrap) to load +any plugins containing events/routes. Doing so will ensure that your +events/routes are connected for each test case. Alternatively if you wish to +load plugins manually in a test you can use the `loadPlugins()` method. + +### Testing with Encrypted Cookies + +If you use the [Encrypted Cookie Middleware](../controllers/middleware#encrypted-cookie-middleware) in your +application, there are helper methods for setting encrypted cookies in your +test cases: + +``` php +// Set a cookie using AES and the default key. +$this->cookieEncrypted('my_cookie', 'Some secret values'); + +// Assume this action modifies the cookie. +$this->get('/articles/index'); + +$this->assertCookieEncrypted('An updated value', 'my_cookie'); +``` + +### Testing Flash Messages + +If you want to assert the presence of flash messages in the session and not the +rendered HTML, you can use `enableRetainFlashMessages()` in your tests to +retain flash messages in the session so you can write assertions: + +``` php +// Enable retention of flash messages instead of consuming them. +$this->enableRetainFlashMessages(); +$this->get('/articles/delete/9999'); + +$this->assertSession('That article does not exist', 'Flash.flash.0.message'); + +// Assert a flash message in the 'flash' key. +$this->assertFlashMessage('Article deleted', 'flash'); + +// Assert the second flash message, also in the 'flash' key. +$this->assertFlashMessageAt(1, 'Article really deleted'); + +// Assert a flash message in the 'auth' key at the first position +$this->assertFlashMessageAt(0, 'You are not allowed to enter this dungeon!', 'auth'); + +// Assert a flash messages uses the error element +$this->assertFlashElement('Flash/error'); + +// Assert the second flash message element +$this->assertFlashElementAt(1, 'Flash/error'); + +// Assert a flash message contains a substring (Added in 5.3.0) +$this->assertFlashMessageContains('deleted', 'flash'); + +// Assert the second flash message contains a substring (Added in 5.3.0) +$this->assertFlashMessageContainsAt(1, 'really deleted'); +``` + +::: info Added in version 5.3.0 +`assertFlashMessageContains()` and `assertFlashMessageContainsAt()` were added. +::: + +### Testing a JSON Responding Controller + +JSON is a friendly and common format to use when building a web service. +Testing the endpoints of your web service is very simple with CakePHP. Let us +begin with a simple example controller that responds in JSON: + +``` php +use Cake\View\JsonView; + +class MarkersController extends AppController +{ + public function viewClasses(): array + { + return [JsonView::class]; + } + + public function view($id) + { + $marker = $this->Markers->get($id); + $this->set('marker', $marker); + $this->viewBuilder()->setOption('serialize', ['marker']); + } +} +``` + +Now we create the file **tests/TestCase/Controller/MarkersControllerTest.php** +and make sure our web service is returning the proper response: + +``` php +class MarkersControllerTest extends IntegrationTestCase +{ + use IntegrationTestTrait; + + public function testGet(): void + { + $this->configRequest([ + 'headers' => ['Accept' => 'application/json'] + ]); + $this->get('/markers/view/1.json'); + + // Check that the response was a 200 + $this->assertResponseOk(); + + $expected = [ + ['id' => 1, 'lng' => 66, 'lat' => 45], + ]; + $expected = json_encode($expected, JSON_PRETTY_PRINT); + $this->assertEquals($expected, (string)$this->_response->getBody()); + } +} +``` + +We use the `JSON_PRETTY_PRINT` option as CakePHP's built in JsonView will use +that option when `debug` is enabled. + +### Testing with file uploads + +Simulating file uploads is straightforward when you use the default +"[uploaded files as objects](../controllers/request-response#request-file-uploads)" mode. You can simply +create instances that implement +[\Psr\Http\Message\UploadedFileInterface](https://www.php-fig.org/psr/psr-7/#16-uploaded-files) +(the default implementation currently used by CakePHP is +`\Laminas\Diactoros\UploadedFile`), and pass them in your test request data. +In the CLI environment such objects will by default pass validation checks that +test whether the file was uploaded via HTTP. The same is not true for array style +data as found in `$_FILES`, it would fail that check. + +In order to simulate exactly how the uploaded file objects would be present on +a regular request, you not only need to pass them in the request data, but you also +need to pass them to the test request configuration via the `files` option. It's +not technically necessary though unless your code accesses uploaded files via the +`Cake\Http\ServerRequest::getUploadedFile()` or +`Cake\Http\ServerRequest::getUploadedFiles()` methods. + +Let's assume articles have a teaser image, and a `Articles hasMany Attachments` +association, the form would look like something like this accordingly, where one +image file, and multiple attachments/files would be accepted: + +``` php +Form->create($article, ['type' => 'file']) ?> +Form->control('title') ?> +Form->control('teaser_image', ['type' => 'file']) ?> +Form->control('attachments.0.attachment', ['type' => 'file']) ?> +Form->control('attachments.0.description']) ?> +Form->control('attachments.1.attachment', ['type' => 'file']) ?> +Form->control('attachments.1.description']) ?> +Form->button('Submit') ?> +Form->end() ?> +``` + +The test that would simulate the corresponding request could look like this: + +``` php +public function testAddWithUploads(): void +{ + $teaserImage = new \Laminas\Diactoros\UploadedFile( + '/path/to/test/file.jpg', // stream or path to file representing the temp file + 12345, // the filesize in bytes + \UPLOAD_ERR_OK, // the upload/error status + 'teaser.jpg', // the filename as sent by the client + 'image/jpeg' // the mimetype as sent by the client + ); + + $textAttachment = new \Laminas\Diactoros\UploadedFile( + '/path/to/test/file.txt', + 12345, + \UPLOAD_ERR_OK, + 'attachment.txt', + 'text/plain' + ); + + $pdfAttachment = new \Laminas\Diactoros\UploadedFile( + '/path/to/test/file.pdf', + 12345, + \UPLOAD_ERR_OK, + 'attachment.pdf', + 'application/pdf' + ); + + // This is the data accessible via `$this->request->getUploadedFile()` + // and `$this->request->getUploadedFiles()`. + $this->configRequest([ + 'files' => [ + 'teaser_image' => $teaserImage, + 'attachments' => [ + 0 => [ + 'attachment' => $textAttachment, + ], + 1 => [ + 'attachment' => $pdfAttachment, + ], + ], + ], + ]); + + // This is the data accessible via `$this->request->getData()`. + $postData = [ + 'title' => 'New Article', + 'teaser_image' => $teaserImage, + 'attachments' => [ + 0 => [ + 'attachment' => $textAttachment, + 'description' => 'Text attachment', + ], + 1 => [ + 'attachment' => $pdfAttachment, + 'description' => 'PDF attachment', + ], + ], + ]; + $this->post('/articles/add', $postData); + + $this->assertResponseOk(); + $this->assertFlashMessage('The article was saved successfully'); + $this->assertFileExists('/path/to/uploads/teaser.jpg'); + $this->assertFileExists('/path/to/uploads/attachment.txt'); + $this->assertFileExists('/path/to/uploads/attachment.pdf'); +} +``` + +> [!TIP] +> If you configure the test request with files, then it *must* match the +> structure of your POST data (but only include the uploaded file objects)! + +Likewise you can simulate [upload errors](https://www.php.net/manual/en/features.file-upload.errors.php) +or otherwise invalid files that do not pass validation: + +``` php +public function testAddWithInvalidUploads(): void +{ + $missingTeaserImageUpload = new \Laminas\Diactoros\UploadedFile( + '', + 0, + \UPLOAD_ERR_NO_FILE, + '', + '' + ); + + $uploadFailureAttachment = new \Laminas\Diactoros\UploadedFile( + '/path/to/test/file.txt', + 1234567890, + \UPLOAD_ERR_INI_SIZE, + 'attachment.txt', + 'text/plain' + ); + + $invalidTypeAttachment = new \Laminas\Diactoros\UploadedFile( + '/path/to/test/file.exe', + 12345, + \UPLOAD_ERR_OK, + 'attachment.exe', + 'application/vnd.microsoft.portable-executable' + ); + + $this->configRequest([ + 'files' => [ + 'teaser_image' => $missingTeaserImageUpload, + 'attachments' => [ + 0 => [ + 'file' => $uploadFailureAttachment, + ], + 1 => [ + 'file' => $invalidTypeAttachment, + ], + ], + ], + ]); + + $postData = [ + 'title' => 'New Article', + 'teaser_image' => $missingTeaserImageUpload, + 'attachments' => [ + 0 => [ + 'file' => $uploadFailureAttachment, + 'description' => 'Upload failure attachment', + ], + 1 => [ + 'file' => $invalidTypeAttachment, + 'description' => 'Invalid type attachment', + ], + ], + ]; + $this->post('/articles/add', $postData); + + $this->assertResponseOk(); + $this->assertFlashMessage('The article could not be saved'); + $this->assertResponseContains('A teaser image is required'); + $this->assertResponseContains('Max allowed filesize exceeded'); + $this->assertResponseContains('Unsupported file type'); + $this->assertFileNotExists('/path/to/uploads/teaser.jpg'); + $this->assertFileNotExists('/path/to/uploads/attachment.txt'); + $this->assertFileNotExists('/path/to/uploads/attachment.exe'); +} +``` + +### Disabling Error Handling Middleware in Tests + +When debugging tests that are failing because your application is encountering +errors it can be helpful to temporarily disable the error handling middleware to +allow the underlying error to bubble up. You can use +`disableErrorHandlerMiddleware()` to do this: + +``` php +public function testGetMissing(): void +{ + $this->disableErrorHandlerMiddleware(); + $this->get('/markers/not-there'); + $this->assertResponseCode(404); +} +``` + +In the above example, the test would fail and the underlying exception message +and stack trace would be displayed instead of the rendered error page being +checked. + +### Assertion methods + +The `IntegrationTestTrait` trait provides a number of assertion methods that +make testing responses much simpler. Some examples are: + +``` php +// Check for a 2xx response code +$this->assertResponseOk(); + +// Check for a 2xx/3xx response code +$this->assertResponseSuccess(); + +// Check for a 4xx response code +$this->assertResponseError(); + +// Check for a 5xx response code +$this->assertResponseFailure(); + +// Check for a specific response code, for example, 200 +$this->assertResponseCode(200); + +// Check the Location header +$this->assertRedirect(['controller' => 'Articles', 'action' => 'index']); + +// Check the Location header matches the same previous URL +$this->assertRedirectBack(); + +// Check the Location header matches the referer URL +$this->assertRedirectBackToReferer(); + +// Check that no Location header has been set +$this->assertNoRedirect(); + +// Check a part of the Location header +$this->assertRedirectContains('/articles/edit/'); + +// Assert location header does not contain +$this->assertRedirectNotContains('/articles/edit/'); + +// Assert not empty response content +$this->assertResponseNotEmpty(); + +// Assert empty response content +$this->assertResponseEmpty(); + +// Assert response content +$this->assertResponseEquals('Yeah!'); + +// Assert response content doesn't equal +$this->assertResponseNotEquals('No!'); + +// Assert partial response content +$this->assertResponseContains('You won!'); +$this->assertResponseNotContains('You lost!'); + +// Assert file sent back +$this->assertFileResponse('/absolute/path/to/file.ext'); + +// Assert layout +$this->assertLayout('default'); + +// Assert which template was rendered (if any) +$this->assertTemplate('index'); + +// Assert data in the session +$this->assertSession(1, 'Auth.User.id'); + +// Assert response header. +$this->assertHeader('Content-Type', 'application/json'); +$this->assertHeaderContains('Content-Type', 'html'); + +// Assert content-type header doesn't contain xml +$this->assertHeaderNotContains('Content-Type', 'xml'); + +// Assert view variables +$user = $this->viewVariable('user'); +$this->assertEquals('jose', $user->username); + +// Assert cookie values in the response +$this->assertCookie('1', 'thingid'); + +// Assert a cookie is or is not present +$this->assertCookieIsSet('remember_me'); +$this->assertCookieNotSet('remember_me'); + +// Check the content type +$this->assertContentType('application/json'); +``` + +In addition to the above assertion methods, you can also use all of the +assertions in [TestSuite](https://api.cakephp.org/5.x/class-Cake.TestSuite.TestCase.html) and those +found in [PHPUnit](https://phpunit.de/manual/current/en/appendixes.assertions.html). + +### Comparing test results to a file + +For some types of test, it may be easier to compare the result of a test to the +contents of a file - for example, when testing the rendered output of a view. +The `StringCompareTrait` adds a simple assert method for this purpose. + +Usage involves using the trait, setting the comparison base path and calling +`assertSameAsFile`: + +``` php +use Cake\TestSuite\StringCompareTrait; +use Cake\TestSuite\TestCase; + +class SomeTest extends TestCase +{ + use StringCompareTrait; + + public function setUp(): void + { + $this->_compareBasePath = APP . 'tests' . DS . 'comparisons' . DS; + parent::setUp(); + } + + public function testExample(): void + { + $result = ...; + $this->assertSameAsFile('example.php', $result); + } +} +``` + +The above example will compare `$result` to the contents of the file +`APP/tests/comparisons/example.php`. + +A mechanism is provided to write/update test files, by setting the environment +variable `UPDATE_TEST_COMPARISON_FILES`, which will create and/or update test +comparison files as they are referenced: + +``` bash +phpunit +... +FAILURES! +Tests: 6, Assertions: 7, Failures: 1 + +UPDATE_TEST_COMPARISON_FILES=1 phpunit +... +OK (6 tests, 7 assertions) + +git status +... +# Changes not staged for commit: +# (use "git add ..." to update what will be committed) +# (use "git checkout -- ..." to discard changes in working directory) +# +# modified: tests/comparisons/example.php +``` + +## Console Integration Testing + +See [Console Integration Testing](../console-commands/commands#console-integration-testing) for how to test console commands. + +## Mocking Injected Dependencies + +See [Mocking Services In Tests](../development/dependency-injection#mocking-services-in-tests) for how to replace services injected with +the dependency injection container in your integration tests. + +## Mocking HTTP Client Responses + +See [Httpclient Testing](../core-libraries/httpclient#httpclient-testing) to know how to create mock responses to external APIs. + +## Testing Views + +Generally most applications will not directly test their HTML code. Doing so is +often results in fragile, difficult to maintain test suites that are prone to +breaking. When writing functional tests using `IntegrationTestTrait` +you can inspect the rendered view content by setting the `return` option to +'view'. While it is possible to test view content using `IntegrationTestTrait`, +a more robust and maintainable integration/view testing can be accomplished +using tools like [Selenium webdriver](https://www.selenium.dev/). + +## Testing Components + +Let's pretend we have a component called PagematronComponent in our application. +This component helps us set the pagination limit value across all the +controllers that use it. Here is our example component located in +**src/Controller/Component/PagematronComponent.php**: + +``` php +class PagematronComponent extends Component +{ + public $controller = null; + + public function setController($controller) + { + $this->controller = $controller; + // Make sure the controller is using pagination + if (!isset($this->controller->paginate)) { + $this->controller->paginate = []; + } + } + + public function startup(EventInterface $event): void + { + $this->setController($event->getSubject()); + } + + public function adjust(string $length = 'short'): void + { + switch ($length) { + case 'long': + $this->controller->paginate['limit'] = 100; + break; + case 'medium': + $this->controller->paginate['limit'] = 50; + break; + default: + $this->controller->paginate['limit'] = 20; + break; + } + } +} +``` + +Now we can write tests to ensure our paginate `limit` parameter is being set +correctly by the `adjust()` method in our component. We create the file +**tests/TestCase/Controller/Component/PagematronComponentTest.php**: + +``` php +namespace App\Test\TestCase\Controller\Component; + +use App\Controller\Component\PagematronComponent; +use Cake\Controller\Controller; +use Cake\Controller\ComponentRegistry; +use Cake\Event\Event; +use Cake\Http\ServerRequest; +use Cake\Http\Response; +use Cake\TestSuite\TestCase; + +class PagematronComponentTest extends TestCase +{ + protected $component; + protected $controller; + + public function setUp(): void + { + parent::setUp(); + // Setup our component and provide it a basic controller. + // If your component relies on Application features, use AppController. + $request = new ServerRequest(); + $response = new Response(); + $this->controller = new Controller($request); + $registry = new ComponentRegistry($this->controller); + + $this->component = new PagematronComponent($registry); + $event = new Event('Controller.startup', $this->controller); + $this->component->startup($event); + } + + public function testAdjust(): void + { + // Test our adjust method with different parameter settings + $this->component->adjust(); + $this->assertEquals(20, $this->controller->paginate['limit']); + + $this->component->adjust('medium'); + $this->assertEquals(50, $this->controller->paginate['limit']); + + $this->component->adjust('long'); + $this->assertEquals(100, $this->controller->paginate['limit']); + } + + public function tearDown(): void + { + parent::tearDown(); + // Clean up after we're done + unset($this->component, $this->controller); + } +} +``` + +## Testing Helpers + +Since a decent amount of logic resides in Helper classes, it's +important to make sure those classes are covered by test cases. + +First we create an example helper to test. The `CurrencyRendererHelper` will +help us display currencies in our views and for simplicity only has one method +`usd()`: + +``` php +// src/View/Helper/CurrencyRendererHelper.php +namespace App\View\Helper; + +use Cake\View\Helper; + +class CurrencyRendererHelper extends Helper +{ + public function usd($amount): string + { + return 'USD ' . number_format($amount, 2, '.', ','); + } +} +``` + +Here we set the decimal places to 2, decimal separator to dot, thousands +separator to comma, and prefix the formatted number with 'USD' string. + +Now we create our tests: + +``` php +// tests/TestCase/View/Helper/CurrencyRendererHelperTest.php + +namespace App\Test\TestCase\View\Helper; + +use App\View\Helper\CurrencyRendererHelper; +use Cake\TestSuite\TestCase; +use Cake\View\View; + +class CurrencyRendererHelperTest extends TestCase +{ + public $helper = null; + + // Here we instantiate our helper + public function setUp(): void + { + parent::setUp(); + $View = new View(); + $this->helper = new CurrencyRendererHelper($View); + } + + // Testing the usd() function + public function testUsd(): void + { + $this->assertEquals('USD 5.30', $this->helper->usd(5.30)); + + // We should always have 2 decimal digits + $this->assertEquals('USD 1.00', $this->helper->usd(1)); + $this->assertEquals('USD 2.05', $this->helper->usd(2.05)); + + // Testing the thousands separator + $this->assertEquals( + 'USD 12,000.70', + $this->helper->usd(12000.70) + ); + } +} +``` + +Here, we call `usd()` with different parameters and tell the test suite to +check if the returned values are equal to what is expected. + +Save this and execute the test. You should see a green bar and messaging +indicating 1 pass and 4 assertions. + +When you are testing a Helper which uses other helpers, be sure to mock the +View clases `loadHelpers` method. + + + +## Testing Events + +The [Events System](../core-libraries/events) is a great way to decouple your application +code, but sometimes when testing, you tend to test the results of events in the +test cases that execute those events. This is an additional form of coupling +that can be removed by using `assertEventFired` and `assertEventFiredWith` +instead. + +Expanding on the Orders example, say we have the following tables: + +``` php +class OrdersTable extends Table +{ + public function place($order): bool + { + if ($this->save($order)) { + // moved cart removal to CartsTable + $event = new Event('Model.Order.afterPlace', $this, [ + 'order' => $order + ]); + $this->getEventManager()->dispatch($event); + + return true; + } + + return false; + } +} + +class CartsTable extends Table +{ + public function initialize() + { + // Models don't share the same event manager instance, + // so we need to use the global instance to listen to + // events from other models + \Cake\Event\EventManager::instance()->on( + 'Model.Order.afterPlace', + callable: [$this, 'removeFromCart'] + ); + } + + public function removeFromCart(EventInterface $event): void + { + $order = $event->getData('order'); + $this->delete($order->cart_id); + } +} +``` + +> [!NOTE] +> To assert that events are fired, you must first enable +> [Tracking Events](../core-libraries/events#tracking-events) on the event manager you wish to assert against. + +To test the `OrdersTable` above, we enable tracking in `setUp()` then assert +that the event was fired, and assert that the `$order` entity was passed in +the event data: + +``` php +namespace App\Test\TestCase\Model\Table; + +use App\Model\Table\OrdersTable; +use Cake\Event\EventList; +use Cake\TestSuite\TestCase; + +class OrdersTableTest extends TestCase +{ + protected $fixtures = ['app.Orders']; + + public function setUp(): void + { + parent::setUp(); + $this->Orders = $this->getTableLocator()->get('Orders'); + // enable event tracking + $this->Orders->getEventManager()->setEventList(new EventList()); + } + + public function testPlace(): void + { + $order = new Order([ + 'user_id' => 1, + 'item' => 'Cake', + 'quantity' => 42, + ]); + + $this->assertTrue($this->Orders->place($order)); + + $this->assertEventFired('Model.Order.afterPlace', $this->Orders->getEventManager()); + $this->assertEventFiredWith('Model.Order.afterPlace', 'order', $order, $this->Orders->getEventManager()); + } +} +``` + +By default, the global `EventManager` is used for assertions, so testing +global events does not require passing the event manager: + +``` php +$this->assertEventFired('My.Global.Event'); +$this->assertEventFiredWith('My.Global.Event', 'user', 1); +``` + +## Testing Email + +See [Email Testing](../core-libraries/email#email-testing) for information on testing email. + +## Testing Logging + +See [Log Testing](../core-libraries/logging#log-testing) for information on testing log messages. + +## Creating Test Suites + +If you want several of your tests to run at the same time, you can create a test +suite. A test suite is composed of several test cases. You can either create +test suites in your application's **phpunit.xml** file. A simple example +would be: + +``` xml + + + src/Model + src/Service/UserServiceTest.php + src/Model/Cloud/ImagesTest.php + + +``` + +## Creating Tests for Plugins + +Tests for plugins are created in their own directory inside the plugins +folder. : + + /src + /plugins + /Blog + /tests + /TestCase + /Fixture + +They work just like normal tests but you have to remember to use the naming +conventions for plugins when importing classes. This is an example of a testcase +for the `BlogPost` model from the plugins chapter of this manual. A difference +from other tests is in the first line where 'Blog.BlogPost' is imported. You +also need to prefix your plugin fixtures with `plugin.Blog.BlogPosts`: + +``` php +namespace Blog\Test\TestCase\Model\Table; + +use Blog\Model\Table\BlogPostsTable; +use Cake\TestSuite\TestCase; + +class BlogPostsTableTest extends TestCase +{ + // Plugin fixtures located in /plugins/Blog/tests/Fixture/ + protected $fixtures = ['plugin.Blog.BlogPosts']; + + public function testSomething(): void + { + // Test something. + } +} +``` + +If you want to use plugin fixtures in the app tests you can +reference them using `plugin.pluginName.fixtureName` syntax in the +`$fixtures` array. Additionally if you use vendor plugin name or fixture +directories you can use the following: `plugin.vendorName/pluginName.folderName/fixtureName`. + +Before you can use fixtures you should ensure you have the [fixture +listener](#fixture-phpunit-configuration) configured in your `phpunit.xml` +file. You should also ensure that your fixtures are loadable. Ensure the +following is present in your **composer.json** file: + +``` json +"autoload-dev": { + "psr-4": { + "MyPlugin\\Test\\": "plugins/MyPlugin/tests/" + } +} +``` + +> [!NOTE] +> Remember to run `composer.phar dumpautoload` when adding new autoload +> mappings. + +## Generating Tests with Bake + +If you use [bake](../bake/usage) to +generate scaffolding, it will also generate test stubs. If you need to +re-generate test case skeletons, or if you want to generate test skeletons for +code you wrote, you can use `bake`: + +``` bash +bin/cake bake test +``` + +`` should be one of: + +1. Entity +2. Table +3. Controller +4. Component +5. Behavior +6. Helper +7. Shell +8. Task +9. ShellHelper +10. Cell +11. Form +12. Mailer +13. Command + +While `` should be the name of the object you want to bake a test +skeleton for. diff --git a/docs/en/elasticsearch.md b/docs/en/elasticsearch.md new file mode 100644 index 0000000000..51a601bca0 --- /dev/null +++ b/docs/en/elasticsearch.md @@ -0,0 +1,3 @@ +# ElasticSearch + +This page has [moved](https://book.cakephp.org/elasticsearch/3/en/). diff --git a/docs/en/epub-contents.md b/docs/en/epub-contents.md new file mode 100644 index 0000000000..a3c19e4265 --- /dev/null +++ b/docs/en/epub-contents.md @@ -0,0 +1,54 @@ +orphan + +# Contents + +- [CakePHP at a Glance](intro) +- [Quick Start Guide](quickstart) +- [Migration Guides](appendices/migration-guides) +- [Tutorials & Examples](tutorials-and-examples) +- [Contributing](contributing) +- [Installation](installation) +- [Configuration](development/configuration) +- [Routing](development/routing) +- [Request & Response Objects](controllers/request-response) +- [Controllers](controllers) +- [Views](views) +- [Database Access & ORM](orm) +- [Caching](core-libraries/caching) +- [Bake Console](bake) +- [Console Commands](console-commands) +- [Debugging](development/debugging) +- [Deployment](deployment) +- [Mailer](core-libraries/email) +- [Error & Exception Handling](development/errors) +- [Events System](core-libraries/events) +- [Internationalization & Localization](core-libraries/internationalization-and-localization) +- [Logging](core-libraries/logging) +- [Modelless Forms](core-libraries/form) +- [Pagination](controllers/pagination) +- [Plugins](plugins) +- [REST](development/rest) +- [Security](security) +- [Sessions](development/sessions) +- [Testing](development/testing) +- [Validation](core-libraries/validation) +- [App Class](core-libraries/app) +- [Collections](core-libraries/collections) +- [Hash](core-libraries/hash) +- [Http Client](core-libraries/httpclient) +- [Inflector](core-libraries/inflector) +- [Number](core-libraries/number) +- [Registry Objects](core-libraries/registry-objects) +- [Text](core-libraries/text) +- [Date & Time](core-libraries/time) +- [Xml](core-libraries/xml) +- [Constants & Functions](core-libraries/global-constants-and-functions) +- [Chronos](chronos) +- [Debug Kit](debug-kit) +- [Migrations](migrations) +- [ElasticSearch](elasticsearch) +- [Appendices](appendices) + +
+ +
diff --git a/docs/en/index.md b/docs/en/index.md new file mode 100644 index 0000000000..94a533f505 --- /dev/null +++ b/docs/en/index.md @@ -0,0 +1,648 @@ +--- +title: CakePHP 5 Documentation - Build Better Web Applications Faster +description: Official CakePHP 5 documentation. Learn how to build modern PHP web applications with convention over configuration, powerful ORM, built-in security, and rapid development tools. +outline: 2 +--- +# Build Better Web Applications, Faster + +**CakePHP 5** is a modern PHP framework running on PHP |phpversion| (min. PHP |minphpversion|) that helps you write clean, maintainable code without the complexity. Whether you're building a simple blog or a complex enterprise application, CakePHP gives you the tools to get it done right. + +::: tip Perfect for +✅ Developers who value **convention over configuration**
+✅ Teams building **secure, scalable applications**
+✅ Projects that need to **ship quickly** without sacrificing quality
+✅ Applications requiring **modern PHP standards** (PSR-7, PSR-15, PSR-17) +::: + +## Why CakePHP? + +
+ +🚀 **Rapid Development**
+Scaffold applications in minutes with powerful code generation tools. + +🔒 **Security First**
+Built-in protection against SQL injection, XSS, CSRF, and more. + +📦 **Batteries Included**
+ORM, validation, caching, authentication — everything you need out of the box. + +🎯 **Convention over Configuration**
+Sensible defaults mean less setup, more coding. + +
+ + +## Quick Start + +Get a CakePHP application running in under 5 minutes: + +::: code-group + +```bash [Composer] +# Create new project +composer create-project --prefer-dist cakephp/app:~5.0 my_app + +# Start development server +cd my_app +bin/cake server + +# Open http://localhost:8765 +``` + +```bash [DDEV] +# Setup with DDEV +mkdir my-cakephp-app && cd my-cakephp-app +ddev config --project-type=cakephp --docroot=webroot +ddev composer create --prefer-dist cakephp/app:~5.0 +ddev launch +``` + +```bash [Docker] +# Using official PHP image +docker run -it --rm -v $(pwd):/app composer create-project \ + --prefer-dist cakephp/app:~5.0 my_app + +cd my_app +docker run -it --rm -p 8765:8765 -v $(pwd):/app \ + -w /app php:8.2-cli php bin/cake server -H 0.0.0.0 +``` + +::: + +> [!TIP] +> You should see a welcome page with green checkmarks at **http://localhost:8765** + +::: details System Requirements +Make sure your system meets these requirements before getting started: + +| Component | Version | +|-----------|------| +| PHP | 8.2 - 8.5 | +| Database | MySQL 5.7+, PostgreSQL 9.6+, SQLite 3, SQL Server 2012+ | +| Extensions | `mbstring`, `intl`, `pdo`, `simplexml` | +| Composer | Latest | +::: + +## Your First Application + +Let's build a simple blog in 10 minutes! Follow along with this hands-on tutorial. + +> [!NOTE] +> This tutorial assumes you already have a database created. Use your database management tool to create a database named `blog` before proceeding. + +### Step 1: Configure Database Connection + +Update your database credentials in **config/app_local.php**: + +```php{7-11} [config/app_local.php] + [ + 'default' => [ + 'host' => 'localhost', + 'username' => 'my_user', + 'password' => 'secret', + 'database' => 'blog', + 'encoding' => 'utf8mb4', + ], + ], +]; +``` + +::: tip Configuration Files +- **config/app.php** - Default configuration (committed to git) +- **config/app_local.php** - Local overrides (gitignored) +::: + +### Step 2: Create Database Tables + +Choose your preferred approach for creating the database schema: + +> [!NOTE] +> **Migrations** are recommended for team projects and production - they're version-controlled and database-agnostic. +> **Raw SQL** is fine for quick prototyping or if you prefer direct database control. + +#### Option A: Using Migrations + +::: code-group + +```bash [Commands] +# Step 1: Generate a migration file +bin/cake bake migration CreateArticles +# This creates: config/Migrations/YYYYMMDDHHMMSS_CreateArticles.php + +# Step 2: Edit the generated file to define your table structure +# See the "Migration File" tab for the complete example +# Open: config/Migrations/YYYYMMDDHHMMSS_CreateArticles.php + +# Step 3: Run the migration to create the table +bin/cake migrations migrate + +# Step 4: (Optional) Generate and run seed data +bin/cake bake seed Articles +bin/cake migrations seed +``` + +```php [Migration File] +table('articles'); + $table->addColumn('title', 'string', ['limit' => 255]) + ->addColumn('slug', 'string', ['limit' => 191]) + ->addColumn('body', 'text', ['null' => true]) + ->addColumn('published', 'boolean', ['default' => false]) + ->addColumn('created', 'datetime') + ->addColumn('modified', 'datetime') + ->addIndex(['slug'], ['unique' => true]) + ->create(); + } +} +``` + +```php [Seed File] + 'First Post', + 'slug' => 'first-post', + 'body' => 'This is my first blog post!', + 'published' => true, + 'created' => date('Y-m-d H:i:s'), + 'modified' => date('Y-m-d H:i:s'), + ], + [ + 'title' => 'Second Post', + 'slug' => 'second-post', + 'body' => 'Another great article.', + 'published' => true, + 'created' => date('Y-m-d H:i:s'), + 'modified' => date('Y-m-d H:i:s'), + ], + ]; + + $table = $this->table('articles'); + $table->insert($data)->save(); + } +} +``` + +::: + +#### Option B: Using Raw SQL + +::: code-group + +```sql [MySQL] +USE blog; + +CREATE TABLE articles ( + id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + slug VARCHAR(191) UNIQUE, + body TEXT, + published BOOLEAN DEFAULT FALSE, + created DATETIME, + modified DATETIME +) ENGINE=InnoDB; + +INSERT INTO articles (title, slug, body, published, created, modified) +VALUES ('First Post', 'first-post', 'This is my first blog post!', TRUE, NOW(), NOW()); +``` + +```sql [PostgreSQL] +\c blog + +CREATE TABLE articles ( + id SERIAL PRIMARY KEY, + title VARCHAR(255) NOT NULL, + slug VARCHAR(191) UNIQUE, + body TEXT, + published BOOLEAN DEFAULT FALSE, + created TIMESTAMP, + modified TIMESTAMP +); + +INSERT INTO articles (title, slug, body, published, created, modified) +VALUES ('First Post', 'first-post', 'This is my first blog post!', TRUE, NOW(), NOW()); +``` + +```sql [SQLite] +CREATE TABLE articles ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title VARCHAR(255) NOT NULL, + slug VARCHAR(191) UNIQUE, + body TEXT, + published BOOLEAN DEFAULT 0, + created DATETIME, + modified DATETIME +); + +INSERT INTO articles (title, slug, body, published, created, modified) +VALUES ('First Post', 'first-post', 'This is my first blog post!', 1, datetime('now'), datetime('now')); +``` + +::: + +### Step 3: Generate Your First Model + +Use CakePHP's code generation tool: + +```bash +# Generate model classes +bin/cake bake model Articles + +# Output: +# ✓ Created: src/Model/Table/ArticlesTable.php +# ✓ Created: src/Model/Entity/Article.php +# ✓ Created: tests/TestCase/Model/Table/ArticlesTableTest.php +``` + +This creates: + +::: code-group + +```php [ArticlesTable.php] +setTable('articles'); + $this->setDisplayField('title'); + $this->setPrimaryKey('id'); + + $this->addBehavior('Timestamp'); + } +} +``` + +```php [Article.php] + true, + 'slug' => true, + 'body' => true, + 'published' => true, + 'created' => true, + 'modified' => true, + ]; +} +``` + +::: + +### Step 4: Create Your Controller + +Generate a controller with views: + +```bash +bin/cake bake controller Articles +``` + +```php [src/Controller/ArticlesController.php] +Articles->find('all') + ->where(['published' => true]) + ->orderBy(['created' => 'DESC']); + + $this->set(compact('articles')); + } + + public function view(?string $slug = null): void + { + $article = $this->Articles + ->findBySlug($slug) + ->firstOrFail(); + + $this->set(compact('article')); + } + + public function add(): void + { + $article = $this->Articles->newEmptyEntity(); + + if ($this->request->is('post')) { + $article = $this->Articles->patchEntity( + $article, + $this->request->getData() + ); + + if ($this->Articles->save($article)) { + $this->Flash->success('Article saved!'); + return $this->redirect(['action' => 'index']); + } + + $this->Flash->error('Unable to save article.'); + } + + $this->set(compact('article')); + } +} +``` + +### Step 5: Create Your Views + +Create a simple list view in **templates/Articles/index.php**: + +```php [templates/Articles/index.php] +

Blog Articles

+ + +
+

+ Html->link( + h($article->title), + ['action' => 'view', $article->slug] + ) ?> +

+

+ Published: created->format('F d, Y') ?> +

+

body) ?>

+
+ + +Html->link('New Article', ['action' => 'add'], ['class' => 'button']) ?> +``` + +> [!TIP] +> Visit **http://localhost:8765/articles** to see your blog in action! + +## Next Steps + +Now that you've built your first CakePHP application, here are some great places to continue your journey: + +**Learn the Fundamentals:** +- [CakePHP Conventions](intro/conventions) - Understanding the naming conventions that power CakePHP +- [MVC Pattern](intro) - How CakePHP structures applications +- [Configuration](development/configuration) - Customizing your application + +**Build Real Applications:** +- [Tutorials & Examples](tutorials-and-examples) - Step-by-step guides +- [CMS Tutorial](tutorials-and-examples/cms/installation) - Build a complete content management system + +**Master Core Features:** +- [Database & ORM](orm) - Advanced queries, associations, and data modeling +- [Controllers](controllers) - Request handling, components, and middleware +- [Views](views) - Templates, helpers, and rendering +- [Security](security) - Best practices for secure applications + +## Get Help + +Join our community and get the support you need: + + + +**Additional Resources:** +- [API Documentation](https://api.cakephp.org/5.0/) - Complete API reference +- [Stack Overflow](https://stackoverflow.com/questions/tagged/cakephp) - Find answers to common questions + +## How CakePHP Works + +Understanding the request flow helps you build better applications. Here's what happens when a user visits your CakePHP application: + +
+CakePHP request flow diagram +
+ +**A typical request follows these steps:** + +1. 🌐 **Request arrives** → Webserver routes to `webroot/index.php` +2. ⚙️ **Application boots** → Your app is loaded and middleware initializes +3. 🛣️ **Routing** → Request is matched to a controller and action +4. 🎮 **Controller** → Action is called, interacts with Models +5. 📊 **Model Layer** → Fetches and processes data from the database +6. 🎨 **View Layer** → Renders the response (HTML, JSON, XML, etc.) +7. 📤 **Response sent** → Back through middleware to the client + +::: tip Understanding MVC +The **Model** handles your data and business logic, the **Controller** coordinates the request, and the **View** presents the data. This separation keeps your code organized and testable. + +[Learn more about MVC in CakePHP →](intro) +::: + +## Everything You Need, Out of the Box + +CakePHP comes with powerful features that save you time and help you build better applications: + +::: details 🚀 Code Generation (Bake) +Generate complete CRUD applications in seconds: +```bash +bin/cake bake all Articles +# Creates: Model, Controller, Views, Tests +``` +Bake can scaffold entire features, saving hours of repetitive coding. Perfect for prototyping or generating boilerplate. + +[Learn more about Bake →](https://book.cakephp.org/bake/2/en/) +::: + +::: details 💾 Caching Framework +Integrated caching with multiple backends: +```php +// Cache expensive operations +$results = Cache::remember('expensive_query', function () { + return $this->Articles->find('complex')->toArray(); +}); +``` +**Supported backends:** Redis, Memcached, APCu, File, Database + +[Learn more about Caching →](core-libraries/caching) +::: + +::: details 🧪 Built-in Testing +Write tests with confidence using the integrated testing framework: +```php +public function testAddArticle(): void +{ + $this->post('/articles/add', ['title' => 'Test']); + $this->assertResponseOk(); + $this->assertFlashMessage('Article saved!'); +} +``` +Supports unit tests, integration tests, and browser tests out of the box. + +[Learn more about Testing →](development/testing) +::: + +::: details 🔐 Authentication & Authorization +Drop-in user management with flexible policies: +```bash +composer require cakephp/authentication cakephp/authorization +``` +Handle login, permissions, and access control with minimal configuration. + +[Authentication Guide →](https://book.cakephp.org/authentication/) • [Authorization Guide →](https://book.cakephp.org/authorization/) +::: + +::: details 🌐 REST API Support +Build APIs with automatic content type negotiation: +```php +// Automatically serves JSON/XML based on Accept header +public function index() +{ + $articles = $this->Articles->find('all'); + $this->set('articles', $articles); + $this->viewBuilder()->setOption('serialize', ['articles']); +} +``` +Supports JSON, XML, and custom formats with minimal code. + +[Learn more about REST →](development/rest) +::: + +::: details 📦 Database Migrations +Version control your database schema: +```bash +bin/cake bake migration CreateArticles +bin/cake migrations migrate +``` +Keep your database changes in sync across development, staging, and production. + +[Learn more about Migrations →](https://book.cakephp.org/migrations/) +::: + +## What Makes CakePHP Special? + +::: code-group + +```php [Convention Over Configuration] +Articles->find('all'); + $this->set(compact('articles')); + } +} +``` + +```php [Powerful ORM] +Articles->find() + ->contain(['Users', 'Comments']) + ->matching('Tags', function ($q) { + return $q->where(['Tags.name IN' => ['PHP', 'CakePHP']]); + }) + ->where(['Articles.published' => true]) + ->orderBy(['Articles.view_count' => 'DESC']) + ->limit(10); +``` + +```php [Built-in Security] +Articles->newEntity($data); +// Only $_accessible fields can be set +``` + +```bash [Code Generation] +# Generate complete CRUD in seconds +bin/cake bake all Articles + +# Creates: +# ✓ Model (Table + Entity) +# ✓ Controller (with all actions) +# ✓ Templates (index, view, add, edit) +# ✓ Tests (full coverage) +``` + +::: + + +
+ +
+ Ready to Build Something Amazing +
+ +

Ready to Build Something Amazing?

+ +**Start your CakePHP journey today and join thousands of developers building modern web applications.** + +

+ Get Started → + + Join Community + + View Examples +

+ +
diff --git a/docs/en/installation.md b/docs/en/installation.md new file mode 100644 index 0000000000..52cf3698f8 --- /dev/null +++ b/docs/en/installation.md @@ -0,0 +1,502 @@ +--- +title: Installation Guide +description: Learn how to install CakePHP using Composer, Docker, or DDEV. Complete setup guide for PHP 8.2+ with all system requirements and web server configurations. +--- + +# Installation + +CakePHP is designed to be easy to install and configure. This guide will walk you through getting CakePHP up and running in just a few minutes. + +## System Requirements + +::: tip Quick Check +Verify your PHP version meets the requirements: +```bash +php -v +``` +::: + +**Minimum Requirements:** + +| Component | Version | +|-----------|---------| +| PHP | |minphpversion| (|phpversion| supported) | +| Extensions | `mbstring`, `intl`, `pdo`, `simplexml` | +| Composer | Latest stable | + +**Supported Databases:** + +- MySQL 5.7+ +- MariaDB 10.1+ +- PostgreSQL 9.6+ +- Microsoft SQL Server 2012+ +- SQLite 3.8.9+ + +::: warning Web Server Requirements +Your web server's PHP version must match your CLI PHP version (|minphpversion|+). All database drivers require the appropriate PDO extension. +::: + +## Installation Methods + +Choose the method that best fits your workflow: + +### Method 1: Composer (Recommended) + +The standard way to install CakePHP: + +::: code-group + +```bash [Install Composer] +# Linux/macOS +curl -sS https://getcomposer.org/installer | php +sudo mv composer.phar /usr/local/bin/composer + +# Verify installation +composer --version +``` + +```powershell [Windows] +# Download and run the Windows installer +# https://getcomposer.org/Composer-Setup.exe + +# Verify installation +composer --version +``` + +```bash [Create Project] +# Create a new CakePHP 5 application +composer create-project --prefer-dist cakephp/app:~|cakeversion| my_app_name + +# Navigate to your app +cd my_app_name + +# Start development server +bin/cake server + +# Or if you have frankenphp available +bin/cake server --frankenphp +``` + +::: + +::: tip Version Constraints +Your `composer.json` version constraint controls updates: +- `"cakephp/cakephp": "|cakeversion|.*"` - Patch releases only (recommended) +- `"cakephp/cakephp": "^|cakeversion|"` - Minor + patch releases (may require config changes) +::: + +### Method 2: DDEV (Fast Setup) + +Perfect for local development environments: + +::: code-group + +```bash [New Project] +# Create and configure project +mkdir my-cakephp-app && cd my-cakephp-app +ddev config --project-type=cakephp --docroot=webroot +ddev composer create --prefer-dist cakephp/app:~|cakeversion| + +# Launch in browser +ddev launch +``` + +```bash [Existing Project] +# Clone your repository +git clone +cd + +# Configure DDEV +ddev config --project-type=cakephp --docroot=webroot +ddev composer install + +# Launch in browser +ddev launch +``` + +::: + +::: info Learn More +Check [DDEV Documentation](https://ddev.readthedocs.io/) for installation and advanced configuration. +::: + +### Method 3: Docker + +For containerized development: + +```bash +# Create project using Composer in Docker +docker run --rm -v $(pwd):/app composer create-project \ + --prefer-dist cakephp/app:~|cakeversion| my_app + +# Start PHP development server (install required extensions first) +cd my_app +docker run -it --rm -p 8765:8765 -v $(pwd):/app \ + -w /app php:8.2-cli bash -c "apt-get update && apt-get install -y libicu-dev && docker-php-ext-install intl mbstring && php bin/cake server -H 0.0.0.0" +``` + +::: warning Development Only +The built-in server is for development only. Never use it in production environments. +::: + +## File Permissions + +CakePHP uses the **tmp** and **logs** directories for various operations like caching, sessions, and logging. + +::: warning Permission Setup Required +Ensure **logs** and **tmp** (including all subdirectories) are writable by your web server user. +::: + +### Quick Setup (Unix/Linux/macOS) + +If your web server and CLI users differ, set permissions for the directories: + +::: code-group + +```bash [Linux with ACL] +# Auto-detect web server user and set permissions using ACL +HTTPDUSER=`ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\ -f1` +setfacl -R -m u:${HTTPDUSER}:rwx tmp logs +setfacl -R -d -m u:${HTTPDUSER}:rwx tmp logs +``` + +```bash [macOS / Without ACL] +# Auto-detect web server user and set permissions using chmod +HTTPDUSER=`ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\ -f1` +sudo chown -R $(whoami):${HTTPDUSER} tmp logs +sudo chmod -R 775 tmp logs +``` + +```bash [Simple Alternative] +# If auto-detection doesn't work, use broader permissions +chmod -R 777 tmp logs +``` + +::: + +::: warning macOS Note +macOS does not include `setfacl` by default. Use the chmod method or install ACL tools via Homebrew: `brew install acl` +::: + +### Make Console Executable + +::: code-group + +```bash [Unix/Linux/macOS] +chmod +x bin/cake +``` + +```bash [Windows] +# .bat file is already executable +# For WSL or shared directories, ensure execute permissions are shared +``` + +```bash [Alternative] +# If you cannot change permissions +php bin/cake.php +``` + +::: + +## Development Server + +The fastest way to get started. CakePHP includes a development server built on PHP's built-in web server: + +::: code-group + +```bash [Default] +# Starts at http://localhost:8765 +bin/cake server +``` + +```bash [Custom Host/Port] +# Useful for network access or port conflicts +bin/cake server -H 192.168.1.100 -p 5000 +``` + +```bash [Network Access] +# Allow access from other devices on your network +bin/cake server -H 0.0.0.0 +``` + +::: + +::: tip Success! +Visit **http://localhost:8765** and you should see the CakePHP welcome page with green checkmarks. +::: + +::: danger Production Warning +**Never** use the development server in production. It's designed only for local development and lacks security hardening, performance optimizations, and proper process management. +::: + +## Production Deployment + +For production environments, configure your web server to serve from the **webroot** directory. + +### Directory Structure + +After installation, your structure should look like this: + +```text +my_app/ +├── bin/ +├── config/ +├── logs/ +├── plugins/ +├── src/ +├── templates/ +├── tests/ +├── tmp/ +├── vendor/ +├── webroot/ # ← Web server document root +│ ├── css/ +│ ├── img/ +│ ├── js/ +│ ├── .htaccess +│ └── index.php +├── .gitignore +├── .htaccess +├── composer.json +└── README.md +``` + +::: tip Configuration Required +Point your web server's DocumentRoot to `/path/to/my_app/webroot/` +::: + +## Web Server Configuration + +::: info Configuration Examples +The following examples are illustrative starting points. You should fine-tune these configurations to match your application's specific requirements, security policies, and performance needs. +::: + +Choose your web server and follow the appropriate configuration: + +### Apache + +Apache works out of the box with CakePHP's included `.htaccess` files. + +::: code-group + +```apache [Virtual Host] + + ServerName myapp.local + DocumentRoot /var/www/myapp/webroot + + + Options FollowSymLinks + AllowOverride All + Require all granted + + + ErrorLog ${APACHE_LOG_DIR}/myapp_error.log + CustomLog ${APACHE_LOG_DIR}/myapp_access.log combined + +``` + +```apache [Enable mod_rewrite] +# Ensure mod_rewrite is enabled +LoadModule rewrite_module modules/mod_rewrite.so + +# Verify with: +apache2ctl -M | grep rewrite +``` + +```apache [Subdirectory Install] +# If installing in a subdirectory like /~username/myapp/ + + RewriteEngine On + RewriteBase /~username/myapp + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + +``` + +::: + +::: details Troubleshooting Apache +**If rewrites aren't working:** + +1. Verify `AllowOverride All` is set in your DocumentRoot directive +2. Check that `.htaccess` files exist in root and webroot directories +3. Ensure `mod_rewrite` is loaded +4. Restart Apache after configuration changes + +**Performance optimization:** +```apache +# Add to webroot/.htaccess to prevent CakePHP from handling static assets +RewriteCond %{REQUEST_URI} !^/(webroot/)?(img|css|js)/(.*)$ +``` +::: + +### nginx + +nginx requires manual rewrite configuration: + +```nginx +server { + listen 80; + server_name myapp.local; + + root /var/www/myapp/webroot; + index index.php; + + access_log /var/log/nginx/myapp_access.log; + error_log /var/log/nginx/myapp_error.log; + + location / { + try_files $uri $uri/ /index.php?$args; + } + + location ~ \.php$ { + try_files $uri =404; + include fastcgi_params; + fastcgi_pass unix:/var/run/php/php8.2-fpm.sock; + fastcgi_index index.php; + fastcgi_intercept_errors on; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + } +} +``` + +::: warning PHP-FPM Socket +Modern PHP-FPM uses Unix sockets instead of TCP. Update `fastcgi_pass` to match your setup: +- Unix socket: `unix:/var/run/php/php8.2-fpm.sock` +- TCP: `127.0.0.1:9000` +::: + +### Caddy / FrankenPHP + +Modern web server with automatic HTTPS. FrankenPHP extends Caddy with a built-in PHP runtime: + +::: code-group + +```text [Caddy + PHP-FPM] +myapp.local { + root * /var/www/myapp/webroot + php_fastcgi unix//var/run/php/php8.2-fpm.sock + encode gzip + file_server + + try_files {path} {path}/ /index.php?{query} +} +``` + +```dockerfile [FrankenPHP Docker] +# Dockerfile in your project root +FROM dunglas/frankenphp + +# Copy your CakePHP application +COPY . /app + +# FrankenPHP defaults to /app/public as document root. +# CakePHP uses webroot/ instead, so override it: +ENV SERVER_ROOT=/app/webroot + +# Install dependencies (composer.json lives in /app) +RUN composer install --no-dev --optimize-autoloader + +# Build and run: +# docker build -t myapp . +# docker run -p 80:80 -p 443:443 myapp +``` + +```bash [FrankenPHP Binary] +# Download FrankenPHP +curl -L https://github.com/dunglas/frankenphp/releases/latest/download/frankenphp-linux-x86_64 -o frankenphp +chmod +x frankenphp + +# Run with your CakePHP app +./frankenphp php-server --root /var/www/myapp/webroot +``` + +```text [FrankenPHP Caddyfile] +# Caddyfile in your project root +{ + frankenphp +} + +myapp.local { + root * /var/www/myapp/webroot + php_server + encode zstd gzip + file_server +} +``` + +::: + +::: tip Local Development +For local development, you can use the built-in CakePHP development server with FrankenPHP support: +```bash +bin/cake server --frankenphp +``` +This requires the `frankenphp` binary to be available in your `PATH`. +::: + +::: info Why FrankenPHP? +FrankenPHP combines PHP with Caddy, providing automatic HTTPS, HTTP/3, and modern compression without needing PHP-FPM. Particularly efficient for containerized deployments. +::: + +### IIS (Windows) + +1. Install [URL Rewrite Module 2.0](https://www.iis.net/downloads/microsoft/url-rewrite) +2. Create `web.config` in your application root: + +```xml + + + + + + + + + + + + + + + + + + + + + +``` + +## Without URL Rewriting + +If you cannot enable URL rewriting, you can use CakePHP's built-in non-rewritten `index.php` URLs. + +In **config/app.php**, uncomment: + +```php +'App' => [ + // ... + 'baseUrl' => env('SCRIPT_NAME'), +] +``` + +Remove these `.htaccess` files: +- `/.htaccess` +- `/webroot/.htaccess` + +Your URLs will include `index.php`: +- **With rewriting:** `https://myapp.com/articles/view/1` +- **Without rewriting:** `https://myapp.com/index.php/articles/view/1` + +## Next Steps + +Your CakePHP installation is complete! Here's what to do next: + +::: tip Ready to Build? +Follow the [Quick Start Guide](quickstart) to create your first CakePHP application in minutes. +::: + +**Learn More:** +- [Configuration](development/configuration) - Customize your application +- [Database Setup](orm/database-basics) - Connect to your database +- [Tutorials](tutorials-and-examples) - Step-by-step guides diff --git a/docs/en/intro.md b/docs/en/intro.md new file mode 100644 index 0000000000..e2ad374b63 --- /dev/null +++ b/docs/en/intro.md @@ -0,0 +1,164 @@ +# CakePHP at a Glance + +CakePHP is designed to make common web-development tasks simple, and easy. By +providing an all-in-one toolbox to get you started the various parts of CakePHP +work well together or separately. + +The goal of this overview is to introduce the general concepts in CakePHP, and +give you a quick overview of how those concepts are implemented in CakePHP. If +you are itching to get started on a project, you can [start with the +tutorial](tutorials-and-examples/cms/installation), or [dive into the docs](topics). + +## Conventions Over Configuration + +CakePHP provides a basic organizational structure that covers class names, +filenames, database table names, and other conventions. While the conventions +take some time to learn, by following the conventions CakePHP provides you can +avoid needless configuration and make a uniform application structure that makes +working with various projects simple. The [conventions chapter](intro/conventions) covers the various conventions that CakePHP uses. + +## The Model Layer + +The Model layer represents the part of your application that implements the +business logic. It is responsible for retrieving data and converting it into the +primary meaningful concepts in your application. This includes processing, +validating, associating or other tasks related to handling data. + +In the case of a social network, the Model layer would take care of +tasks such as saving the user data, saving friends' associations, storing +and retrieving user photos, finding suggestions for new friends, etc. +The model objects can be thought of as "Friend", "User", "Comment", or +"Photo". If we wanted to load some data from our `users` table we could do: + +``` php +use Cake\ORM\Locator\LocatorAwareTrait; + +$users = $this->fetchTable('Users'); +$resultset = $users->find()->all(); +foreach ($resultset as $row) { + echo $row->username; +} +``` + +You may notice that we didn't have to write any code before we could start +working with our data. By using conventions, CakePHP will use standard classes +for table and entity classes that have not yet been defined. + +If we wanted to make a new user and save it (with validation) we would do +something like: + +``` php +use Cake\ORM\Locator\LocatorAwareTrait; + +$users = $this->fetchTable('Users'); +$user = $users->newEntity(['email' => 'mark@example.com']); +$users->save($user); +``` + +## The View Layer + +The View layer renders a presentation of modeled data. Being separate from the +Model objects, it is responsible for using the information it has available +to produce any presentational interface your application might need. + +For example, the view could use model data to render an HTML view template containing it, +or a XML formatted result for others to consume: + +``` php +// In a view template file, we'll render an 'element' for each user. + +
  • + element('user_info', ['user' => $user]) ?> +
  • + +``` + +The View layer provides a number of extension points like [View Templates](views#view-templates), [View Elements](views#view-elements) +and [View Cells](views/cells) to let you re-use your presentation logic. + +The View layer is not only limited to HTML or text representation of the data. +It can be used to deliver common data formats like JSON, XML, and through +a pluggable architecture any other format you may need, such as CSV. + +## The Controller Layer + +The Controller layer handles requests from users. It is responsible for +rendering a response with the aid of both the Model and the View layers. + +A controller can be seen as a manager that ensures that all resources needed for +completing a task are delegated to the correct workers. It waits for petitions +from clients, checks their validity according to authentication or authorization +rules, delegates data fetching or processing to the model, selects the type of +presentational data that the clients are accepting, and finally delegates the +rendering process to the View layer. An example of a user registration +controller would be: + +``` php +public function add() +{ + $user = $this->Users->newEmptyEntity(); + if ($this->request->is('post')) { + $user = $this->Users->patchEntity($user, $this->request->getData()); + if ($this->Users->save($user, ['validate' => 'registration'])) { + $this->Flash->success(__('You are now registered.')); + } else { + $this->Flash->error(__('There were some problems.')); + } + } + $this->set('user', $user); +} +``` + +You may notice that we never explicitly rendered a view. CakePHP's conventions +will take care of selecting the right view and rendering it with the view data +we prepared with `set()`. + + + +## CakePHP Request Cycle + +Now that you are familiar with the different layers in CakePHP, lets review how +a request cycle works in CakePHP: + +
    +Flow diagram showing a typical CakePHP request +
    + +The typical CakePHP request cycle starts with a user requesting a page or +resource in your application. At a high level each request goes through the +following steps: + +1. The webserver rewrite rules direct the request to **webroot/index.php**. +2. Your Application is loaded and bound to an `HttpServer`. +3. Your application's middleware is initialized. +4. A request and response is dispatched through the PSR-7 Middleware that your + application uses. Typically this includes error trapping and routing. +5. If no response is returned from the middleware and the request contains + routing information, a controller & action are selected. +6. The controller's action is called and the controller interacts with the + required Models and Components. +7. The controller delegates response creation to the View to generate the output + resulting from the model data. +8. The view uses Helpers and Cells to generate the response body and headers. +9. The response is sent back out through the [Middleware](controllers/middleware). +10. The `HttpServer` emits the response to the webserver. + +## Just the Start + +Hopefully this quick overview has piqued your interest. Some other great +features in CakePHP are: + +- A [caching](core-libraries/caching) framework that integrates with + Memcached, Redis and other backends. +- Powerful [code generation tools](bake/usage) so you can start immediately. +- [Integrated testing framework](development/testing) so you can ensure + your code works perfectly. + +The next obvious steps are to [download CakePHP](installation), read the +[tutorial and build something awesome](tutorials-and-examples/cms/installation). + +## Additional Reading + +- [Where to Get Help](intro/where-to-get-help) +- [CakePHP Conventions](intro/conventions) +- [CakePHP Folder Structure](intro/cakephp-folder-structure) diff --git a/docs/en/intro/cakephp-folder-structure.md b/docs/en/intro/cakephp-folder-structure.md new file mode 100644 index 0000000000..d8d3b3bf23 --- /dev/null +++ b/docs/en/intro/cakephp-folder-structure.md @@ -0,0 +1,69 @@ +# CakePHP Folder Structure + +After you've downloaded the CakePHP application skeleton, there are a few top +level folders you should see: + +- `bin/` holds the Cake console executables so you can execute e.g. `bin/cake bake all`. + +- `config/` holds the [Configuration](../development/configuration) files. + Database connection details, bootstrapping, core configuration files + and more should be stored here. + +- `plugins/` is where the [Plugins](../plugins) your application uses are stored. + +- `logs/` contains your log files, can be adjusted via [Log Configuration](../core-libraries/logging.md#logging-configuration). + +- `src/` will be where your application's source files like Controllers, Models, Commands etc. will be placed. + +- `templates/` has presentational files placed here: + elements, error pages, layouts, and view template files. + +- `resources/` is primarily used for the `locales/` subfolder storing language files for static internationalization. + +- `tests/` will be where you put the test cases for your application. + +- `tmp/` is where CakePHP stores temporary data. The actual data it + stores depends on how you have CakePHP configured, but this folder + is usually used to store translation messages, model descriptions and sometimes + session information. + +- `vendor/` is where CakePHP and other application dependencies will + be installed by [Composer](https://getcomposer.org). **Editing these files is not + advised, as Composer will overwrite your changes next time you update.** + +- `webroot/` is the public document root of your application. It + contains all the files you want to be publicly reachable. + +Make sure that the `tmp/` and `logs/` folders exist and are writable, +otherwise the performance of your application will be severely +impacted. In debug mode, CakePHP will warn you if these directories are not +writable. + +## The src Folder + +CakePHP's `src/` folder is where you will do most of your application +development. Let's look a little closer at the folders inside. + +### Command +Contains your application's console commands. See +[Command Objects](../console-commands/commands) to learn more. + +> [!NOTE] +> The folder `Command/` is not present by default. +> It will be auto generated when you create your first command using bake. + +### Console +Contains the installation script executed by Composer. + +### Controller +Contains your application's [Controllers](../controllers) and their components. + +### Middleware +Stores any [Middleware](../controllers/middleware) for your application. + +### Model +Contains your application's [Tables](../orm/table-objects.md), [Entities](../orm/entities.md) and [Behaviors](../orm/behaviors.md). + +### View +Presentational classes are placed here: [Views](../views.md), [Cells](../views/cells.md), [Helpers](../views/helpers.md). + diff --git a/docs/en/intro/conventions.md b/docs/en/intro/conventions.md new file mode 100644 index 0000000000..03eaf0974f --- /dev/null +++ b/docs/en/intro/conventions.md @@ -0,0 +1,296 @@ +# CakePHP Conventions + +We are big fans of convention over configuration. While it takes a bit of time +to learn CakePHP's conventions, you save time in the long run. By following +conventions, you get free functionality, and you liberate yourself from the +maintenance nightmare of tracking config files. Conventions also make for a very +uniform development experience, allowing other developers to jump in and help. + +## Controller Conventions + +Controller class names are plural, CamelCased, and end in `Controller`. +`UsersController` and `MenuLinksController` are both examples of +conventional controller names. + +Public methods on Controllers are often exposed as 'actions' accessible through +a web browser. They are camelBacked. For example the `/users/view-me` maps to the `viewMe()` method +of the `UsersController` out of the box (if one uses default dashed inflection in routing). +Protected or private methods cannot be accessed with routing. + +For inflection of acronyms it is useful to treat them as words, so `CMS` would be `Cms`. + +### URL Considerations for Controller Names + +As you've just seen, single word controllers map to a simple lower case URL +path. For example, `UsersController` (which would be defined in the file name +**UsersController.php**) is accessed from `http://example.com/users`. + +While you can route multiple word controllers in any way you like, the +convention is that your URLs are lowercase and dashed using the `DashedRoute` +class, therefore `/menu-links/view-all` is the correct form to access +the `MenuLinksController::viewAll()` action. + +When you create links using `this->Html->link()`, you can use the following +conventions for the url array: + +``` php +$this->Html->link('link-title', [ + 'prefix' => 'MyPrefix' // CamelCased + 'plugin' => 'MyPlugin', // CamelCased + 'controller' => 'ControllerName', // CamelCased + 'action' => 'actionName' // camelBacked +] +``` + +For more information on CakePHP URLs and parameter handling, see +[Routes Configuration](../development/routing#routes-configuration). + + + +## File and Class Name Conventions + +In general, filenames match the class names, and follow the PSR-4 standard for +autoloading. The following are some examples of class names and their filenames: + +- The Controller class `LatestArticlesController` would be found in a file + named **LatestArticlesController.php** +- The Component class `MyHandyComponent` would be found in a file named + **MyHandyComponent.php** +- The Table class `OptionValuesTable` would be found in a file named + **OptionValuesTable.php**. +- The Entity class `OptionValue` would be found in a file named + **OptionValue.php**. +- The Behavior class `EspeciallyFunkableBehavior` would be found in a file + named **EspeciallyFunkableBehavior.php** +- The View class `SuperSimpleView` would be found in a file named + **SuperSimpleView.php** +- The Helper class `BestEverHelper` would be found in a file named + **BestEverHelper.php** + +Each file would be located in the appropriate folder/namespace in your app +folder. + + + +## Database Conventions + +Table names corresponding to CakePHP models are plural and underscored. For +example `users`, `menu_links`, and `user_favorite_pages` +respectively. Table name whose name contains multiple words should only +pluralize the last word, for example, `menu_links`. + +Column names with two or more words are underscored, for example, `first_name`. + +Foreign keys in hasMany, belongsTo/hasOne relationships are recognized by +default as the (singular) name of the related table followed by `_id`. So if +Users hasMany Articles, the `articles` table will refer to the `users` +table via a `user_id` foreign key. For a table like `menu_links` +whose name contains multiple words, the foreign key would be +`menu_link_id`. + +Join (or "junction") tables are used in BelongsToMany relationships between +models. These should be named for the tables they connect. The names should be +pluralized and sorted alphabetically: `articles_tags`, not `tags_articles` +or `article_tags`. *The bake command will not work if this convention is not +followed.* If the junction table holds any data other than the linking foreign +keys, you should create a concrete entity/table class for the table. + +In addition to using an auto-incrementing integer as primary keys, you can also +use UUID columns. CakePHP will create UUID values automatically using +(`Cake\Utility\Text::uuid()`) whenever you save new records using +the `Table::save()` method. + +## Model Conventions + +Table class names are plural, CamelCased and end in `Table`. `UsersTable`, +`MenuLinksTable`, and `UserFavoritePagesTable` are all examples of +table class names matching the `users`, `menu_links` and +`user_favorite_pages` tables respectively. + +Entity class names are singular CamelCased and have no suffix. `User`, +`MenuLink`, and `UserFavoritePage` are all examples of entity names +matching the `users`, `menu_links` and `user_favorite_pages` +tables respectively. + +Enum class names should use a `{Entity}{Column}` convention, and enum cases +should use CamelCased names. + +## View Conventions + +View template files are named after the controller functions they display, in an +underscored form. The `viewAll()` function of the `ArticlesController` class +will look for a view template in **templates/Articles/view_all.php**. + +The basic pattern is +**templates/Controller/underscored_function_name.php**. + +> [!NOTE] +> By default CakePHP uses English inflections. If you have database +> tables/columns that use another language, you will need to add inflection +> rules (from singular to plural and vice-versa). You can use +> `Cake\Utility\Inflector` to define your custom inflection +> rules. See the documentation about [Inflector](../core-libraries/inflector) for more +> information. + +## Plugins Conventions + +It is useful to prefix a CakePHP plugin with "cakephp-" in the package name. +This makes the name semantically related on the framework it depends on. + +Do **not** use the CakePHP namespace (cakephp) as vendor name as this is +reserved to CakePHP owned plugins. The convention is to use lowercase letters +and dashes as separator: + +``` text +// Bad +cakephp/foo-bar + +// Good +your-name/cakephp-foo-bar +``` + +See [awesome list recommendations](https://github.com/FriendsOfCake/awesome-cakephp/blob/master/CONTRIBUTING.md#tips-for-creating-cakephp-plugins) +for details. + +## Summarized + +By naming the pieces of your application using CakePHP conventions, you gain +functionality without the hassle and maintenance tethers of configuration. +Here's a final example that ties the conventions together: + +- Database table: "articles", "menu_links" +- Table class: `ArticlesTable`, found at **src/Model/Table/ArticlesTable.php** +- Entity class: `Article`, found at **src/Model/Entity/Article.php** +- Controller class: `ArticlesController`, found at + **src/Controller/ArticlesController.php** +- View template, found at **templates/Articles/index.php** + +Using these conventions, CakePHP knows that a request to +`http://example.com/articles` maps to a call on the `index()` method of the +`ArticlesController`, where the `Articles` model is automatically available. +None of these relationships have been configured by any means other than by +creating classes and files that you'd need to create anyway. + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Examplearticlesmenu_links
    Database Tablearticlesmenu_linksTable names corresponding to CakePHP models are plural and underscored.
    FileArticlesController.phpMenuLinksController.php
    TableArticlesTable.phpMenuLinksTable.phpTable class names are plural, CamelCased and end in Table
    EntityArticle.phpMenuLink.phpEntity class names are singular, CamelCased: Article and MenuLink
    ClassArticlesControllerMenuLinksController
    ControllerArticlesControllerMenuLinksControllerPlural, CamelCased, end in Controller
    TemplatesArticles/index.php Articles/add.php Articles/get_list.phpMenuLinks/index.php MenuLinks/add.php MenuLinks/get_list.phpView template files are named after the controller functions they display, in an underscored form
    BehaviorArticlesBehavior.phpMenuLinksBehavior.php
    ViewArticlesView.phpMenuLinksView.php
    HelperArticlesHelper.phpMenuLinksHelper.php
    ComponentArticlesComponent.phpMenuLinksComponent.php
    PluginBad: cakephp/articles Good: you/cakephp-articlescakephp/menu-links you/cakephp-menu-linksUseful to prefix a CakePHP plugin with "cakephp-" in the package name. Do not use the CakePHP namespace (cakephp) as vendor name as this is reserved to CakePHP owned plugins. The convention is to use lowercase letters and dashes as separator.
    Each file would be located in the appropriate folder/namespace in your app folder.
    + +## Database Convention Summary + + ++++ + + + + + + + + + + + + + + + + + + +

    Foreign keys

    +

    hasMany belongsTo/ hasOne BelongsToMany

    Relationships are recognized by default as the (singular) name of the related table followed by _id. Users hasMany Articles, articles table will refer to the users table via a user_id foreign key.
    Multiple Wordsmenu_links whose name contains multiple words, the foreign key would be menu_link_id.
    Auto IncrementIn addition to using an auto-incrementing integer as primary keys, you can also use UUID columns. CakePHP will create UUID values automatically using (Cake\Utility\Text::uuid()) whenever you save new records using the Table::save() method.
    Join tablesShould be named after the model tables they will join or the bake command won't work, arranged in alphabetical order (articles_tags rather than tags_articles). Additional columns on the junction table you should create a separate entity/table class for that table.
    + +Now that you've been introduced to CakePHP's fundamentals, you might try a run +through the [Content Management Tutorial](../tutorials-and-examples/cms/installation) to see how things fit +together. diff --git a/docs/en/intro/where-to-get-help.md b/docs/en/intro/where-to-get-help.md new file mode 100644 index 0000000000..2b0d01a1da --- /dev/null +++ b/docs/en/intro/where-to-get-help.md @@ -0,0 +1,105 @@ +# Where to Get Help + +## The Official CakePHP website + + + +The Official CakePHP website is always a great place to visit. It features links +to oft-used developer tools, screencasts, donation opportunities, and downloads. + +## The Cookbook + + + +This manual should probably be the first place you go to get answers. As with +many other open source projects, we get new folks regularly. Try your best to +answer your questions on your own first. Answers may come slower, but will +remain longer – and you'll also be lightening our support load. Both the manual +and the API have an online component. + +## The Bakery + + + +The CakePHP Bakery is a clearing house for all things regarding CakePHP. Check +it out for tutorials, case studies, and code examples. Once you're acquainted +with CakePHP, log on and share your knowledge with the community and gain +instant fame and fortune. + +## The API + + + +Straight to the point and straight from the core developers, the CakePHP API +(Application Programming Interface) is the most comprehensive documentation +around for all the nitty gritty details of the internal workings of the +framework. It's a straight forward code reference, so bring your propeller hat. + +## The Test Cases + +If you ever feel the information provided in the API is not sufficient, check +out the code of the test cases provided with CakePHP in `tests/TestCase/`. +They can serve as practical examples for function and data member usage for a class. + +## Slack + +[CakePHP Slack Support Channel](https://cakesf.slack.com/messages/german/) + +If you're stumped, give us a holler in the CakePHP Slack support channel. +We'd love to hear from you, whether you need some help, want to +find users in your area, or would like to donate your brand new sports car. + +## Discord + +[CakePHP Discord](https://discord.com/invite/k4trEMPebj) + +You can also join us on Discord. + + + +## Official CakePHP Forum + +[CakePHP Official Forum](https://discourse.cakephp.org) + +Our official forum where you can ask for help, suggest ideas and have a talk +about CakePHP. It's a perfect place for quickly finding answers and help others. +Join the CakePHP family by signing up. + +## Stackoverflow + +[https://stackoverflow.com/](https://stackoverflow.com/questions/tagged/cakephp/) + +Tag your questions with `cakephp` and the specific version you are using to +enable existing users of stackoverflow to find your questions. + +## Where to get Help in your Language + +### Danish + +- [Danish CakePHP Slack Channel](https://cakesf.slack.com/messages/denmark/) + +### French + +- [French CakePHP Slack Channel](https://cakesf.slack.com/messages/french/) + +### German + +- [German CakePHP Slack Channel](https://cakesf.slack.com/messages/german/) +- [German CakePHP Facebook Group](https://www.facebook.com/groups/146324018754907/) + +### Dutch + +- [Dutch CakePHP Slack Channel](https://cakesf.slack.com/messages/netherlands/) + +### Japanese + +- [Japanese CakePHP Slack Channel](https://cakesf.slack.com/messages/japanese/) +- [Japanese CakePHP Facebook Group](https://www.facebook.com/groups/304490963004377/) + +### Portuguese + +- [Portuguese CakePHP Slack Channel](https://cakesf.slack.com/messages/portuguese/) + +### Spanish + +- [Spanish CakePHP Slack Channel](https://cakesf.slack.com/messages/spanish/) diff --git a/docs/en/migrations.md b/docs/en/migrations.md new file mode 100644 index 0000000000..fce5ff7ec6 --- /dev/null +++ b/docs/en/migrations.md @@ -0,0 +1,3 @@ +# Migrations + +This page has [moved](https://book.cakephp.org/migrations/4/). diff --git a/docs/en/orm.md b/docs/en/orm.md new file mode 100644 index 0000000000..d80c153105 --- /dev/null +++ b/docs/en/orm.md @@ -0,0 +1,127 @@ +# Database Access & ORM + +In CakePHP, working with data through the database is done with two primary object types: + +- **Repositories** or **table objects** provide access to collections of data. + They allow you to save new records, modify/delete existing ones, define + relations, and perform bulk operations. +- **Entities** represent individual records and allow you to define row/record + level behavior & functionality. + +These two classes are usually responsible for managing almost everything +that happens regarding your data, its validity, interactions and evolution +of the information workflow in your domain of work. + +CakePHP's built-in ORM specializes in relational databases, but can be extended +to support alternative datasources. + +The CakePHP ORM borrows ideas and concepts from both ActiveRecord and Datamapper +patterns. It aims to create a hybrid implementation that combines aspects of +both patterns to create a fast, simple to use ORM. + +Before we get started exploring the ORM, make sure you [configure your +database connections](orm/database-basics#database-configuration). + +## Quick Example + +To get started you don't have to write any code. If you've followed the +[CakePHP conventions for your database tables](intro/conventions#model-and-database-conventions) you can just start using the ORM. For example +if we wanted to load some data from our `articles` table we would start off +creating our `Articles` table class. Create +**src/Model/Table/ArticlesTable.php** with the following code: + +``` php +fetchTable('Articles')->find()->all(); + + foreach ($resultset as $row) { + echo $row->title; + } +} +``` + +In other contexts, you can use the `LocatorAwareTrait` which add accessor methods for ORM tables: + +``` php +use Cake\ORM\Locator\LocatorAwareTrait; + +public function someMethod() +{ + $articles = $this->fetchTable('Articles'); + // more code. +} +``` + +Within a static method you can use the `Cake\Datasource\FactoryLocator` +to get the table locator: + +``` php +$articles = TableRegistry::getTableLocator()->get('Articles'); +``` + +Table classes represent **collections** of **entities**. Next, lets create an +entity class for our Articles. Entity classes let you define accessor and +mutator methods, define custom logic for individual records and much more. We'll +start off by adding the following to **src/Model/Entity/Article.php** after the +`fetchTable('Articles'); +$resultset = $articles->find()->all(); + +foreach ($resultset as $row) { + // Each row is now an instance of our Article class. + echo $row->title; +} +``` + +CakePHP uses naming conventions to link the Table and Entity class together. If +you need to customize which entity a table uses you can use the +`entityClass()` method to set a specific classname. + +See the chapters on [Table Objects](orm/table-objects) and [Entities](orm/entities) for more +information on how to use table objects and entities in your application. + +## More Information + +- [Database Basics](orm/database-basics) +- [Query Builder](orm/query-builder) +- [Table Objects](orm/table-objects) +- [Entities](orm/entities) +- [Associations - Linking Tables Together](orm/associations) +- [Retrieving Data & Results Sets](orm/retrieving-data-and-resultsets) +- [Validating Data](orm/validation) +- [Saving Data](orm/saving-data) +- [Deleting Data](orm/deleting-data) +- [Behaviors](orm/behaviors) +- [Schema System](orm/schema-system) diff --git a/docs/en/orm/associations.md b/docs/en/orm/associations.md new file mode 100644 index 0000000000..c4b712a038 --- /dev/null +++ b/docs/en/orm/associations.md @@ -0,0 +1,786 @@ +# Associations - Linking Tables Together + +Defining relations between different objects in your application should be +a natural process. For example, an article may have many comments, and belong to +an author. Authors may have many articles and comments. The four association +types in CakePHP are: hasOne, hasMany, belongsTo, and belongsToMany. + +| Relationship | Association Type | Example | +|--------------|------------------|------------------------------------| +| one to one | hasOne | A user has one profile. | +| one to many | hasMany | A user can have multiple articles. | +| many to one | belongsTo | Many articles belong to a user. | +| many to many | belongsToMany | Tags belong to many articles. | + +Associations are defined during the `initialize()` method of your table +object. Methods matching the association type allow you to define the +associations in your application. For example if we wanted to define a belongsTo +association in our ArticlesTable: + +``` php +namespace App\Model\Table; + +use Cake\ORM\Table; + +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsTo('Authors'); + } +} +``` + +The simplest form of any association setup takes the table alias you want to +associate with. By default all of the details of an association will use the +CakePHP conventions. If you want to customize how your associations are handled +you can modify them with setters: + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsTo('Authors', [ + 'className' => 'Publishing.Authors' + ]) + ->setForeignKey('author_id') + ->setProperty('author'); + } +} +``` + +The property name will be the property key (of the associated entity) on the entity object, in this case: + +``` php +$authorEntity = $articleEntity->author; +``` + +You can also use arrays to customize your associations: + +``` php +$this->belongsTo('Authors', [ + 'className' => 'Publishing.Authors', + 'foreignKey' => 'author_id', + 'propertyName' => 'author' +]); +``` + +However, arrays do not offer the typehinting and autocomplete benefits that the fluent interface does. + +The same table can be used multiple times to define different types of +associations. For example consider a case where you want to separate +approved comments and those that have not been moderated yet: + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->hasMany('Comments') + ->setFinder('approved'); + + $this->hasMany('UnapprovedComments', [ + 'className' => 'Comments' + ]) + ->setFinder('unapproved') + ->setProperty('unapproved_comments'); + } +} +``` + +As you can see, by specifying the `className` key, it is possible to use the +same table as different associations for the same table. You can even create +self-associated tables to create parent-child relationships: + +``` php +class CategoriesTable extends Table +{ + public function initialize(array $config): void + { + $this->hasMany('SubCategories', [ + 'className' => 'Categories', + ]); + + $this->belongsTo('ParentCategories', [ + 'className' => 'Categories', + ]); + } +} +``` + +You can also setup associations in mass by making a single call to +`Table::addAssociations()` which accepts an array containing a set of +table names indexed by association type as an argument: + +``` php +class PostsTable extends Table +{ + public function initialize(array $config): void + { + $this->addAssociations([ + 'belongsTo' => [ + 'Users' => ['className' => 'App\Model\Table\UsersTable'], + ], + 'hasMany' => ['Comments'], + 'belongsToMany' => ['Tags'], + ]); + } +} +``` + +Each association type accepts multiple associations where the keys are the +aliases, and the values are association config data. If numeric keys are used +the values will be treated as association aliases. + + + +## HasOne Associations + +Let's set up a Users table with a hasOne relationship to the Addresses table. + +First, your database tables need to be keyed correctly. For a hasOne +relationship to work, one table has to contain a foreign key that points to a +record in the other table. In this case, the Addresses table will contain a field +called 'user_id'. The basic pattern is: + +**hasOne:** the *other* model contains the foreign key. + +| Relation | Schema | +|------------------------|-------------------| +| Users hasOne Addresses | addresses.user_id | +| Doctors hasOne Mentors | mentors.doctor_id | + +> [!NOTE] +> It is not mandatory to follow CakePHP conventions, you can override the name +> of any `foreignKey` in your associations definitions. Nevertheless, sticking +> to conventions will make your code less repetitive, easier to read and to +> maintain. + +Once you create the `UsersTable` and `AddressesTable` classes, you can make +the association with the following code: + +``` php +class UsersTable extends Table +{ + public function initialize(array $config): void + { + $this->hasOne('Addresses'); + } +} +``` + +If you need more control, you can define your associations using the setters. +For example, you might want to limit the association to include only certain +records: + +``` php +class UsersTable extends Table +{ + public function initialize(array $config): void + { + $this->hasOne('Addresses') + ->setName('Addresses') + ->setFinder('primary') + ->setDependent(true); + } +} +``` + +If you want to break different addresses into multiple associations, you can do something like: + +``` php +class UsersTable extends Table +{ + public function initialize(array $config): void + { + $this->hasOne('HomeAddresses', [ + 'className' => 'Addresses' + ]) + ->setProperty('home_address') + ->setConditions(['HomeAddresses.label' => 'Home']) + ->setDependent(true); + + $this->hasOne('WorkAddresses', [ + 'className' => 'Addresses' + ]) + ->setProperty('work_address') + ->setConditions(['WorkAddresses.label' => 'Work']) + ->setDependent(true); + } +} +``` + +> [!NOTE] +> If a column is shared by multiple hasOne associations, you must qualify it with the association alias. +> In the above example, the 'label' column is qualified with the 'HomeAddresses' and 'WorkAddresses' aliases. + +Possible keys for hasOne association arrays include: + +- **className**: The class name of the other table. This is the same name used + when getting an instance of the table. In the 'Users hasOne Addresses' example, + it should be 'Addresses'. The default value is the name of the association. +- **foreignKey**: The name of the foreign key column in the other table. The + default value is the underscored, singular name of the current model, + suffixed with '\_id' such as 'user_id' in the above example. +- **bindingKey**: The name of the column in the current table used to match the + `foreignKey`. The default value is the primary key of the current table + such as 'id' of Users in the above example. +- **conditions**: An array of find() compatible conditions such as + `['Addresses.primary' => true]` +- **joinType**: The type of the join used in the SQL query. Accepted values are + 'LEFT' and 'INNER'. You can use 'INNER' to get results only where the + association is set. The default value is 'LEFT'. +- **dependent**: When the dependent key is set to `true`, and an entity is + deleted, the associated model records are also deleted. In this case we set it + to `true` so that deleting a User will also delete her associated Address. +- **cascadeCallbacks**: When this and **dependent** are `true`, cascaded + deletes will load and delete entities so that callbacks are properly + triggered. When `false`, `deleteAll()` is used to remove associated data + and no callbacks are triggered. +- **propertyName**: The property name that should be filled with data from the + associated table into the source table results. By default this is the + underscored & singular name of the association so `address` in our example. +- **strategy**: The query strategy used to load matching record from the other table. + Accepted values are `'join'` and `'select'`. Using `'select'` will generate a separate query + and can be useful when the other table is in different database. The default is `'join'`. +- **finder**: The finder method to use when loading associated records. + +Once this association has been defined, find operations on the Users table can +contain the Address record if it exists: + +``` php +// In a controller or table method. +$query = $users->find('all')->contain(['Addresses'])->all(); +foreach ($query as $user) { + echo $user->address->street; +} +``` + +The above would emit SQL that is similar to: + +``` sql +SELECT * FROM users INNER JOIN addresses ON addresses.user_id = users.id; +``` + + + +## BelongsTo Associations + +Now that we have Address data access from the User table, let's define +a belongsTo association in the Addresses table in order to get access to related +User data. The belongsTo association is a natural complement to the hasOne and +hasMany associations - it allows us to see related data from the other +direction. + +When keying your database tables for a belongsTo relationship, follow this +convention: + +**belongsTo:** the *current* model contains the foreign key. + +| Relation | Schema | +|---------------------------|-------------------| +| Addresses belongsTo Users | addresses.user_id | +| Mentors belongsTo Doctors | mentors.doctor_id | + +> [!TIP] +> If a table contains a foreign key, it belongs to the other table. + +We can define the belongsTo association in our Addresses table as follows: + +``` php +class AddressesTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsTo('Users'); + } +} +``` + +We can also define a more specific relationship using the setters: + +``` php +class AddressesTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsTo('Users') + ->setForeignKey('user_id') + ->setJoinType('INNER'); + } +} +``` + +Possible keys for belongsTo association arrays include: + +- **className**: The class name of the other table. This is the same name used + when getting an instance of the table. In the 'Addresses belongsTo Users' example, + it should be 'Users'. The default value is the name of the association. +- **foreignKey**: The name of the foreign key column in the current table. The + default value is the underscored, singular name of the other model, + suffixed with '\_id' such as 'user_id' in the above example. +- **bindingKey**: The name of the column in the other table used to match the + `foreignKey`. The default value is the primary key of the other table + such as 'id' of Users in the above example. +- **conditions**: An array of find() compatible conditions or SQL strings such + as `['Users.active' => true]` +- **joinType**: The type of the join used in the SQL query. Accepted values are + 'LEFT' and 'INNER'. You can use 'INNER' to get results only where the + association is set. The default value is 'LEFT'. +- **propertyName**: The property name that should be filled with data from the + associated table into the source table results. By default this is the + underscored & singular name of the association so `user` in our example. +- **strategy**: The query strategy used to load matching record from the other table. + Accepted values are `'join'` and `'select'`. Using `'select'` will generate a separate query + and can be useful when the other table is in different database. The default is `'join'`. +- **finder**: The finder method to use when loading associated records. + +Once this association has been defined, find operations on the Addresses table can +contain the User record if it exists: + +``` php +// In a controller or table method. +$query = $addresses->find('all')->contain(['Users'])->all(); +foreach ($query as $address) { + echo $address->user->username; +} +``` + +The above would output SQL similar to: + +``` sql +SELECT * FROM addresses LEFT JOIN users ON addresses.user_id = users.id; +``` + + + +## HasMany Associations + +An example of a hasMany association is "Articles hasMany Comments". Defining this +association will allow us to fetch an article's comments when the article is +loaded. + +When creating your database tables for a hasMany relationship, follow this +convention: + +**hasMany:** the *other* model contains the foreign key. + +| Relation | Schema | +|---------------------------|---------------------| +| Articles hasMany Comments | Comments.article_id | +| Products hasMany Options | Options.product_id | +| Doctors hasMany Patients | Patients.doctor_id | + +We can define the hasMany association in our Articles model as follows: + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->hasMany('Comments'); + } +} +``` + +We can also define a more specific relationship using the setters: + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->hasMany('Comments') + ->setForeignKey('article_id') + ->setDependent(true); + } +} +``` + +Sometimes you may want to configure composite keys in your associations: + +``` php +// Within ArticlesTable::initialize() call +$this->hasMany('Comments') + ->setForeignKey([ + 'article_id', + 'article_hash', + ]); +``` + +Relying on the example above, we have passed an array containing the desired +composite keys to `setForeignKey()`. By default the `bindingKey` would be +automatically defined as `id` and `hash` respectively, but let's assume that +you need to specify different binding fields than the defaults. You can setup it +manually with `setBindingKey()`: + +``` php +// Within ArticlesTable::initialize() call +$this->hasMany('Comments') + ->setForeignKey([ + 'article_id', + 'article_hash', + ]) + ->setBindingKey([ + 'whatever_id', + 'whatever_hash', + ]); +``` + +Like hasOne associations, `foreignKey` is in the other (Comments) +table and `bindingKey` is in the current (Articles) table. + +Possible keys for hasMany association arrays include: + +- **className**: The class name of the other table. This is the same name used + when getting an instance of the table. In the 'Articles hasMany Comments' example, + it should be 'Comments'. The default value is the name of the association. +- **foreignKey**: The name of the foreign key column in the other table. The + default value is the underscored, singular name of the current model, + suffixed with '\_id' such as 'article_id' in the above example. +- **bindingKey**: The name of the column in the current table used to match the + `foreignKey`. The default value is the primary key of the current table + such as 'id' of Articles in the above example. +- **conditions**: an array of find() compatible conditions or SQL + strings such as `['Comments.visible' => true]`. It is recommended to + use the `finder` option instead. +- **sort**: an array of find() compatible order clauses or SQL + strings such as `['Comments.created' => 'ASC']` +- **dependent**: When dependent is set to `true`, recursive model + deletion is possible. In this example, Comment records will be + deleted when their associated Article record has been deleted. +- **cascadeCallbacks**: When this and **dependent** are `true`, cascaded + deletes will load and delete entities so that callbacks are properly + triggered. When `false`, `deleteAll()` is used to remove associated data + and no callbacks are triggered. +- **propertyName**: The property name that should be filled with data from the + associated table into the source table results. By default this is the + underscored & plural name of the association so `comments` in our example. +- **strategy**: Defines the query strategy to use. Defaults to 'select'. The + other valid value is 'subquery', which replaces the `IN` list with an + equivalent subquery. +- **saveStrategy**: Either 'append' or 'replace'. Defaults to 'append'. When 'append' the current + records are appended to any records in the database. When 'replace' associated + records not in the current set will be removed. If the foreign key is a nullable + column or if `dependent` is true records will be orphaned. +- **finder**: The finder method to use when loading associated records. See the + [Association Finder](#association-finder) section for more information. + +Once this association has been defined, find operations on the Articles table +can contain the Comment records if they exist: + +``` php +// In a controller or table method. +$query = $articles->find('all')->contain(['Comments'])->all(); +foreach ($query as $article) { + echo $article->comments[0]->text; +} +``` + +The above would output SQL similar to: + +``` sql +SELECT * FROM articles; +SELECT * FROM comments WHERE article_id IN (1, 2, 3, 4, 5); +``` + +When the subquery strategy is used, SQL similar to the following will be +generated: + +``` sql +SELECT * FROM articles; +SELECT * FROM comments WHERE article_id IN (SELECT id FROM articles); +``` + +You may want to cache the counts for your hasMany associations. This is useful +when you often need to show the number of associated records, but don't want to +load all the records just to count them. For example, the comment count on any +given article is often cached to make generating lists of articles more +efficient. You can use the [CounterCacheBehavior](../orm/behaviors/counter-cache) to cache counts of associated records. + +You should make sure that your database tables do not contain columns that match +association property names. If for example you have counter fields that conflict +with association properties, you must either rename the association property, or +the column name. + + + +## BelongsToMany Associations + +An example of a BelongsToMany association is "Article BelongsToMany Tags", where +the tags from one article are shared with other articles. BelongsToMany is +often referred to as "has and belongs to many", and is a classic "many to many" +association. + +The main difference between hasMany and BelongsToMany is that the link between +the models in a BelongsToMany association is not exclusive. For example, we are +joining our Articles table with a Tags table. Using 'funny' as a Tag for my +Article, doesn't "use up" the tag. I can also use it on the next article +I write. + +Three database tables are required for a BelongsToMany association. In the +example above we would need tables for `articles`, `tags` and +`articles_tags`. The `articles_tags` table contains the data that links +tags and articles together. The joining table is named after the two tables +involved, separated with an underscore by convention. In its simplest form, this +table consists of `article_id` and `tag_id` and a multi-column +`PRIMARY KEY` index spanning both columns. + +**belongsToMany** requires a separate join table that includes both *model* +names. + +| Relationship | Join Table Fields | +|----|----| +| Articles belongsToMany Tags | articles_tags.id, articles_tags.tag_id, articles_tags.article_id | +| Patients belongsToMany Doctors | doctors_patients.id, doctors_patients.doctor_id, doctors_patients.patient_id. | + +We can define the belongsToMany association in both our models as follows: + +``` php +// In src/Model/Table/ArticlesTable.php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsToMany('Tags'); + } +} + +// In src/Model/Table/TagsTable.php +class TagsTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsToMany('Articles'); + } +} +``` + +We can also define a more specific relationship using configuration: + +``` php +// In src/Model/Table/TagsTable.php +class TagsTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsToMany('Articles', [ + 'joinTable' => 'articles_tags', + ]); + } +} +``` + +Possible keys for belongsToMany association arrays include: + +- **className**: The class name of the other table. This is the same name used + when getting an instance of the table. In the 'Articles belongsToMany Tags' + example, it should be 'Tags'. The default value is the name of the association. +- **joinTable**: The name of the join table used in this + association (if the current table doesn't adhere to the naming + convention for belongsToMany join tables). By default this table + name will be used to load the Table instance for the join table. +- **foreignKey**: The name of the foreign key that references the current model + found on the join table, or list in case of composite foreign keys. + This is especially handy if you need to define multiple + belongsToMany relationships. The default value for this key is the + underscored, singular name of the current model, suffixed with '\_id'. +- **bindingKey**: The name of the column in the current table, that will be used + for matching the `foreignKey`. Defaults to the primary key. +- **targetForeignKey**: The name of the foreign key that references the target + model found on the join model, or list in case of composite foreign keys. + The default value for this key is the underscored, singular name of + the target model, suffixed with '\_id'. +- **conditions**: An array of `find()` compatible conditions. If you have + conditions on an associated table, you should use a 'through' model, and + define the necessary belongsTo associations on it. It is recommended to + use the `finder` option instead. +- **sort**: an array of find() compatible order clauses. +- **dependent**: When the dependent key is set to `false`, and an entity is + deleted, the data of the join table will not be deleted. +- **through**: Allows you to provide either the alias of the Table instance you + want used on the join table, or the instance itself. This makes customizing + the join table keys possible, and allows you to customize the behavior of the + pivot table. +- **cascadeCallbacks**: When this is `true`, cascaded deletes will load and + delete entities so that callbacks are properly triggered on join table + records. When `false`, `deleteAll()` is used to remove associated data and + no callbacks are triggered. This defaults to `false` to help reduce + overhead. +- **propertyName**: The property name that should be filled with data from the + associated table into the source table results. By default this is the + underscored & plural name of the association, so `tags` in our example. +- **strategy**: Defines the query strategy to use. Defaults to 'select'. The + other valid value is 'subquery', which replaces the `IN` list with an + equivalent subquery. +- **saveStrategy**: Either 'append' or 'replace'. Defaults to 'replace'. + Indicates the mode to be used for saving associated entities. The former will + only create new links between both side of the relation and the latter will + do a wipe and replace to create the links between the passed entities when + saving. +- **finder**: The finder method to use when loading associated records. See the + [Association Finder](#association-finder) section for more information. + +Once this association has been defined, find operations on the Articles table can +contain the Tag records if they exist: + +``` php +// In a controller or table method. +$query = $articles->find('all')->contain(['Tags'])->all(); +foreach ($query as $article) { + echo $article->tags[0]->text; +} +``` + +The above would output SQL similar to: + +``` sql +SELECT * FROM articles; +SELECT * FROM tags +INNER JOIN articles_tags ON ( + tags.id = article_tags.tag_id + AND article_id IN (1, 2, 3, 4, 5) +); +``` + +When the subquery strategy is used, SQL similar to the following will be +generated: + +``` sql +SELECT * FROM articles; +SELECT * FROM tags +INNER JOIN articles_tags ON ( + tags.id = article_tags.tag_id + AND article_id IN (SELECT id FROM articles) +); +``` + + + +### Using the 'through' Option + +If you plan on adding extra information to the join/pivot table, or if you need +to use join columns outside of the conventions, you will need to define the +`through` option. The `through` option provides you full control over how +the belongsToMany association will be created. + +It is sometimes desirable to store additional data with a many to many +association. Consider the following: + + Student BelongsToMany Course + Course BelongsToMany Student + +A Student can take many Courses and a Course can be taken by many Students. This +is a simple many to many association. The following table would suffice: + + id | student_id | course_id + +Now what if we want to store the number of days that were attended by the +student on the course and their final grade? The table we'd want would be: + + id | student_id | course_id | days_attended | grade + +The way to implement our requirement is to use a **join model**, otherwise known +as a **hasMany through** association. That is, the association is a model +itself. So, we can create a new model CoursesMemberships. Take a look at the +following models: + +``` php +class StudentsTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsToMany('Courses', [ + 'through' => 'CoursesMemberships', + ]); + } +} + +class CoursesTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsToMany('Students', [ + 'through' => 'CoursesMemberships', + ]); + } +} + +class CoursesMembershipsTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsTo('Students'); + $this->belongsTo('Courses'); + } +} +``` + +The CoursesMemberships join table uniquely identifies a given Student's +participation on a Course in addition to extra meta-information. + +When using a query object with a BelongsToMany relationship with a `through` +model, add contain and matching conditions for the association target table into +your query object. The `through` table can then be referenced in other conditions +such as a where condition by designating the through table name before the field +you are filtering on: + +``` php +$query = $this->find( + 'list', + valueField: 'studentFirstName', order: 'students.id' + ) + ->contain(['Courses']) + ->matching('Courses') + ->where(['CoursesMemberships.grade' => 'B']); +``` + + + +### Using Association Finders + +By default associations will load records based on the foreign key columns. If +you want to define additional conditions for associations, you can use +a `finder`. When an association is loaded the ORM will use your [custom +finder](../orm/retrieving-data-and-resultsets#custom-find-methods) to load, update, or delete associated records. +Using finders lets you encapsulate your queries and make them more reusable. +There are some limitations when using finders to load data in associations that +are loaded using joins (belongsTo/hasOne). Only the following aspects of the +query will be applied to the root query: + +- Where conditions. +- Additional joins. +- Contained associations. + +Other aspects of the query, such as selected columns, order, group by, having +and other sub-statements, will not be applied to the root query. Associations +that are *not* loaded through joins (hasMany/belongsToMany), do not have the +above restrictions and can also use result formatters or map/reduce functions. + +## Association Conventions + +By default, associations should be configured and referenced using the CamelCase style. +This enables property chains to related tables in the following way: + +``` php +$this->MyTableOne->MyTableTwo->find()->...; +``` + +Association properties on entities do not use CamelCase conventions though. Instead for a hasOne/belongsTo relation like "User belongsTo Roles", you would get a role property instead of Role or \`Roles\`: + +``` php +// A single entity (or null if not available) +$role = $user->role; +``` + +Whereas for the other direction "Roles hasMany Users" it would be: + +``` php +// Collection of user entities (or null if not available) +$users = $role->users; +``` + +## Loading Associations + +Once you've defined your associations you can [eager load associations](../orm/retrieving-data-and-resultsets#eager-loading-associations) when fetching results. diff --git a/docs/en/orm/behaviors.md b/docs/en/orm/behaviors.md new file mode 100644 index 0000000000..c3c140ccaa --- /dev/null +++ b/docs/en/orm/behaviors.md @@ -0,0 +1,352 @@ +# Behaviors + +Behaviors are a way to organize and enable horizontal re-use of Model layer +logic. Conceptually they are similar to traits. However, behaviors are +implemented as separate classes. This allows them to hook into the +life-cycle callbacks that models emit, while providing trait-like features. + +Behaviors provide a convenient way to package up behavior that is common across +many models. For example, CakePHP includes a `TimestampBehavior`. Many +models will want timestamp fields, and the logic to manage these fields is +not specific to any one model. It is these kinds of scenarios that behaviors are +a perfect fit for. + +## Using Behaviors + + + +## Core Behaviors + +- [CounterCache](../orm/behaviors/counter-cache) +- [Timestamp](../orm/behaviors/timestamp) +- [Translate](../orm/behaviors/translate) +- [Tree](../orm/behaviors/tree) + +## Creating a Behavior + +In the following examples we will create a very simple `SluggableBehavior`. +This behavior will allow us to populate a slug field with the results of +`Text::slug()` based on another field. + +Before we create our behavior we should understand the conventions for +behaviors: + +- Behavior files are located in **src/Model/Behavior**, or + `MyPlugin\Model\Behavior`. +- Behavior classes should be in the `App\Model\Behavior` namespace, or + `MyPlugin\Model\Behavior` namespace. +- Behavior class names end in `Behavior`. +- Behaviors extend `Cake\ORM\Behavior`. + +To create our sluggable behavior. Put the following into +**src/Model/Behavior/SluggableBehavior.php**: + +``` php +namespace App\Model\Behavior; + +use Cake\ORM\Behavior; + +class SluggableBehavior extends Behavior +{ +} +``` + +Similar to tables, behaviors also have an `initialize()` hook where you can +put your behavior's initialization code, if required: + +``` php +public function initialize(array $config): void +{ + // Some initialization code here +} +``` + +We can now add this behavior to one of our table classes. In this example we'll +use an `ArticlesTable`, as articles often have slug properties for creating +friendly URLs: + +``` php +namespace App\Model\Table; + +use Cake\ORM\Table; + +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Sluggable'); + } +} +``` + +Our new behavior doesn't do much of anything right now. Next, we'll add a mixin +method and an event listener so that when we save entities we can automatically +slug a field. + +### Calling behavior methods + +Public methods on behaviors can be called as normal methods: + +``` php +$articles->getBehavior('Sluggable')->slug($value); +``` + +Public methods defined on behaviors are also added as 'mixin' methods on the +table object they are attached to. If you attach two behaviors that provide the +same methods an exception will be raised. If a behavior provides the same method +as a table class, the behavior method will not be callable from the table. +Behavior mixin methods will receive the exact same arguments that are provided +to the table. For example, if our SluggableBehavior defined the following +method: + +``` php +public function slug($value) +{ + return Text::slug($value, $this->_config['replacement']); +} +``` + +It could be invoked using: + +``` php +$slug = $articles->slug('My article'); +``` + +::: info Deprecated in version 5.3.0 +Calling behavior methods as mixin methods is deprecated +::: + +#### Limiting or Renaming Exposed Mixin Methods + +When creating behaviors, there may be situations where you don't want to expose +public methods as mixin methods. In these cases you can use the +`implementedMethods` configuration key to rename or exclude mixin methods. For +example if we wanted to prefix our slug() method we could do the following: + +``` php +protected $_defaultConfig = [ + 'implementedMethods' => [ + 'superSlug' => 'slug', + ] +]; +``` + +Applying this configuration will make `slug()` not callable, however it will +add a `superSlug()` mixin method to the table. Notably if our behavior +implemented other public methods they would **not** be available as mixin +methods with the above configuration. + +Since the exposed methods are decided by configuration you can also +rename/remove mixin methods when adding a behavior to a table. For example: + +``` php +// In a table's initialize() method. +$this->addBehavior('Sluggable', [ + 'implementedMethods' => [ + 'superSlug' => 'slug', + ] +]); +``` + +### Defining Event Listeners + +Now that our behavior has a mixin method to slug fields, we can implement +a callback listener to automatically slug a field when entities are saved. We'll +also modify our slug method to accept an entity instead of just a plain value. Our +behavior should now look like: + +``` php +namespace App\Model\Behavior; + +use ArrayObject; +use Cake\Datasource\EntityInterface; +use Cake\Event\EventInterface; +use Cake\ORM\Behavior; +use Cake\ORM\Entity; +use Cake\ORM\Query\SelectQuery; +use Cake\Utility\Text; + +class SluggableBehavior extends Behavior +{ + protected array $_defaultConfig = [ + 'field' => 'title', + 'slug' => 'slug', + 'replacement' => '-', + ]; + + public function slug(EntityInterface $entity) + { + $config = $this->getConfig(); + $value = $entity->get($config['field']); + $entity->set($config['slug'], Text::slug($value, $config['replacement'])); + } + + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void + { + $this->slug($entity); + } + +} +``` + +The above code shows a few interesting features of behaviors: + +- Behaviors can define callback methods by defining methods that follow the + [Table Callbacks](../orm/table-objects#table-callbacks) conventions. +- Behaviors can define a default configuration property. This property is merged + with the overrides when a behavior is attached to the table. + +To prevent the save from continuing, simply stop event propagation in your callback: + +``` php +public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void +{ + if (...) { + $event->stopPropagation(); + $event->setResult(false); + + return; + } + $this->slug($entity); +} +``` + +Alternatively, you can return false from the callback. This has the same effect as stopping event propagation. + +### Defining Finders + +Now that we are able to save articles with slug values, we should implement +a finder method so we can fetch articles by their slug. Behavior finder +methods, use the same conventions as [Custom Find Methods](../orm/retrieving-data-and-resultsets#custom-find-methods) do. Our +`find('slug')` method would look like: + +``` php +public function findSlug(SelectQuery $query, string $slug): SelectQuery +{ + return $query->where(['slug' => $slug]); +} +``` + +Once our behavior has the above method we can call it: + +``` php +$article = $articles->find('slug', slug: $value)->first(); +``` + +#### Limiting or Renaming Exposed Finder Methods + +When creating behaviors, there may be situations where you don't want to expose +finder methods, or you need to rename finders to avoid duplicated methods. In +these cases you can use the `implementedFinders` configuration key to rename +or exclude finder methods. For example if we wanted to rename our `find(slug)` +method we could do the following: + +``` php +protected array $_defaultConfig = [ + 'implementedFinders' => [ + 'slugged' => 'findSlug', + ] +]; +``` + +Applying this configuration will make `find('slug')` trigger an error. However +it will make `find('slugged')` available. Notably if our behavior implemented +other finder methods they would **not** be available, as they are not included +in the configuration. + +Since the exposed methods are decided by configuration you can also +rename/remove finder methods when adding a behavior to a table. For example: + +``` php +// In a table's initialize() method. +$this->addBehavior('Sluggable', [ + 'implementedFinders' => [ + 'slugged' => 'findSlug', + ] +]); +``` + +## Transforming Request Data into Entity Properties + +Behaviors can define logic for how the custom fields they provide are +marshalled by implementing the `Cake\ORM\PropertyMarshalInterface`. This +interface requires a single method to be implemented: + +``` php +public function buildMarshalMap($marshaller, $map, $options) +{ + return [ + 'custom_behavior_field' => function ($value, $entity) { + // Transform the value as necessary + return $value . '123'; + } + ]; +} +``` + +The `TranslateBehavior` has a non-trivial implementation of this interface +that you might want to refer to. + +## Removing Loaded Behaviors + +To remove a behavior from your table you can call the `removeBehavior()` method: + +``` php +// Remove the loaded behavior +$this->removeBehavior('Sluggable'); +``` + +## Accessing Loaded Behaviors + +Once you've attached behaviors to your Table instance you can introspect the +loaded behaviors, or access specific behaviors using the `BehaviorRegistry`: + +``` php +// See which behaviors are loaded +$table->behaviors()->loaded(); + +// Check if a specific behavior is loaded. +// Remember to omit plugin prefixes. +$table->behaviors()->has('CounterCache'); + +// Get a loaded behavior +// Remember to omit plugin prefixes +$table->behaviors()->get('CounterCache'); +``` + +### Re-configuring Loaded Behaviors + +To modify the configuration of an already loaded behavior you can combine the +`BehaviorRegistry::get` command with `config` command provided by the +`InstanceConfigTrait` trait. + +For example, if a parent class, such as `AppTable`, loaded the `Timestamp` +behavior you could do the following to add, modify or remove the configurations +for the behavior. In this case, we will add an event we want Timestamp to +respond to: + +``` php +namespace App\Model\Table; + +use App\Model\Table\AppTable; // similar to AppController + +class UsersTable extends AppTable +{ + public function initialize(array $options): void + { + parent::initialize($options); + + // For example, if our parent calls $this->addBehavior('Timestamp') + // and we want to add an additional event + if ($this->behaviors()->has('Timestamp')) { + $this->behaviors()->get('Timestamp')->setConfig([ + 'events' => [ + 'Users.login' => [ + 'last_login' => 'always' + ], + ], + ]); + } + } +} +``` diff --git a/docs/en/orm/behaviors/counter-cache.md b/docs/en/orm/behaviors/counter-cache.md new file mode 100644 index 0000000000..656e7e6262 --- /dev/null +++ b/docs/en/orm/behaviors/counter-cache.md @@ -0,0 +1,193 @@ +# CounterCache + +`class` Cake\\ORM\\Behavior\\**CounterCacheBehavior** + +Often times web applications need to display counts of related objects. For +example, when showing a list of articles you may want to display how many +comments it has. Or when showing a user you might want to show how many +friends/followers she has. The CounterCache behavior is intended for these +situations. CounterCache will update a field in the associated models assigned +in the options when it is invoked. The fields should exist in the database and +be of the type INT. + +## Basic Usage + +You enable the CounterCache behavior like any other behavior, but it won't do +anything until you configure some relations and the field counts that should be +stored on each of them. Using our example below, we could cache the comment +count for each article with the following: + +``` php +class CommentsTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('CounterCache', [ + 'Articles' => ['comment_count'] + ]); + } +} +``` + +> [!NOTE] +> The column `comment_count` should exist in the `articles` table. + +The CounterCache configuration should be a map of relation names and the +specific configuration for that relation. + +As you see you need to add the behavior on the "other side" of the association +where you actually want the field to be updated. In this example the behavior +is added to the `CommentsTable` even though it updates the `comment_count` +field in the `ArticlesTable`. + +The counter's value will be updated each time an entity is saved or deleted. +The counter **will not** be updated when you + +- save the entity without changing data or +- use `updateAll()` or +- use `deleteAll()` or +- execute SQL you have written + +## Advanced Usage + +If you need to keep a cached counter for less than all of the related records, +you can supply additional conditions or finder methods to generate a +counter value: + +``` php +// Use a specific find method. +// In this case find(published) +$this->addBehavior('CounterCache', [ + 'Articles' => [ + 'comment_count' => [ + 'finder' => 'published', + ], + ], +]); +``` + +If you don't have a custom finder method you can provide an array of conditions +to find records instead: + +``` php +$this->addBehavior('CounterCache', [ + 'Articles' => [ + 'comment_count' => [ + 'conditions' => ['Comments.spam' => false], + ], + ], +]); +``` + +If you want CounterCache to update multiple fields, for example both showing a +conditional count and a basic count you can add these fields in the array: + +``` php +$this->addBehavior('CounterCache', [ + 'Articles' => ['comment_count', + 'published_comment_count' => [ + 'finder' => 'published', + ], + ], +]); +``` + +If you want to calculate the CounterCache field value on your own, you can set +the `ignoreDirty` option to `true`. +This will prevent the field from being recalculated if you've set it dirty +before: + +``` php +$this->addBehavior('CounterCache', [ + 'Articles' => [ + 'comment_count' => [ + 'ignoreDirty' => true, + ], + ], +]); +``` + +Lastly, if a custom finder and conditions are not suitable you can provide +a callback function. Your function must return the count value to be stored: + +``` php +$this->addBehavior('CounterCache', [ + 'Articles' => [ + 'rating_avg' => function ($event, $entity, $table, $original) { + return 4.5; + } + ], +]); +``` + +Your function can return `false` to skip updating the counter column, or +a `SelectQuery` object that produced the count value. If you return a `SelectQuery` +object, your query will be used as a subquery in the update statement. The +`$table` parameter refers to the table object holding the behavior (not the +target relation) for convenience. The callback is invoked at least once with +`$original` set to `false`. If the entity-update changes the association +then the callback is invoked a *second* time with `true`, the return value +then updates the counter of the *previously* associated item. + +> [!NOTE] +> The CounterCache behavior works for `belongsTo` associations only. For +> example for "Comments belongsTo Articles", you need to add the CounterCache +> behavior to the `CommentsTable` in order to generate `comment_count` for +> Articles table. + +::: info Changed in version 5.1.2 +As of CakePHP 5.1.2, the counter cache values are updated using a single query using sub-queries, instead of separate queries, to fetch the count and update a record. If required you can disable the use of sub-queries by setting useSubQuery key to false in the config ['Articles' => ['comment_count' => ['useSubQuery' => false]] +::: + +## Belongs to many Usage + +It is possible to use the CounterCache behavior in a `belongsToMany` association. +First, you need to add the `through` and `cascadeCallbacks` options to the +`belongsToMany` association: + +``` text +'through' => 'CommentsArticles', +'cascadeCallbacks' => true +``` + +Also see [Using The Through Option](../../orm/associations#using-the-through-option) how to configure a custom join table. + +The `CommentsArticles` is the name of the junction table classname. +If you don't have it you should create it with the bake CLI tool. + +In this `src/Model/Table/CommentsArticlesTable.php` you then need to add the behavior +with the same code as described above.: + +``` php +$this->addBehavior('CounterCache', [ + 'Articles' => ['comments_count'], +]); +``` + +Finally clear all caches with `bin/cake cache clear_all` and try it out. + +## Manually updating counter caches + +`method` Cake\\ORM\\Behavior\\CounterCacheBehavior::**updateCounterCache(?string $assocName = null, int $limit = 100, ?int $page = null): void**() + +The `updateCounterCache()` method allows you to update the counter cache values +for all records of one or all configured associations in batches. This can be useful, +for example, to update the counter cache after importing data directly into the database.: + +``` php +// Update the counter cache for all configured associations +$table->getBehavior('CounterCache')->updateCounterCache(); + +// Update the counter cache for a specific association, 200 records per batch +$table->getBehavior('CounterCache')->updateCounterCache('Articles', 200); + +// Update only the first page of records +$table->getBehavior('CounterCache')->updateCounterCache('Articles', page: 1); +``` + +::: info Added in version 5.2.0 +::: + +> [!NOTE] +> This methods won't update the counter cache values for fields which are +> configured to use a closure to get the count value. diff --git a/docs/en/orm/behaviors/timestamp.md b/docs/en/orm/behaviors/timestamp.md new file mode 100644 index 0000000000..eb00530d5f --- /dev/null +++ b/docs/en/orm/behaviors/timestamp.md @@ -0,0 +1,88 @@ +# Timestamp + +`class` Cake\\ORM\\Behavior\\**TimestampBehavior** + +The timestamp behavior allows your table objects to update one or more +timestamps on each model event. This is primarily used to populate data into +`created` and `modified` fields. However, with some additional +configuration, you can update any timestamp/datetime column on any event a table +publishes. + +## Basic Usage + +You enable the timestamp behavior like any other behavior: + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Timestamp'); + } +} +``` + +The default configuration will do the following: + +- When a new entity is saved the `created` and `modified` fields will be set + to the current time. +- When an entity is updated, the `modified` field is set to the current time. + +## Using and Configuring the Behavior + +If you need to modify fields with different names, or want to update additional +timestamp fields on custom events you can use some additional configuration: + +``` php +class OrdersTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Timestamp', [ + 'events' => [ + 'Model.beforeSave' => [ + 'created_at' => 'new', + 'updated_at' => 'always', + ], + 'Orders.completed' => [ + 'completed_at' => 'always' + ] + ] + ]); + } +} +``` + +As you can see above, in addition to the standard `Model.beforeSave` event, we +are also updating the `completed_at` column when orders are completed. + +## Updating Timestamps on Entities + +Sometimes you'll want to update just the timestamps on an entity without +changing any other properties. This is sometimes referred to as 'touching' +a record. In CakePHP you can use the `touch()` method to do exactly this: + +``` php +// Touch based on the Model.beforeSave event. +$articles->getBehavior('Timestamp')->touch($article); + +// Touch based on a specific event. +$orders->getBehavior('Timestamp')->touch($order, 'Orders.completed'); +``` + +After you have saved the entity, the field is updated. + +Touching records can be useful when you want to signal that a parent resource +has changed when a child resource is created/updated. For example: updating an +article when a new comment is added. + +## Saving Updates Without Modifying Timestamps + +To disable the automatic modification of the `updated` timestamp column when +saving an entity you can mark the attribute as 'dirty': + +``` php +// Mark the modified column as dirty making +// the current value be set on update. +$order->setDirty('modified', true); +``` diff --git a/docs/en/orm/behaviors/translate.md b/docs/en/orm/behaviors/translate.md new file mode 100644 index 0000000000..ce3052e945 --- /dev/null +++ b/docs/en/orm/behaviors/translate.md @@ -0,0 +1,565 @@ +# Translate + +`class` Cake\\ORM\\Behavior\\**TranslateBehavior** + +The Translate behavior allows you to create and retrieve translated copies +of your entities in multiple languages. + +> [!WARNING] +> The TranslateBehavior does not support composite primary keys at this point +> in time. + +## Translation Strategies + +The behavior offers two strategies for how the translations are stored. + +1. Shadow table Strategy: This strategy uses a separate "shadow table" for each + Table object to store translation of all translated fields of that table. + This is the default strategy. +2. Eav Strategy: This strategy uses a `i18n` table where it stores the + translation for each of the fields of any given Table object that it's bound to. + +## Shadow Table Strategy + +Let's assume we have an `articles` table and we want it's `title` and `body` +fields to be translated. For that we create a shadow table `articles_translations`: + +``` sql +CREATE TABLE `articles_translations` ( + `id` int(11) NOT NULL, + `locale` varchar(5) NOT NULL, + `title` varchar(255), + `body` text, + PRIMARY KEY (`id`,`locale`) +); +``` + +The shadow table needs `id` and `locale` columns which together +form the primary key and other columns with same name as primary table which +need to be translated. + +A note on language abbreviations: The Translate Behavior doesn't impose any +restrictions on the language identifier, the possible values are only restricted +by the `locale` column type/size. `locale` is defined as `varchar(6)` in +case you want to use abbreviations like `es-419` (Spanish for Latin America, +language abbreviation with area code [UN M.49](https://en.wikipedia.org/wiki/UN_M.49)). + +> [!TIP] +> It's wise to use the same language abbreviations as required for +> [Internationalization and Localization](../../core-libraries/internationalization-and-localization). Thus you are +> consistent and switching the language works identical for both, the +> `Translate` behavior and `Internationalization and Localization`. + +So it's recommended to use either the two letter ISO code of the language like +`en`, `fr`, `de` or the full locale name such as `fr_FR`, `es_AR`, +`da_DK` which contains both the language and the country where it is spoken. + +## Eav Strategy + +In order to use the Eav strategy, you need to create a `i18n` table with the +correct schema. Currently the only way of loading the `i18n` table is by +manually running the following SQL script in your database: + +``` sql +CREATE TABLE i18n ( + id int NOT NULL auto_increment, + locale varchar(6) NOT NULL, + model varchar(255) NOT NULL, + foreign_key int(10) NOT NULL, + field varchar(255) NOT NULL, + content text, + PRIMARY KEY (id), + UNIQUE INDEX I18N_LOCALE_FIELD(locale, model, foreign_key, field), + INDEX I18N_FIELD(model, foreign_key, field) +); +``` + +The schema is also available as sql file in **/config/schema/i18n.sql**. + +## Attaching the Translate Behavior to Your Tables + +Attaching the behavior can be done in the `initialize()` method in your Table +class: + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + // By default ShadowTable will be used. + $this->addBehavior('Translate', ['fields' => ['title', 'body']]); + } +} +``` + +For shadow table strategy specifying the `fields` key is optional as the +behavior can infer the fields from the shadow table columns. + +If you want to use the `EavStrategy` then you can configure the behavior +as: + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Translate', [ + 'strategyClass' => \Cake\ORM\Behavior\Translate\EavStrategy::class, + 'fields' => ['title', 'body'], + ]); + } +} +``` + +For `EavStrategy` you are required to pass the `fields` key in the +configuration array. This list of fields is needed to tell the behavior what +columns will be able to store translations. + +By default the locale specified in `App.defaultLocale` config is used as default +locale for the `TranslateBehavior`. You can override that by setting `defaultLocale` +config of the behavior: + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Translate', [ + 'defaultLocale' => 'en_GB', + ]); + } +} +``` + +## Quick tour + +Regardless of the datastructure strategy you choose the behavior provides the +same API to manage translations. + +Now, select a language to be used for retrieving entities by changing +the application language, which will affect all translations: + +``` php +// In the Articles controller. Change the locale to Spanish, for example +I18n::setLocale('es'); +``` + +Then, get an existing entity: + +``` php +$article = $this->Articles->get(12); +echo $article->title; // Echoes 'A title', not translated yet +``` + +Next, translate your entity: + +``` php +$article->title = 'Un Artículo'; +$this->Articles->save($article); +``` + +You can try now getting your entity again: + +``` php +$article = $this->Articles->get(12); +echo $article->title; // Echoes 'Un Artículo', yay piece of cake! +``` + +Working with multiple translations can be done by using a special trait +in your Entity class: + +``` php +use Cake\ORM\Behavior\Translate\TranslateTrait; +use Cake\ORM\Entity; + +class Article extends Entity +{ + use TranslateTrait; +} +``` + +Now you can find all translations for a single entity: + +``` php +$article = $this->Articles->find('translations')->first(); +echo $article->translation('es')->title; // 'Un Artículo' + +echo $article->translation('en')->title; // 'An Article'; +``` + +And save multiple translations at once: + +``` php +$article->translation('es')->title = 'Otro Título'; +$article->translation('fr')->title = 'Un autre Titre'; +$this->Articles->save($article); +``` + +If you want to go deeper on how it works or how to tune the +behavior for your needs, keep on reading the rest of this chapter. + +### Using a Separate Translations Table for Eav strategy + +If you wish to use a table other than `i18n` for translating a particular +repository, you can specify the name of the table class name for your custom +table in the behavior's configuration. This is common when you have multiple +tables to translate and you want a cleaner separation of the data that is stored +for each different table: + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Translate', [ + 'fields' => ['title', 'body'], + 'translationTable' => 'ArticlesI18n', + ]); + } +} +``` + +You need to make sure that any custom table you use has the columns `field`, +`foreign_key`, `locale` and `model`. + +## Reading Translated Content + +As shown above you can use the `setLocale()` method to choose the active +translation for entities that are loaded: + +``` php +// Load I18n core functions at the beginning of your Articles Controller: +use Cake\I18n\I18n; + +// Then you can change the language in your action: +I18n::setLocale('es'); + +// All entities in results will contain spanish translation +$results = $this->Articles->find()->all(); +``` + +This method works with any finder in your tables. For example, you can +use TranslateBehavior with `find('list')`: + +``` php +I18n::setLocale('es'); +$data = $this->Articles->find('list')->toArray(); + +// Data will contain +[1 => 'Mi primer artículo', 2 => 'El segundo artículo', 15 => 'Otro articulo' ...] + +// Change the locale to french for a single find call +$data = $this->Articles->find('list', locale: 'fr')->toArray(); +``` + +### Retrieve All Translations For An Entity + +When building interfaces for updating translated content, it is often helpful to +show one or more translation(s) at the same time. You can use the +`translations` finder for this: + +``` php +// Find the first article with all corresponding translations +$article = $this->Articles->find('translations')->first(); +``` + +In the example above you will get a list of entities back that have a +`_translations` property set. This property will contain a list of translation +data entities. For example the following properties would be accessible: + +``` text +// Outputs 'en' +echo $article->_translations['en']->locale; + +// Outputs 'title' +echo $article->_translations['en']->field; + +// Outputs 'My awesome post!' +echo $article->_translations['en']->body; +``` + +A more elegant way for dealing with this data is by adding a trait to the entity +class that is used for your table: + +``` php +use Cake\ORM\Behavior\Translate\TranslateTrait; +use Cake\ORM\Entity; + +class Article extends Entity +{ + use TranslateTrait; +} +``` + +This trait contains a single method called `translation`, which lets you +access or create new translation entities on the fly: + +``` php +// Outputs 'title' +echo $article->translation('en')->title; + +// Adds a new translation data entity to the article +$article->translation('de')->title = 'Wunderbar'; +``` + +### Limiting the Translations to be Retrieved + +You can limit the languages that are fetched from the database for a particular +set of records: + +``` php +$results = $this->Articles->find('translations', locales: ['en', 'es']); +$article = $results->first(); +$spanishTranslation = $article->translation('es'); +$englishTranslation = $article->translation('en'); +``` + +### Preventing Retrieval of Empty Translations + +Translation records can contain any string, if a record has been translated +and stored as an empty string ('') the translate behavior will take and use +this to overwrite the original field value. + +If this is undesired, you can ignore translations which are empty using the +`allowEmptyTranslations` config key: + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Translate', [ + 'fields' => ['title', 'body'], + 'allowEmptyTranslations' => false + ]); + } +} +``` + +The above would only load translated data that had content. + +### Retrieving All Translations For Associations + +It is also possible to find translations for any association in a single find +operation: + +``` php +$article = $this->Articles->find('translations')->contain([ + 'Categories' => function ($query) { + return $query->find('translations'); + } +])->first(); + +// Outputs 'Programación' +echo $article->categories[0]->translation('es')->name; +``` + +This assumes that `Categories` has the TranslateBehavior attached to it. It +simply uses the query builder function for the `contain` clause to use the +`translations` custom finder in the association. + + + +### Retrieving one language without using I18n::setLocale + +calling `I18n::setLocale('es');` changes the default locale for all translated +finds, there may be times you wish to retrieve translated content without +modifying the application's state. For these scenarios use the behavior's +`setLocale()` method: + + I18n::setLocale('en'); // reset for illustration + + // specific locale. + $this->Articles->getBehavior('Translate')->setLocale('es'); + + $article = $this->Articles->get(12); + echo $article->title; // Echoes 'Un Artículo', yay piece of cake! + +Note that this only changes the locale of the Articles table, it would not +affect the language of associated data. To affect associated data it's necessary +to call the method on each table, for example: + +``` php +I18n::setLocale('en'); // reset for illustration + +$this->Articles->getBehavior('Translate')->setLocale('es'); +$this->Articles->Categories->getBehavior('Translate')->setLocale('es'); + +$data = $this->Articles->find('all', contain: ['Categories']); +``` + +This example also assumes that `Categories` has the TranslateBehavior attached +to it. + +### Querying Translated Fields + +TranslateBehavior does not substitute find conditions by default. You need to use +`translationField()` method to compose find conditions on translated fields: + +``` php +$this->Articles->getBehavior('Translate')->setLocale('es'); +$query = $this->Articles->find()->where([ + $this->Articles->getBehavior('Translate')->translationField('title') => 'Otro Título' +]); +``` + +## Saving in Another Language + +The philosophy behind the TranslateBehavior is that you have an entity +representing the default language, and multiple translations that can override +certain fields in such entity. Keeping this in mind, you can intuitively save +translations for any given entity. For example, given the following setup: + +``` php +// in src/Model/Table/ArticlesTable.php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Translate', ['fields' => ['title', 'body']]); + } +} + +// in src/Model/Entity/Article.php +class Article extends Entity +{ + use TranslateTrait; +} + +// In the Articles Controller +$article = new Article([ + 'title' => 'My First Article', + 'body' => 'This is the content', + 'footnote' => 'Some afterwords' +]); + +$this->Articles->save($article); +``` + +So, after you save your first article, you can now save a translation for it, +there are a couple ways to do it. The first one is setting the language directly +into the entity: + +``` php +$article->_locale = 'es'; +$article->title = 'Mi primer Artículo'; + +$this->Articles->save($article); +``` + +After the entity has been saved, the translated field will be persisted as well, +one thing to note is that values from the default language that were not +overridden will be preserved: + +``` text +// Outputs 'This is the content' +echo $article->body; + +// Outputs 'Mi primer Artículo' +echo $article->title; +``` + +Once you override the value, the translation for that field will be saved and +can be retrieved as usual: + +``` php +$article->body = 'El contendio'; +$this->Articles->save($article); +``` + +The second way to use for saving entities in another language is to set the +default language directly to the table: + +``` php +$article->title = 'Mi Primer Artículo'; + +$this->Articles->getBehavior('Translate')->setLocale('es'); +$this->Articles->save($article); +``` + +Setting the language directly in the table is useful when you need to both +retrieve and save entities for the same language or when you need to save +multiple entities at once. + + + +## Saving Multiple Translations + +It is a common requirement to be able to add or edit multiple translations to +any database record at the same time. This can be done using the +`TranslateTrait`: + +``` php +use Cake\ORM\Behavior\Translate\TranslateTrait; +use Cake\ORM\Entity; + +class Article extends Entity +{ + use TranslateTrait; +} +``` + +Now, You can populate translations before saving them: + +``` php +$translations = [ + 'fr' => ['title' => "Un article"], + 'es' => ['title' => 'Un artículo'], +]; + +foreach ($translations as $lang => $data) { + $article->translation($lang)->set($data, ['guard' => false]); +} + +$this->Articles->save($article); +``` + +And create form controls for your translated fields: + +``` php +// In a view template. +Form->create($article); ?> +
    + French + Form->control('_translations.fr.title'); ?> + Form->control('_translations.fr.body'); ?> +
    +
    + Spanish + Form->control('_translations.es.title'); ?> + Form->control('_translations.es.body'); ?> +
    +``` + +In your controller, you can marshal the data as normal: + +``` php +$article = $this->Articles->newEntity($this->request->getData()); +$this->Articles->save($article); +``` + +This will result in your article, the french and spanish translations all being +persisted. You'll need to remember to add `_translations` into the +`$_accessible` fields of your entity as well. + +### Validating Translated Entities + +When attaching `TranslateBehavior` to a model, you can define the validator +that should be used when translation records are created/modified by the +behavior during `newEntity()` or `patchEntity()`: + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Translate', [ + 'fields' => ['title'], + 'validator' => 'translated', + ]); + } +} +``` + +The above will use the validator created by `validationTranslated` to +validated translated entities. diff --git a/docs/en/orm/behaviors/tree.md b/docs/en/orm/behaviors/tree.md new file mode 100644 index 0000000000..177ec2d962 --- /dev/null +++ b/docs/en/orm/behaviors/tree.md @@ -0,0 +1,376 @@ +# Tree + +`class` Cake\\ORM\\Behavior\\**TreeBehavior** + +It's fairly common to want to store hierarchical data in a database +table. Examples of such data might be categories with unlimited +subcategories, data related to a multilevel menu system or a +literal representation of hierarchy such as departments in a company. + +Relational databases are usually not well suited for storing and retrieving this +type of data, but there are a few known techniques that can make them effective +for working with multi-level information. + +The TreeBehavior helps you maintain a hierarchical data structure in the +database that can be queried without much overhead and helps reconstruct the +tree data for finding and displaying processes. + +## Requirements + +This behavior requires the following columns in your table: + +- `parent_id` (nullable) The column holding the ID of the parent row. This column should be indexed. +- `lft` (integer, signed) Used to maintain the tree structure. This column should be indexed. +- `rght` (integer, signed) Used to maintain the tree structure. + +You can configure the name of those fields should you need to customize them. +More information on the meaning of the fields and how they are used can be found +in this article describing the [MPTT logic](https://www.sitepoint.com/hierarchical-data-database-2/) + +> [!WARNING] +> The TreeBehavior is not safe for concurrent write operations. +> Simultaneous requests that modify tree-structured data +> (e.g., insertions, deletions, or moves) can lead to corruption of the +> `lft` and `rght` values. +> +> To prevent this, a locking mechanism like a +> [Semaphore](https://www.php.net/manual/en/book.sem.php) should be used. + +> [!WARNING] +> The TreeBehavior does not support composite primary keys at this point in +> time. + +## A Quick Tour + +You enable the Tree behavior by adding it to the Table you want to store +hierarchical data in: + +``` php +class CategoriesTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Tree'); + } +} +``` + +Once added, you can let CakePHP build the internal structure if the table is +already holding some rows: + +``` php +// In a controller +$categories = $this->getTableLocator()->get('Categories'); +$categories->recover(); +``` + +You can verify it works by getting any row from the table and asking for the +count of descendants it has: + +``` php +$node = $categories->get(1); +echo $categories->getBehavior('Tree')->childCount($node); +``` + +### Getting direct descendents + +Getting a flat list of the descendants for a node can be done with: + +``` php +$descendants = $categories->find('children', for: 1); + +foreach ($descendants as $category) { + echo $category->name . "\n"; +} +``` + +If you need to pass conditions you do so as per normal: + +``` php +$descendants = $categories + ->find('children', for: 1) + ->where(['name LIKE' => '%Foo%']) + ->all(); + +foreach ($descendants as $category) { + echo $category->name . "\n"; +} +``` + +If you instead need a threaded list, where children for each node are nested +in a hierarchy, you can stack the 'threaded' finder: + +``` php +$children = $categories + ->find('children', for: 1) + ->find('threaded') + ->toArray(); + +foreach ($children as $child) { + echo "{$child->name} has " . count($child->children) . " direct children"; +} +``` + +While, if you're using custom `parent_id` you need to pass it in the +'threaded' finder option (i.e. `parentField`) . + +> [!NOTE] +> For more information on 'threaded' finder options see [Finding Threaded Data logic](../../orm/retrieving-data-and-resultsets#finding-threaded-data) + +### Getting formatted tree lists + +Traversing threaded results usually requires recursive functions in, but if you +only require a result set containing a single field from each level so you can +display a list, in an HTML select for example, it is better to use the +`treeList` finder: + +``` php +$list = $categories->find('treeList')->toArray(); + +// In a CakePHP template file: +echo $this->Form->control('categories', ['options' => $list]); + +// Or you can output it in plain text, for example in a CLI script +foreach ($list as $categoryName) { + echo $categoryName . "\n"; +} +``` + +The output will be similar to: + + My Categories + _Fun + __Sport + ___Surfing + ___Skating + _Trips + __National + __International + +The `treeList` finder takes a number of options: + +- `keyPath`: A dot separated path to fetch the field to use for the array key, + or a closure to return the key out of the provided row. +- `valuePath`: A dot separated path to fetch the field to use for the array + value, or a closure to return the value out of the provided row. +- `spacer`: A string to be used as prefix for denoting the depth in the tree + for each item + +An example of all options in use is: + +``` php +$query = $categories->find('treeList', + keyPath: 'url', + valuePath: 'id', + spacer: ' ' +); +``` + +An example using closure: + +``` php +$query = $categories->find('treeList', + valuePath: function($entity){ + return $entity->url . ' ' . $entity->id + } +); +``` + +### Finding a path or branch in the tree + +One very common task is to find the tree path from a particular node to the root +of the tree. This is useful, for example, for adding the breadcrumbs list for +a menu structure: + +``` php +$nodeId = 5; +$crumbs = $categories->find('path', for: $nodeId)->all(); + +foreach ($crumbs as $crumb) { + echo $crumb->name . ' > '; +} +``` + +Trees constructed with the TreeBehavior cannot be sorted by any column other +than `lft`, this is because the internal representation of the tree depends on +this sorting. Luckily, you can reorder the nodes inside the same level without +having to change their parent: + +``` php +$node = $categories->get(5); + +// Move the node so it shows up one position up when listing children. +$categories->getBehavior('Tree')->moveUp($node); + +// Move the node to the top of the list inside the same level. +$categories->getBehavior('Tree')->moveUp($node, true); + +// Move the node to the bottom. +$categories->getBehavior('Tree')->moveDown($node, true); +``` + +## Configuration + +If the default column names that are used by this behavior don't match your own +schema, you can provide aliases for them: + +``` php +public function initialize(array $config): void +{ + $this->addBehavior('Tree', [ + 'parent' => 'ancestor_id', // Use this instead of parent_id + 'left' => 'tree_left', // Use this instead of lft + 'right' => 'tree_right' // Use this instead of rght + ]); +} +``` + +### Node Level (Depth) + +Knowing the depth of tree nodes can be useful when you want to retrieve nodes +only up to a certain level, for example, when generating menus. You can use the +`level` option to specify the field that will save level of each node: + +``` php +$this->addBehavior('Tree', [ + 'level' => 'level', // Defaults to null, i.e. no level saving +]); +``` + +If you don't want to cache the level using a db field you can use +`TreeBehavior::getLevel()` method to get level of a node. + +### Scoping and Multi Trees + +Sometimes you want to persist more than one tree structure inside the same +table, you can achieve that by using the 'scope' configuration. For example, in +a locations table you may want to create one tree per country: + +``` php +class LocationsTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Tree', [ + 'scope' => ['country_name' => 'Brazil'] + ]); + } +} +``` + +In the previous example, all tree operations will be scoped to only the rows +having the column `country_name` set to 'Brazil'. You can change the scoping +on the fly by using the 'config' function: + +``` php +$this->getBehavior('Tree')->setConfig('scope', ['country_name' => 'France']); +``` + +Optionally, you can have a finer grain control of the scope by passing a closure +as the scope: + +``` php +$this->getBehavior('Tree')->setConfig('scope', function ($query) { + $country = $this->getConfigureContry(); // A made-up function + return $query->where(['country_name' => $country]); +}); +``` + +### Deletion Behavior + +By enabling the `cascadeCallbacks` option, `TreeBehavior` will load all of +the entities that are going to be deleted. Once loaded, these entities will be +deleted individually using `Table::delete()`. This enables ORM callbacks to be +fired when tree nodes are deleted: + +``` php +$this->addBehavior('Tree', [ + 'cascadeCallbacks' => true, +]); +``` + +## Recovering with custom sort field + +By default, `recover()` sorts the items using the primary key. This works great +if this is a numeric (auto increment) column, but can lead to weird results if you +use UUIDs. + +If you need custom sorting for the recovery, you can set a +custom order clause in your config: + +``` php +$this->addBehavior('Tree', [ + 'recoverOrder' => ['country_name' => 'DESC'], +]); +``` + +## Saving Hierarchical Data + +When using the Tree behavior, you usually don't need to worry about the +internal representation of the hierarchical structure. The positions where nodes +are placed in the tree are deduced from the `parent_id` column in each of your +entities: + +``` php +$aCategory = $categoriesTable->get(10); +$aCategory->parent_id = 5; +$categoriesTable->save($aCategory); +``` + +Providing inexistent parent ids when saving or attempting to create a loop in +the tree (making a node child of itself) will throw an exception. + +You can make a node into a root in the tree by setting the `parent_id` column to +null: + +``` php +$aCategory = $categoriesTable->get(10); +$aCategory->parent_id = null; +$categoriesTable->save($aCategory); +``` + +Children for the new root node will be preserved. + +## Deleting Nodes + +Deleting a node and all its sub-tree (any children it may have at any depth in +the tree) is trivial: + +``` php +$aCategory = $categoriesTable->get(10); +$categoriesTable->delete($aCategory); +``` + +The TreeBehavior will take care of all internal deleting operations for you. It +is also possible to only delete one node and re-assign all children to the +immediately superior parent node in the tree: + +``` php +$aCategory = $categoriesTable->get(10); +$categoriesTable->getBehavior('Tree')->removeFromTree($aCategory); +$categoriesTable->delete($aCategory); +``` + +All children nodes will be kept and a new parent will be assigned to them. + +The deletion of a node is based off of the `lft` and `rght` values of the entity. This +is important to note when looping through the various children of a node for +conditional deletes: + +``` php +$descendants = $teams->find('children', for: 1)->all(); + +foreach ($descendants as $descendant) { + $team = $teams->get($descendant->id); // search for the up-to-date entity object + if ($team->expired) { + $teams->delete($team); // deletion reorders the lft and rght of database entries + } +} +``` + +TreeBehavior will reorder the `lft` and `rght` values of records in the +table when a node is deleted. + +In our example above, the `lft` and `rght` values of the entities inside +`$descendants` will be inaccurate. You will need to reload existing entity +objects if you need an accurate shape of the tree. diff --git a/docs/en/orm/database-basics.md b/docs/en/orm/database-basics.md new file mode 100644 index 0000000000..852267223b --- /dev/null +++ b/docs/en/orm/database-basics.md @@ -0,0 +1,1252 @@ +# Database Basics + +The CakePHP database access layer abstracts and provides help with most aspects +of dealing with relational databases such as, keeping connections to the server, +building queries, preventing SQL injections, inspecting and altering schemas, +and with debugging and profiling queries sent to the database. + +## Quick Tour + +The functions described in this chapter illustrate what is possible to do with +the lower-level database access API. If instead you want to learn more about the +complete ORM, you can read the [Query Builder](../orm/query-builder) and +[Table Objects](../orm/table-objects) sections. + +The easiest way to create a database connection is using a `DSN` string: + +``` php +use Cake\Datasource\ConnectionManager; + +$dsn = 'mysql://root:password@localhost/my_database'; +ConnectionManager::setConfig('default', ['url' => $dsn]); +``` + +Once created, you can access the connection object to start using it: + +``` php +$connection = ConnectionManager::get('default'); +``` + +> [!NOTE] +> For supported databases, see [installation notes](../installation). + + + +### Running Select Statements + +Running raw SQL queries is a breeze: + +``` php +use Cake\Datasource\ConnectionManager; + +$connection = ConnectionManager::get('default'); +$results = $connection->execute('SELECT * FROM articles')->fetchAll('assoc'); +``` + +You can use prepared statements to insert parameters: + +``` php +$results = $connection + ->execute('SELECT * FROM articles WHERE id = :id', ['id' => 1]) + ->fetchAll('assoc'); +``` + +It is also possible to use complex data types as arguments: + +``` php +use Cake\Datasource\ConnectionManager; +use DateTime; + +$connection = ConnectionManager::get('default'); +$results = $connection + ->execute( + 'SELECT * FROM articles WHERE created >= :created', + ['created' => new DateTime('1 day ago')], + ['created' => 'datetime'] + ) + ->fetchAll('assoc'); +``` + +Instead of writing the SQL manually, you can use the query builder: + +``` php +// Prior to 4.5 use $connection->query() instead. +$results = $connection + ->selectQuery('*', 'articles') + ->where(['created >' => new DateTime('1 day ago')], ['created' => 'datetime']) + ->orderBy(['title' => 'DESC']) + ->execute() + ->fetchAll('assoc'); +``` + +### Running Insert Statements + +Inserting rows in the database is usually a matter of a couple lines: + +``` php +use Cake\Datasource\ConnectionManager; +use DateTime; + +$connection = ConnectionManager::get('default'); +$connection->insert('articles', [ + 'title' => 'A New Article', + 'created' => new DateTime('now') +], ['created' => 'datetime']); +``` + +### Running Update Statements + +Updating rows in the database is equally intuitive, the following example will +update the article with **id** 10: + +``` php +use Cake\Datasource\ConnectionManager; +$connection = ConnectionManager::get('default'); +$connection->update('articles', ['title' => 'New title'], ['id' => 10]); +``` + +### Running Delete Statements + +Similarly, the `delete()` method is used to delete rows from the database, the +following example deletes the article with **id** 10: + +``` php +use Cake\Datasource\ConnectionManager; +$connection = ConnectionManager::get('default'); +$connection->delete('articles', ['id' => 10]); +``` + + + +## Configuration + +By convention database connections are configured in **config/app.php**. The +connection information defined in this file is fed into +`Cake\Datasource\ConnectionManager` creating the connection configuration +your application will be using. Sample connection information can be found in +**config/app.default.php**. A sample connection configuration would look +like: + +``` php +'Datasources' => [ + 'default' => [ + 'className' => 'Cake\Database\Connection', + 'driver' => 'Cake\Database\Driver\Mysql', + 'persistent' => false, + 'host' => 'localhost', + 'username' => 'my_app', + 'password' => 'secret', + 'database' => 'my_app', + 'encoding' => 'utf8mb4', + 'timezone' => 'UTC', + 'cacheMetadata' => true, + ], +], +``` + +The above will create a 'default' connection, with the provided parameters. You +can define as many connections as you want in your configuration file. You can +also define additional connections at runtime using +`Cake\Datasource\ConnectionManager::setConfig()`. An example of that +would be: + +``` php +use Cake\Datasource\ConnectionManager; + +ConnectionManager::setConfig('default', [ + 'className' => 'Cake\Database\Connection', + 'driver' => 'Cake\Database\Driver\Mysql', + 'persistent' => false, + 'host' => 'localhost', + 'username' => 'my_app', + 'password' => 'secret', + 'database' => 'my_app', + 'encoding' => 'utf8mb4', + 'timezone' => 'UTC', + 'cacheMetadata' => true, +]); +``` + +Configuration options can also be provided as a `DSN` string. This is +useful when working with environment variables or `PaaS` providers: + +``` css +ConnectionManager::setConfig('default', [ + 'url' => 'mysql://my_app:sekret@localhost/my_app?encoding=utf8&timezone=UTC&cacheMetadata=true', +]); +``` + +When using a DSN string you can define any additional parameters/options as +query string arguments. + +By default, all Table objects will use the `default` connection. To +use a non-default connection, see [Configuring Table Connections](../orm/table-objects#configuring-table-connections). + +There are a number of keys supported in database configuration. A full list is +as follows: + +className +The fully namespaced class name of the class that represents the connection to a database server. +This class is responsible for loading the database driver, providing SQL +transaction mechanisms and preparing SQL statements among other things. + +driver +The class name of the driver used to implement all specificities for +a database engine. This can either be a short classname using `plugin syntax`, +a fully namespaced name, or a constructed driver instance. +Examples of short classnames are Mysql, Sqlite, Postgres, and Sqlserver. + +persistent +Whether or not to use a persistent connection to the database. This option +is not supported by SqlServer. An exception is thrown if you attempt to set +`persistent` to `true` with SqlServer. + +host +The database server's hostname (or IP address). + +username +The username for the account. + +password +The password for the account. + +database +The name of the database for this connection to use. Avoid using `.` in +your database name. Because of how it complicates identifier quoting CakePHP +does not support `.` in database names. The path to your SQLite database +should be an absolute path (for example, `ROOT . DS . 'my_app.db'`) to avoid +incorrect paths caused by relative paths. + +port (*optional*) +The TCP port or Unix socket used to connect to the server. + +encoding +Indicates the character set to use when sending SQL statements to +the server. This defaults to the database's default encoding for +all databases other than DB2. + +timezone +Server timezone to set. + +schema +Used in PostgreSQL database setups to specify which schema to use. + +unix_socket +Used by drivers that support it to connect via Unix socket files. If you are +using PostgreSQL and want to use Unix sockets, leave the host key blank. + +ssl_key +The file path to the SSL key file. (Only supported by MySQL). + +ssl_cert +The file path to the SSL certificate file. (Only supported by MySQL). + +ssl_ca +The file path to the SSL certificate authority. (Only supported by MySQL). + +init +A list of queries that should be sent to the database server as +when the connection is created. + +log +Set to `true` to enable query logging. When enabled queries will be logged +at a `debug` level with the `queriesLog` scope. + +quoteIdentifiers +Set to `true` if you are using reserved words or special characters in +your table or column names. Enabling this setting will result in queries +built using the [Query Builder](../orm/query-builder) having identifiers quoted when +creating SQL. It should be noted that this decreases performance because +each query needs to be traversed and manipulated before being executed. + +flags +An associative array of PDO constants that should be passed to the +underlying PDO instance. See the PDO documentation for the flags supported +by the driver you are using. + +cacheMetadata +Either boolean `true`, or a string containing the cache configuration to +store meta data in. Having metadata caching disabled by setting it to `false` +is not advised and can result in very poor performance. See the +[Database Metadata Cache](#database-metadata-cache) section for more information. + +mask +Set the permissions on the generated database file. (Only supported by SQLite) + +cache +The `cache` flag to send to SQLite. + +mode +The `mode` flag value to send to SQLite. + +At this point, you might want to take a look at the +[CakePHP Conventions](../intro/conventions). The correct naming for your tables (and the addition +of some columns) can score you some free functionality and help you avoid +configuration. For example, if you name your database table big_boxes, your +table BigBoxesTable, and your controller BigBoxesController, everything will +work together automatically. By convention, use underscores, lower case, and +plural forms for your database table names - for example: bakers, +pastry_stores, and savory_cakes. + +> [!NOTE] +> If your MySQL server is configured with `skip-character-set-client-handshake` +> then you MUST use the `flags` config to set your charset encoding. For example: +> +> ``` php +> 'flags' => [\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'] +> ``` + + + +## Read and Write Connections + +Connections can have separate read and write roles. Read +roles are expected to represent read-only replicas and write roles are expected +to be the default connection and support write operations. + +Read roles are configured by providing a `read` key in the connection config. +Write roles are configured by providing a `write` key. + +Role configurations override the values in the shared connection config. If the read +and write role configurations are the same, a single connection to the database is used +for both: + +``` text +'default' => [ + 'driver' => 'mysql', + 'username' => '...', + 'password' => '...', + 'database' => '...', + 'read' => [ + 'host' => 'read-db.example.com', + ], + 'write' => [ + 'host' => 'write-db.example.com', + ] +]; +``` + +You can specify the same value for both `read` and `write` key without creating +multiple connections to the database. + +## Managing Connections + +`class` Cake\\Datasource\\**ConnectionManager** + +The `ConnectionManager` class acts as a registry to access database +connections your application has. It provides a place that other objects can get +references to existing connections. + +### Accessing Connections + +`static` Cake\\Datasource\\ConnectionManager::**get**($name): ConnectionInterface + +Once configured connections can be fetched using +`Cake\Datasource\ConnectionManager::get()`. This method will +construct and load a connection if it has not been built before, or return the +existing known connection: + +``` php +use Cake\Datasource\ConnectionManager; + +$connection = ConnectionManager::get('default'); +``` + +Attempting to load connections that do not exist will throw an exception. + +### Creating Connections at Runtime + +Using `setConfig()` and `get()` you can create new connections that are not +defined in your configuration files at runtime: + +``` php +ConnectionManager::setConfig('my_connection', $config); +$connection = ConnectionManager::get('my_connection'); +``` + +See the [Database Configuration](#database-configuration) for more information on the configuration +data used when creating connections. + +## Data Types + +`class` Cake\\Database\\**TypeFactory** + +Since not every database vendor includes the same set of data types, or +the same names for similar data types, CakePHP provides a set of abstracted +data types for use with the database layer. The types CakePHP supports are: + +string +Maps to `VARCHAR` type. In SQL Server the `NVARCHAR` types are used. + +char +Maps to `CHAR` type. In SQL Server the `NCHAR` type is used. + +text +Maps to `TEXT` types. + +uuid +Maps to the UUID type if a database provides one, otherwise this will +generate a `CHAR(36)` field. + +binaryuuid +Maps to the UUID type if the database provides one, otherwise this will +generate a `BINARY(16)` column. Binary UUIDs provide more efficient storage +compared to string UUIDs by storing the UUID as 16 bytes of binary data rather +than a 36-character string. This type automatically handles conversion between +string UUID format (with dashes) and binary format. + +nativeuuid +Maps to the UUID type in MySQL with MariaDb. In all other databases, +`nativeuuid` is an alias for `uuid`. + +integer +Maps to the `INTEGER` type provided by the database. BIT is not yet supported +at this moment. + +smallinteger +Maps to the `SMALLINT` type provided by the database. + +tinyinteger +Maps to the `TINYINT` or `SMALLINT` type provided by the database. In MySQL +`TINYINT(1)` is treated as a boolean. + +biginteger +Maps to the `BIGINT` type provided by the database. + +float +Maps to either `DOUBLE` or `FLOAT` depending on the database. The `precision` +option can be used to define the precision used. + +decimal +Maps to the `DECIMAL` type. Supports the `length` and `precision` +options. Values for decimal type ares be represented as strings (not as float +as some might expect). This is because decimal types are used to represent +exact numeric values in databases and using float type for them in PHP can +potentially lead to precision loss. + +If you want the values to be float in your PHP code then consider using +FLOAT or DOUBLE type columns in your database. Also, depending on your use +case you can explicitly map your decimal columns to float type in your table +schema. + +boolean +Maps to `BOOLEAN` except in MySQL, where `TINYINT(1)` is used to represent +booleans. `BIT(1)` is not yet supported at this moment. + +binary +Maps to the `BLOB` or `BYTEA` type provided by the database. + +date +Maps to a native `DATE` column type. The return value of this column +type is `Cake\I18n\Date` which emulates the date related +methods of PHP's `DateTime` class. + +datetime +See [Datetime Type](#datetime-type). + +datetimefractional +See [Datetime Type](#datetime-type). + +timestamp +Maps to the `TIMESTAMP` type. + +timestampfractional +Maps to the `TIMESTAMP(N)` type. + +time +Maps to a `TIME` type in all databases. + +year +Maps to the `YEAR` type. Only supported in MySQL. + +json +Maps to a `JSON` type if it's available, otherwise it maps to `TEXT`. + +enum +See [Enum Type](#enum-type). + +geometry +Maps to a generic geometry storage type. + +point +Maps to a single point in geospatial storage. + +linestring +Maps to a single line in geospatial storage. + +polygon +Maps to a single polygon in geospatial storage. + +inet +Maps to the `INET` type. Only implemented in postgres. + +cidr +Maps to the `CIDR` type. Only implemented in postgres. + +macaddr +Maps to the `MACADDR` type. Only implemented in postgres. + +These types are used in both the schema reflection features that CakePHP +provides, and schema generation features CakePHP uses when using test fixtures. + +Each type can also provide translation functions between PHP and SQL +representations. These methods are invoked based on the type hints provided when +doing queries. For example a column that is marked as 'datetime' will +automatically convert input parameters from `DateTime` instances into a +timestamp or formatted datestrings. Likewise, 'binary' columns will accept file +handles, and generate file handles when reading data. + +::: info Changed in version 5.1.0 +The `geometry`, `point`, `linestring`, and `polygon` types were added. +::: + +::: info Changed in version 5.2.0 +The `nativeuuid` type was added. +::: + +::: info Added in version 5.3.0 +The `inet`, `cidr`, `macaddr`, and `year` types were added. +::: + + + +### DateTime Type + +`class` Cake\\Database\\**DateTimeType** + +Maps to a native `DATETIME` column type. In PostgreSQL and SQL Server this +turns into a `TIMESTAMP` type. The default return value of this column type is +`Cake\I18n\DateTime` which extends [Chronos](https://github.com/cakephp/chronos) and the native `DateTimeImmutable`. + +`method` Cake\\Database\\DateTimeType::**setTimezone**(string|\\DateTimeZone|null $timezone) + +If your database server's timezone does not match your application's PHP timezone +then you can use this method to specify your database's timezone. This timezone +will then used when converting PHP objects to database's datetime string and +vice versa. + +`class` Cake\\Database\\**DateTimeFractionalType** + +Can be used to map datetime columns that contain microseconds such as +`DATETIME(6)` in MySQL. To use this type you need to add it as a mapped type: + +``` php +// in config/bootstrap.php +use Cake\Database\TypeFactory; +use Cake\Database\Type\DateTimeFractionalType; + +// Overwrite the default datetime type with a more precise one. +TypeFactory::map('datetime', DateTimeFractionalType::class); +``` + +`class` Cake\\Database\\**DateTimeTimezoneType** + +Can be used to map datetime columns that contain time zones such as +`TIMESTAMPTZ` in PostgreSQL. To use this type you need to add it as a mapped type: + +``` php +// in config/bootstrap.php +use Cake\Database\TypeFactory; +use Cake\Database\Type\DateTimeTimezoneType; + +// Overwrite the default datetime type with a more precise one. +TypeFactory::map('datetime', DateTimeTimezoneType::class); +``` + + + +### Enum Type + +`class` Cake\\Database\\**EnumType** + +Maps a [BackedEnum](https://www.php.net/manual/en/language.enumerations.backed.php) to a string or integer column. +To use this type you need to specify which column is associated to which BackedEnum inside the table class: + +``` php +use App\Model\Enum\ArticleStatus; +use Cake\Database\Type\EnumType; + +// in src/Model/Table/ArticlesTable.php +public function initialize(array $config): void +{ + parent::initialize($config); + + $this->getSchema()->setColumnType('status', EnumType::from(ArticleStatus::class)); +} +``` + +A simple `ArticleStatus` could look like: + +``` php +namespace App\Model\Enum; + +enum ArticleStatus: string +{ + case Published = 'Y'; + case Unpublished = 'N'; +} +``` + +CakePHP also provides the `EnumLabelInterface` which can be implemented by +Enums that want to provide a map of human-readable labels: + +``` php +namespace App\Model\Enum; + +use Cake\Database\Type\EnumLabelInterface; + +enum ArticleStatus: string implements EnumLabelInterface +{ + case Published = 'Y'; + case Unpublished = 'N'; + + public static function label(): string + { + return match ($this) { + self::Published => __('Published'), + self::Unpublished => __('Unpublished'), + }; + } +} +``` + +This can be useful if you want to use your enums in `FormHelper` select +inputs. You can use [bake](../bake) to generate an enum class: + +``` text +# generate an enum class with two cases and stored as an integer +bin/cake bake enum UserStatus inactive:0,active:1 -i + +# generate an enum class with two cases as a string +bin/cake bake enum UserStatus published:Y,unpublished:N +``` + +CakePHP recommends a few conventions for enums: + +- Enum classnames should follow `{Entity}{ColumnName}` style to enable + detection while running bake and to aid with project consistency. +- Enum cases should use CamelCase style. +- Enums should implement the `Cake\Database\Type\EnumLabelInterface` to + improve compatibility with bake, and `FormHelper`. + +### Geospatial Types + +The `geometry`, `point`, `linestring`, and `polygon` types are also known +as the "geospatial types". CakePHP offers limited support for geospatial +columns. Currently they can be defined in migrations, read in schema reflection, +and have values set as text. + +::: info Added in version 5.1.0 +Geospatial schema types were added. +::: + + + +### Adding Custom Types + +`class` Cake\\Database\\**TypeFactory** + +`static` Cake\\Database\\TypeFactory::**map**($name, $class): void + +If you need to use vendor specific types that are not built into CakePHP you can +add additional new types to CakePHP's type system. Type classes are expected to +implement the following methods: + +- `toPHP`: Casts given value from a database type to a PHP equivalent. +- `toDatabase`: Casts given value from a PHP type to one acceptable by a database. +- `toStatement`: Casts given value to its Statement equivalent. +- `marshal`: Marshals flat data into PHP objects. + +To fulfill the basic interface, extend `Cake\Database\Type`. +For example if we wanted to add a PointMutation type, we could make the following type +class: + +``` php +// in src/Database/Type/PointMutationType.php + +namespace App\Database\Type; + +use Cake\Database\Driver; +use Cake\Database\Type\BaseType; +use PDO; + +class PointMutationType extends BaseType +{ + public function toPHP(mixed $value, Driver $driver): mixed + { + if ($value === null) { + return null; + } + + return $this->pmDecode($valu + + public function marshal(mixed $value): mixed + { + if (is_array($value) || $value === null) { + return $value; + } + + return $this->pmDecode($value); + } + + public function toDatabase(mixed $value, Driver $driver): mixed + { + return sprintf('%d%s>%s', $value['position'], $value['from'], $value['to']); + } + + public function toStatement(mixed $value, Driver $driver): int + { + if ($value === null) { + return PDO::PARAM_NULL; + } + + return PDO::PARAM_STR; + } + + protected function pmDecode(mixed $value): mixed + { + if (preg_match('/^(\d+)([a-zA-Z])>([a-zA-Z])$/', $value, $matches)) { + return [ + 'position' => (int) $matches[1], + 'from' => $matches[2], + 'to' => $matches[3] + ]; + } + + return null; + } +} +``` + +By default the `toStatement()` method will treat values as strings which will +work for our new type. + +### Connecting Custom Datatypes to Schema Reflection and Generation + +Once we've created our new type, we need to add it into +the type mapping. During our application bootstrap we should do the following: + +``` php +use Cake\Database\TypeFactory; + +TypeFactory::map('point_mutation', \App\Database\Type\PointMutationType:class); +``` + +We then have two ways to use our datatype in our models. + +1. The first path is to overwrite the reflected schema data to use our new type. +2. The second is to implement `Cake\Database\Type\ColumnSchemaAwareInterface` + and define the SQL column type and reflection logic. + +Overwriting the reflected schema with our custom type will enable CakePHP's +database layer to automatically convert PointMutation data when creating queries. In your +Table's [getSchema() method](../orm/saving-data#saving-complex-types) add the + +following: + +``` php +class WidgetsTable extends Table +{ + public function initialize(array $config): void + { + return parent::getSchema()->setColumnType('mutation', 'point_mutation'); + + } +} +``` + +Implementing `ColumnSchemaAwareInterface` gives you more control over +custom datatypes. This avoids overwriting schema definitions if your +datatype has an unambiguous SQL column definition. For example, we could have +our PointMutation type be used anytime a `TEXT` column with a specific comment is +used: + +``` php +// in src/Database/Type/PointMutationType.php + +namespace App\Database\Type; + +use Cake\Database\Driver; +use Cake\Database\Type\BaseType; +use Cake\Database\Type\ColumnSchemaAwareInterface; +use Cake\Database\Schema\TableSchemaInterface; +use PDO; + +class PointMutationType extends BaseType + implements ColumnSchemaAwareInterface +{ + // other methods from earlier + + /** + * Convert abstract schema definition into a driver specific + * SQL snippet that can be used in a CREATE TABLE statement. + * + * Returning null will fall through to CakePHP's built-in types. + */ + public function getColumnSql( + TableSchemaInterface $schema, + string $column, + Driver $driver + ): ?string { + $data = $schema->getColumn($column); + $sql = $driver->quoteIdentifier($column); + $sql .= ' JSON'; + if (isset($data['null']) && $data['null'] === false) { + $sql .= ' NOT NULL'; + } + + return $sql; + } + + /** + * Convert the column data returned from schema reflection + * into the abstract schema data. + * + * Returning null will fall through to CakePHP's built-in types. + */ + public function convertColumnDefinition( + array $definition, + Driver $driver + ): ?array { + return [ + 'type' => $this->_name, + 'length' => null, + ]; + } +``` + +The `$definition` data passed to `convertColumnDefinition()` will contain +the following keys. All keys will exist but may contain `null` if the key has +no value for the current database driver: + +- `length` The length of a column if available.. +- `precision` The precision of the column if available. +- `scale` Can be included for SQLServer connections. + + + +### Mapping Custom Datatypes to SQL Expressions + +The previous example maps a custom datatype for a 'point_mutation' column type +which is easily represented as a string in a SQL statement. Complex SQL data +types cannot be represented as strings/integers in SQL queries. When working +with these datatypes your Type class needs to implement the +`Cake\Database\Type\ExpressionTypeInterface` interface. This interface lets +your custom type represent a value as a SQL expression. As an example, we'll +build a simple Type class for handling `POINT` type data out of MySQL. First +we'll define a 'value' object that we can use to represent `POINT` data in +PHP: + +``` php +// in src/Database/Point.php +namespace App\Database; + +// Our value object is immutable. +class Point +{ + protected $_lat; + protected $_long; + + // Factory method. + public static function parse($value) + { + // Parse the WKB data from MySQL. + $unpacked = unpack('x4/corder/Ltype/dlat/dlong', $value); + + return new static($unpacked['lat'], $unpacked['long']); + } + + public function __construct($lat, $long) + { + $this->_lat = $lat; + $this->_long = $long; + } + + public function lat() + { + return $this->_lat; + } + + public function long() + { + return $this->_long; + } +} +``` + +With our value object created, we'll need a Type class to map data into this +value object and into SQL expressions: + +``` php +namespace App\Database\Type; + +use App\Database\Point; +use Cake\Database\Driver; +use Cake\Database\Expression\FunctionExpression; +use Cake\Database\ExpressionInterface; +use Cake\Database\Type\BaseType; +use Cake\Database\Type\ExpressionTypeInterface; + +class PointType extends BaseType implements ExpressionTypeInterface +{ + public function toPHP($value, Driver $d): mixed + { + return $value === null ? null : Point::parse($value); + } + + public function marshal($value): mixed + { + if (is_string($value)) { + $value = explode(',', $value); + } + if (is_array($value)) { + return new Point($value[0], $value[1]); + } + + return null; + } + + public function toExpression($value): ExpressionInterface + { + if ($value instanceof Point) { + return new FunctionExpression( + 'POINT', + [ + $value->lat(), + $value->long() + ] + ); + } + if (is_array($value)) { + return new FunctionExpression('POINT', [$value[0], $value[1]]); + } + // Handle other cases. + } + + public function toDatabase($value, Driver $driver): mixed + { + return $value; + } +} +``` + +The above class does a few interesting things: + +- The `toPHP` method handles parsing the SQL query results into a value + object. +- The `marshal` method handles converting, data such as given request data, into our value object. + We're going to accept string values like `'10.24,12.34` and arrays for now. +- The `toExpression` method handles converting our value object into the + equivalent SQL expressions. In our example the resulting SQL would be + something like `POINT(10.24, 12.34)`. + +Once we've built our custom type, we'll need to [connect our type +to our table class](../orm/saving-data#saving-complex-types). + +## Connection Classes + +`class` Cake\\Database\\**Connection** + +Connection classes provide a simple interface to interact with database +connections in a consistent way. They are intended as a more abstract interface to +the driver layer and provide features for executing queries, logging queries, and doing +transactional operations. + + + +### Executing Queries + +`method` Cake\\Database\\Connection::**execute**(string $sql, array $params = [], array $types = []): StatementInterface + +Once you've gotten a connection object, you'll probably want to issue some +queries with it. CakePHP's database abstraction layer provides wrapper features +on top of PDO and native drivers. These wrappers provide a similar interface to +PDO. There are a few different ways you can run queries depending on the type of +query you need to run and what kind of results you need back. The most basic +method is `execute()` which allows you to run complet SQL queries: + +``` php +$statement = $connection->execute('UPDATE articles SET published = 1 WHERE id = 2'); +``` + +For parameterized queries use the 2nd argument: + +``` php +$statement = $connection->execute( + 'UPDATE articles SET published = ? WHERE id = ?', + [1, 2] +); +``` + +Without any type hinting information, `execute` will assume all placeholders +are string values. If you need to bind specific types of data, you can use their +abstract type names when creating a query: + +``` php +$statement = $connection->execute( + 'UPDATE articles SET published_date = ? WHERE id = ?', + [new DateTime('now'), 2], + ['date', 'integer'] +); +``` + +`method` Cake\\Database\\Connection::**selectQuery**(): SelectQuery + +These methods allow you to use rich data types in your applications and properly convert +them into SQL statements. The last and most flexible way of creating queries is +to use the [Query Builder](../orm/query-builder). This approach allows you to build complex and +expressive queries without having to use platform specific SQL. When using the +query builder, no SQL will be sent to the database server until the `execute()` +method is called, or the query is iterated. Iterating a query will first execute +it and then start iterating over the result set: + +``` php +$query = $connection->selectQuery(); +$query->select('*') + ->from('articles') + ->where(['published' => true]); + +foreach ($query as $row) { + // Do something with the row. +} +``` + +> [!NOTE] +> Instead of iterating the `$query` you can also call it's `all()` method +> to get the results. + +`method` Cake\\Database\\Connection::**updateQuery**(): UpdateQuery + +This method provides you a builder for `UPDATE` queries: + +``` php +$query = $connection->updateQuery('articles') + ->set(['published' => true]) + ->where(['id' => 2]); +$statement = $query->execute(); +``` + +`method` Cake\\Database\\Connection::**insertQuery**(): InsertQuery + +This method provides you a builder for `INSERT` queries: + +``` php +$query = $connection->insertQuery(); +$query->into('articles') + ->columns(['title']) + ->values(['1st article']); +$statement = $query->execute(); +``` + +`method` Cake\\Database\\Connection::**deleteQuery**(): DeleteQuery + +This method provides you a builder for `DELETE` queries: + +``` php +$query = $connection->deleteQuery(); +$query->delete('articles') + ->where(['id' => 2]); +$statement = $query->execute(); +``` + +### Using Transactions + +The connection objects provide you a few simple ways you do database +transactions. The most basic way of doing transactions is through the `begin()`, +`commit()` and `rollback()` methods, which map to their SQL equivalents: + +``` php +$connection->begin(); +$connection->execute('UPDATE articles SET published = ? WHERE id = ?', [true, 2]); +$connection->execute('UPDATE articles SET published = ? WHERE id = ?', [false, 4]); +$connection->commit(); +``` + +`method` Cake\\Database\\Connection::**transactional**(callable $callback): mixed + +In addition to this interface connection instances also provide the +`transactional()` method which makes handling the begin/commit/rollback calls +much simpler: + +``` php +$connection->transactional(function ($connection) { + $connection->execute('UPDATE articles SET published = ? WHERE id = ?', [true, 2]); + $connection->execute('UPDATE articles SET published = ? WHERE id = ?', [false, 4]); +}); +``` + +In addition to basic queries, you can execute more complex queries using either +the [Query Builder](../orm/query-builder) or [Table Objects](../orm/table-objects). The transactional method will +do the following: + +- Call `begin`. +- Call the provided closure. +- If the closure raises an exception, a rollback will be issued. The original + exception will be re-thrown. +- If the closure returns `false`, a rollback will be issued. +- If the closure executes successfully, the transaction will be committed. + +## Interacting with Statements + +When using the lower level database API, you will often encounter statement +objects. These objects allow you to manipulate the underlying prepared statement +from the driver. After creating and executing a query object, or using +`execute()` you will have a `StatementInterface` instance. + +### Executing & Fetching Rows + +Once a query is executed using `execute()`, results can be fetched using +`fetch()`, `fetchAll()`: + +``` php +$statement->execute(); + +// Read one row. +$row = $statement->fetch('assoc'); + +// Read all rows. +$rows = $statement->fetchAll('assoc'); +``` + +### Getting affected Row Counts + +After executing a statement, you can fetch the number of affected rows: + +``` php +$rowCount = $statement->rowCount(); +``` + +### Checking Error Codes + +If your query was not successful, you can get related error information +using the `errorCode()` and `errorInfo()` methods. These methods work the +same way as the ones provided by PDO: + +``` php +$code = $statement->errorCode(); +$info = $statement->errorInfo(); +``` + + + +## Query Logging + +Query logging can be enabled when configuring your connection by setting the +`log` option to `true`. + +When query logging is enabled, queries will be logged to +`Cake\Log\Log` using the 'debug' level, and the 'queriesLog' scope. +You will need to have a logger configured to capture this level & scope. Logging +to `stderr` can be useful when working on unit tests, and logging to +files/syslog can be useful when working with web requests: + +``` php +use Cake\Log\Log; + +// Console logging +Log::setConfig('queries', [ + 'className' => 'Console', + 'stream' => 'php://stderr', + 'scopes' => ['queriesLog'] +]); + +// File logging +Log::setConfig('queries', [ + 'className' => 'File', + 'path' => LOGS, + 'file' => 'queries.log', + 'scopes' => ['queriesLog'] +]); +``` + +> [!NOTE] +> Query logging is only intended for debugging/development uses. You should +> never leave query logging on in production as it will negatively impact the +> performance of your application. + + + +## Identifier Quoting + +By default CakePHP does **not** quote identifiers in generated SQL queries. The +reason for this is identifier quoting has a few drawbacks: + +- Performance overhead - Quoting identifiers is much slower and complex than not doing it. +- Not necessary in most cases - In non-legacy databases that follow CakePHP's + conventions there is no reason to quote identifiers. + +If you are using a legacy schema that requires identifier quoting you can enable +it using the `quoteIdentifiers` setting in your +[Database Configuration](#database-configuration). You can also enable this feature at runtime: + +``` php +$connection->getDriver()->enableAutoQuoting(); +``` + +When enabled, identifier quoting will cause additional query traversal that +converts all identifiers into `IdentifierExpression` objects. + +> [!NOTE] +> SQL snippets contained in QueryExpression objects will not be modified. + + + +## Metadata Caching + +CakePHP's ORM uses database reflection to determine the schema, indexes and +foreign keys your application contains. Because this metadata changes +infrequently and can be expensive to access, it is typically cached. By default, +metadata is stored in the `_cake_model_` cache configuration. You can define +a custom cache configuration using the `cacheMetatdata` option in your +datasource configuration: + +``` text +'Datasources' => [ + 'default' => [ + // Other keys go here. + + // Use the 'orm_metadata' cache config for metadata. + 'cacheMetadata' => 'orm_metadata', + ] +], +``` + +You can also configure the metadata caching at runtime with the +`cacheMetadata()` method: + +``` php +// Disable the cache +$connection->cacheMetadata(false); + +// Enable the cache +$connection->cacheMetadata(true); + +// Use a custom cache config +$connection->cacheMetadata('orm_metadata'); +``` + +CakePHP also includes a CLI tool for managing metadata caches. See the +[Schema Cache Tool](../console-commands/schema-cache) chapter for more information. + +## Creating Databases + +If you want to create a connection without selecting a database you can omit +the database name: + +``` php +$dsn = 'mysql://root:password@localhost/'; +ConnectionManager::setConfig('setup', ['url' => $dsn]); +``` + +You can now use your connection object to execute queries that create/modify +databases. For example to create a database: + +``` php +$connection = ConnectionManager::get('setup'); +$connection->execute("CREATE DATABASE IF NOT EXISTS my_database"); +``` + +> [!NOTE] +> When creating a database it is a good idea to set the character set and +> collation parameters (e.g. `DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci`). +> If these values are missing, the database will set whatever system default values it uses. diff --git a/docs/en/orm/deleting-data.md b/docs/en/orm/deleting-data.md new file mode 100644 index 0000000000..81bb2577fe --- /dev/null +++ b/docs/en/orm/deleting-data.md @@ -0,0 +1,133 @@ +# Deleting Data + +`class` Cake\\ORM\\**Table** + +## Deleting a Single Entity + +`method` Cake\\ORM\\Table::**delete**(EntityInterface $entity, array $options = []): bool + +Once you've loaded an entity you can delete it by calling the originating +table's delete method: + +``` php +// In a controller. +$entity = $this->Articles->get(2); +$result = $this->Articles->delete($entity); +``` + +When deleting entities a few things happen: + +1. The [delete rules](../orm/validation#application-rules) will be applied. If the rules + fail, deletion will be prevented. +2. The `Model.beforeDelete` event is triggered. If this event is stopped, the + delete will be aborted and the event's result will be returned. +3. The entity will be deleted. +4. All dependent associations will be deleted. If associations are being deleted + as entities, additional events will be dispatched. +5. Any junction table records for BelongsToMany associations will be removed. +6. The `Model.afterDelete` event will be triggered. + +By default all deletes happen within a transaction. You can disable the +transaction with the atomic option: + +``` php +$result = $this->Articles->delete($entity, ['atomic' => false]); +``` + +The `$options` parameter supports the following options: + +- `atomic` Defaults to true. When true the deletion happens within + a transaction. +- `checkRules` Defaults to true. Check deletion rules before deleting + records. + +## Cascading Deletes + +When deleting entities, associated data can also be deleted. If your HasOne and +HasMany associations are configured as `dependent`, delete operations will +'cascade' to those entities as well. By default entities in associated tables +are removed using `Cake\ORM\Table::deleteAll()`. You can elect to +have the ORM load related entities, and delete them individually by setting the +`cascadeCallbacks` option to `true`. A sample HasMany association with both +these options enabled would be: + +``` php +// In a Table's initialize method. +$this->hasMany('Comments', [ + 'dependent' => true, + 'cascadeCallbacks' => true, +]); +``` + +> [!NOTE] +> Setting `cascadeCallbacks` to `true`, results in considerably slower deletes +> when compared to bulk deletes. The cascadeCallbacks option should only be +> enabled when your application has important work handled by event listeners. + +## Bulk Deletes + +`method` Cake\\ORM\\Table::**deleteMany**(iterable $entities, array $options = []): iterable|false + +If you have an array of entities you want to delete you can use `deleteMany()` +to delete them in a single transaction: + +``` php +// Get a boolean indicating success +$success = $this->Articles->deleteMany($entities); + +// Will throw a PersistenceFailedException if any entity cannot be deleted. +$this->Articles->deleteManyOrFail($entities); +``` + +The `$options` for these methods are the same as `delete()`. Deleting +records with these method **will** trigger events. + +### deleteAll() + +`method` Cake\\ORM\\Table::**deleteAll**($conditions): int + +There may be times when deleting rows one by one is not efficient or useful. +In these cases it is more performant to use a bulk-delete to remove many rows at +once: + +``` php +// Delete all the spam +public function destroySpam() +{ + return $this->deleteAll(['is_spam' => true]); +} +``` + +A bulk-delete will be considered successful if 1 or more rows are deleted. The +function returns the number of deleted records as an integer. + +> [!WARNING] +> deleteAll will *not* trigger beforeDelete/afterDelete events. +> If you need callbacks triggered, first load the entities with `find()` +> and delete them in a loop. + +## Strict Deletes + +`method` Cake\\ORM\\Table::**deleteOrFail**(EntityInterface $entity, array $options = []): bool + +Using this method will throw an +`Cake\ORM\Exception\PersistenceFailedException` if: + +- the entity is new +- the entity has no primary key value +- application rules checks failed +- the delete was aborted by a callback. + +If you want to track down the entity that failed to delete, you can use the +`Cake\ORM\Exception\PersistenceFailedException::getEntity()` method: + +``` php +try { + $table->deleteOrFail($entity); +} catch (\Cake\ORM\Exception\PersistenceFailedException $e) { + echo $e->getEntity(); +} +``` + +As this internally performs a `Cake\ORM\Table::delete()` call, all +corresponding delete events will be triggered. diff --git a/docs/en/orm/entities.md b/docs/en/orm/entities.md new file mode 100644 index 0000000000..c509b89e7a --- /dev/null +++ b/docs/en/orm/entities.md @@ -0,0 +1,657 @@ +# Entities + +`class` Cake\\ORM\\**Entity** + +While [Table Objects](../orm/table-objects) represent and provide access to a collection of +objects, entities represent individual rows or domain objects in your +application. Entities contain methods to manipulate and +access the data they contain. Fields can also be accessed as properties on the object. + +Entities are created for you each time you iterate the query instance returned +by `find()` of a table object or when you call `all()` or `first()` method +of the query instance. + +## Creating Entity Classes + +You don't need to create entity classes to get started with the ORM in CakePHP. +However, if you want to have custom logic in your entities you will need to +create classes. By convention entity classes live in **src/Model/Entity/**. If +our application had an `articles` table we could create the following entity: + +``` php +// src/Model/Entity/Article.php +namespace App\Model\Entity; + +use Cake\ORM\Entity; + +class Article extends Entity +{ +} +``` + +Right now this entity doesn't do very much. However, when we load data from our +articles table, we'll get instances of this class. + +> [!NOTE] +> If you don't define an entity class CakePHP will use the basic Entity class. + +## Creating Entities + +Entities can be directly instantiated: + +``` php +use App\Model\Entity\Article; + +$article = new Article(); +``` + +When instantiating an entity you can pass the fields with the data you want +to store in them: + +``` php +use App\Model\Entity\Article; + +$article = new Article([ + 'id' => 1, + 'title' => 'New Article', + 'created' => new DateTime('now') +]); +``` + +The preferred way of getting new entities is using the `newEmptyEntity()` method from the +`Table` objects: + +``` php +use Cake\ORM\Locator\LocatorAwareTrait; + +$article = $this->fetchTable('Articles')->newEmptyEntity(); + +$article = $this->fetchTable('Articles')->newEntity([ + 'id' => 1, + 'title' => 'New Article', + 'created' => new DateTime('now') +]); +``` + +`$article` will be an instance of `App\Model\Entity\Article` or fallback to +`Cake\ORM\Entity` instance if you haven't created the `Article` class. + +> [!NOTE] +> Prior to CakePHP 4.3 you need to use `$this->getTableLocator->get('Articles')` +> to get the table instance. + +## Accessing Entity Data + +Entities provide a few ways to access the data they contain. Most commonly you +will access the data in an entity using object notation: + +``` php +use App\Model\Entity\Article; + +$article = new Article; +$article->title = 'This is my first post'; +echo $article->title; +``` + +You can also use the `get()` and `set()` methods. + +### set() + +`method` Cake\\ORM\\Entity::**set**($field, $value = null, array $options = []) + +### get() + +`method` Cake\\ORM\\Entity::**get**($field) + +For example: + +``` php +$article->set('title', 'This is my first post'); +echo $article->get('title'); +``` + +### patch() + +`method` Cake\\ORM\\Entity::**patch**(array $fields, array $options = []) + +Using `patch()` you can mass assign multiple fields at once: + +``` php +$article->patch([ + 'title' => 'My first post', + 'body' => 'It is the best ever!' +]); +``` + +> [!NOTE] +> `patch()` is available since CakePHP 5.2.0. Prior to that you should use +> `set()` instead. + +> [!WARNING] +> When updating entities with request data you should configure which fields +> can be set with mass assignment. + +You can check if fields are defined in your entities with `has()`: + +``` php +$article = new Article([ + 'title' => 'First post', + 'user_id' => null +]); +$article->has('title'); // true +$article->has('user_id'); // true +$article->has('undefined'); // false +``` + +The `has()` method will return `true` if a field is defined. You can use +`hasValue()` to check if a field contains a 'non-empty' value: + +``` php +$article = new Article([ + 'title' => 'First post', + 'user_id' => null, + 'text' => '', + 'links' => [], +]); +$article->has('title'); // true +$article->hasValue('title'); // true + +$article->has('user_id'); // true +$article->hasValue('user_id'); // false + +$article->has('text'); // true +$article->hasValue('text'); // false + +$article->has('links'); // true +$article->hasValue('links'); // false +``` + +If you often partially load entities you should enable strict-property access +behavior to ensure you're not using properties that haven't been loaded. On +a per-entity basis you can enable this behavior: + +``` php +$article->requireFieldPresence(); +``` + +Once enabled, accessing properties that are not defined will raise +a `Cake\ORM\MissingPropertyException`. + +## Accessors & Mutators + +In addition to the simple get/set interface, entities allow you to provide +accessors and mutator methods. These methods let you customize how fields +are read or set. + +### Accessors + +Accessors let you customize how fields are read. They use the convention of +`_get(FieldName)` with `(FieldName)` being the CamelCased version (multiple +words are joined together to a single word with the first letter of each word +capitalized) of the field name. + +They receive the basic value stored in the `_fields` array as their only +argument. For example: + +``` php +namespace App\Model\Entity; + +use Cake\ORM\Entity; + +class Article extends Entity +{ + protected function _getTitle($title) + { + return strtoupper($title); + } +} +``` + +The example above converts the value of the `title` field to an uppercase +version each time it is read. It would be run when getting the field through any +of these two ways: + +``` php +echo $article->title; // returns FOO instead of foo +echo $article->get('title'); // returns FOO instead of foo +``` + +> [!NOTE] +> Code in your accessors is executed each time you reference the field. You can +> use a local variable to cache it if you are performing a resource-intensive +> operation in your accessor like this: \$myEntityProp = \$entity-\>my_property. + +> [!WARNING] +> Accessors will be used when saving entities, so be careful when defining methods +> that format data, as the formatted data will be persisted. + +### Mutators + +You can customize how fields get set by defining a mutator. They use the +convention of `_set(FieldName)` with `(FieldName)` being the CamelCased version +of the field name. + +Mutators should always return the value that should be stored in the field. +You can also use mutators to set other fields. When doing this, +be careful to not introduce any loops, as CakePHP will not prevent infinitely +looping mutator methods. For example: + +``` php +namespace App\Model\Entity; + +use Cake\ORM\Entity; +use Cake\Utility\Text; + +class Article extends Entity +{ + protected function _setTitle($title) + { + $this->slug = Text::slug($title); + + return strtoupper($title); + } +} +``` + +The example above is doing two things: It stores a modified version of the +given value in the `slug` field and stores an uppercase version in the +`title` field. It would be run when setting the field through +any of these two ways: + +``` php +$user->title = 'foo'; // sets slug field and stores FOO instead of foo +$user->set('title', 'foo'); // sets slug field and stores FOO instead of foo +``` + +> [!WARNING] +> Accessors are also run before entities are persisted to the database. +> If you want to transform fields but not persist that transformation, +> we recommend using virtual fields as those are not persisted. + + + +### Creating Virtual Fields + +By defining accessors you can provide access to fields that do not +actually exist. For example if your users table has `first_name` and +`last_name` you could create a method for the full name: + +``` php +namespace App\Model\Entity; + +use Cake\ORM\Entity; + +class User extends Entity +{ + protected function _getFullName() + { + return $this->first_name . ' ' . $this->last_name; + } +} +``` + +You can access virtual fields as if they existed on the entity. The property +name will be the lower case and underscored version of the method (`full_name`): + +``` php +echo $user->full_name; +echo $user->get('full_name'); +``` + +Do bear in mind that virtual fields cannot be used in finds. If you want +them to be part of JSON or array representations of your entities, +see [Exposing Virtual Fields](#exposing-virtual-fields). + +## Checking if an Entity Has Been Modified + +`method` Cake\\ORM\\Entity::**isDirty**(?string $field = null) + +You may want to make code conditional based on whether or not fields have +changed in an entity. For example, you may only want to validate fields when +they change: + +``` php +// See if the title has been modified. +$article->isDirty('title'); +``` + +You can also flag fields as being modified. This is handy when appending into +array fields as this wouldn't automatically mark the field as dirty, only +exchanging completely would.: + +``` php +// Add a comment and mark the field as changed. +$article->comments[] = $newComment; +$article->setDirty('comments', true); +``` + +In addition you can also base your conditional code on the original field +values by using the `getOriginal()` method. This method will either return +the original value of the field if it has been modified or its actual value. + +You can also check for changes to any field in the entity: + +``` php +// See if the entity has changed +$article->isDirty(); +``` + +To remove the dirty mark from fields in an entity, you can use the `clean()` +method: + +``` php +$article->clean(); +``` + +When creating a new entity, you can avoid the fields from being marked as dirty +by passing an extra option: + +``` php +$article = new Article(['title' => 'New Article'], ['markClean' => true]); +``` + +To get a list of all dirty fields of an `Entity` you may call: + +``` php +$dirtyFields = $entity->getDirty(); +``` + +## Validation Errors + +After you [save an entity](../orm/saving-data#saving-entities) any validation errors will be +stored on the entity itself. You can access any validation errors using the +`getErrors()`, `getError()` or `hasErrors()` methods: + +``` php +// Get all the errors +$errors = $user->getErrors(); + +// Get the errors for a single field. +$errors = $user->getError('password'); + +// Does the entity or any nested entity have an error. +$user->hasErrors(); + +// Does only the root entity have an error +$user->hasErrors(false); +``` + +The `setErrors()` or `setError()` method can also be used to set the errors +on an entity, making it easier to test code that works with error messages: + +``` php +$user->setError('password', ['Password is required']); +$user->setErrors([ + 'password' => ['Password is required'], + 'username' => ['Username is required'] +]); +``` + + + +## Mass Assignment + +While setting fields to entities in bulk is simple and convenient, it can +create significant security issues. Bulk assigning user data from the request +into an entity allows the user to modify any and all columns. When using +anonymous entity classes or creating the entity class with the [Bake Console](../bake) +CakePHP does not protect against mass-assignment. + +The `_accessible` property allows you to provide a map of fields and +whether or not they can be mass-assigned. The values `true` and `false` +indicate whether a field can or cannot be mass-assigned: + +``` php +namespace App\Model\Entity; + +use Cake\ORM\Entity; + +class Article extends Entity +{ + protected array $_accessible = [ + 'title' => true, + 'body' => true + ]; +} +``` + +In addition to concrete fields there is a special `*` field which defines the +fallback behavior if a field is not specifically named: + +``` php +namespace App\Model\Entity; + +use Cake\ORM\Entity; + +class Article extends Entity +{ + protected array $_accessible = [ + 'title' => true, + 'body' => true, + '*' => false, + ]; +} +``` + +> [!NOTE] +> If the `*` field is not defined it will default to `false`. + +### Avoiding Mass Assignment Protection + +When creating a new entity using the `new` keyword you can tell it to not +protect itself against mass assignment: + +``` php +use App\Model\Entity\Article; + +$article = new Article(['id' => 1, 'title' => 'Foo'], ['guard' => false]); +``` + +### Modifying the Guarded Fields at Runtime + +You can modify the list of guarded fields at runtime using the `setAccess()` +method: + +``` php +// Make user_id accessible. +$article->setAccess('user_id', true); + +// Make title guarded. +$article->setAccess('title', false); +``` + +> [!NOTE] +> Modifying accessible fields affects only the instance the method is called +> on. + +When using the `newEntity()` and `patchEntity()` methods in the `Table` +objects you can customize mass assignment protection with options. Please refer +to the [Changing Accessible Fields](../orm/saving-data#changing-accessible-fields) section for more information. + +### Bypassing Field Guarding + +There are some situations when you want to allow mass-assignment to guarded +fields: + +``` php +$article->patch($fields, ['guard' => false]); +``` + +By setting the `guard` option to `false`, you can ignore the accessible +field list for a single call to `patch()`. + +### Checking if an Entity was Persisted + +It is often necessary to know if an entity represents a row that is already +in the database. In those situations use the `isNew()` method: + +``` php +if (!$article->isNew()) { + echo 'This article was saved already!'; +} +``` + +If you are certain that an entity has already been persisted, you can use +`setNew()`: + +``` php +$article->setNew(false); + +$article->setNew(true); +``` + + + +## Lazy Loading Associations + +While eager loading associations is generally the most efficient way to access +your associations, there may be times when you need to lazily load associated +data. Before we get into how to lazy load associations, we should discuss the +differences between eager loading and lazy loading associations: + +Eager loading +Eager loading uses joins (where possible) to fetch data from the +database in as *few* queries as possible. When a separate query is required, +like in the case of a HasMany association, a single query is emitted to +fetch *all* the associated data for the current set of objects. + +Lazy loading +Lazy loading defers loading association data until it is absolutely +required. While this can save CPU time because possibly unused data is not +hydrated into objects, it can result in many more queries being emitted to +the database. For example looping over a set of articles & their comments +will frequently emit N queries where N is the number of articles being +iterated. + +While lazy loading is not included by CakePHP's ORM, you can just use one of the +community plugins to do so. We recommend [the LazyLoad Plugin](https://github.com/jeremyharris/cakephp-lazyload) + +After adding the plugin to your entity, you will be able to do the following: + +``` php +$article = $this->Articles->findById($id); + +// The comments property was lazy loaded +foreach ($article->comments as $comment) { + echo $comment->body; +} +``` + +## Creating Re-usable Code with Traits + +You may find yourself needing the same logic in multiple entity classes. PHP's +traits are a great fit for this. You can put your application's traits in +**src/Model/Entity**. By convention traits in CakePHP are suffixed with +`Trait` so they can be discernible from classes or interfaces. Traits are +often a good complement to behaviors, allowing you to provide functionality for +the table and entity objects. + +For example if we had SoftDeletable plugin, it could provide a trait. This trait +could give methods for marking entities as 'deleted', the method `softDelete` +could be provided by a trait: + +``` php +// SoftDelete/Model/Entity/SoftDeleteTrait.php + +namespace SoftDelete\Model\Entity; + +trait SoftDeleteTrait +{ + public function softDelete() + { + $this->set('deleted', true); + } +} +``` + +You could then use this trait in your entity class by importing it and including +it: + +``` php +namespace App\Model\Entity; + +use Cake\ORM\Entity; +use SoftDelete\Model\Entity\SoftDeleteTrait; + +class Article extends Entity +{ + use SoftDeleteTrait; +} +``` + +## Converting to Arrays/JSON + +When building APIs, you may often need to convert entities into arrays or JSON +data. CakePHP makes this simple: + +``` php +// Get an array. +// Associations will be converted with toArray() as well. +$array = $user->toArray(); + +// Convert to JSON +// Associations will be converted with jsonSerialize hook as well. +$json = json_encode($user); +``` + +When converting an entity to an JSON, the virtual & hidden field lists are +applied. Entities are recursively converted to JSON as well. This means that if you +eager loaded entities and their associations CakePHP will correctly handle +converting the associated data into the correct format. + + + +### Exposing Virtual Fields + +By default virtual fields are not exported when converting entities to +arrays or JSON. In order to expose virtual fields you need to make them +visible. When defining your entity class you can provide a list of virtual +field that should be exposed: + +``` php +namespace App\Model\Entity; + +use Cake\ORM\Entity; + +class User extends Entity +{ + protected array $_virtual = ['full_name']; +} +``` + +This list can be modified at runtime using the `setVirtual()` method: + +``` php +$user->setVirtual(['full_name', 'is_admin']); +``` + +### Hiding Fields + +There are often fields you do not want exported in JSON or array formats. For +example it is often unwise to expose password hashes or account recovery +questions. When defining an entity class, define which fields should be +hidden: + +``` php +namespace App\Model\Entity; + +use Cake\ORM\Entity; + +class User extends Entity +{ + protected $_hidden = ['password']; +} +``` + +This list can be modified at runtime using the `setHidden()` method: + +``` php +$user->setHidden(['password', 'recovery_question']); +``` + +## Storing Complex Types + +Accessor & Mutator methods on entities are not intended to contain the logic for +serializing and unserializing complex data coming from the database. Refer to +the [Saving Complex Types](../orm/saving-data#saving-complex-types) section to understand how your application can +store more complex data types like arrays and objects. diff --git a/docs/en/orm/query-builder.md b/docs/en/orm/query-builder.md new file mode 100644 index 0000000000..106850d86a --- /dev/null +++ b/docs/en/orm/query-builder.md @@ -0,0 +1,2323 @@ +# Query Builder + +`class` Cake\\ORM\\Query\\SelectQuery\\**SelectQuery** + +The ORM's query builder provides a simple to use fluent interface for creating +and running queries. By composing queries together, you can create advanced +queries using unions and subqueries with ease. + +Underneath the covers, the query builder uses PDO prepared statements which +protect against SQL injection attacks. + +## The SelectQuery Object + +The easiest way to create a `SelectQuery` object is to use `find()` from a +`Table` object. This method will return an incomplete query ready to be +modified. You can also use a table's connection object to access the lower level +query builder that does not include ORM features, if necessary. See the +[Database Queries](../orm/database-basics#database-queries) section for more information: + +``` php +use Cake\ORM\Locator\LocatorAwareTrait; + +$articles = $this->fetchTable('Articles'); + +// Start a new query. +$query = $articles->find(); +``` + +When inside a controller, you can use the automatic table variable that is +created using the conventions system: + +``` php +// Inside ArticlesController.php + +$query = $this->Articles->find(); +``` + +### Selecting Rows From A Table + +``` php +use Cake\ORM\Locator\LocatorAwareTrait; + +$query = $this->fetchTable('Articles')->find(); + +foreach ($query->all() as $article) { + debug($article->title); +} +``` + +For the remaining examples, assume that `$articles` is a +`Cake\ORM\Table`. When inside controllers, you can use +`$this->Articles` instead of `$articles`. + +Almost every method in a `SelectQuery` object will return the same query, this means +that `SelectQuery` objects are lazy, and will not be executed unless you tell them +to: + +``` php +$query->where(['id' => 1]); // Return the same query object +$query->orderBy(['title' => 'DESC']); // Still same object, no SQL executed +``` + +You can of course chain the methods you call on SelectQuery objects: + +``` php +$query = $articles + ->find() + ->select(['id', 'name']) + ->where(['id !=' => 1]) + ->orderBy(['created' => 'DESC']); + +foreach ($query->all() as $article) { + debug($article->created); +} +``` + +If you try to call `debug()` on a SelectQuery object, you will see its internal +state and the SQL that will be executed in the database: + +``` php +debug($articles->find()->where(['id' => 1])); + +// Outputs +// ... +// 'sql' => 'SELECT * FROM articles where id = ?' +// ... +``` + +You can execute a query directly without having to use `foreach` on it. +The easiest way is to either call the `all()` or `toList()` methods: + +``` php +$resultsIteratorObject = $articles + ->find() + ->where(['id >' => 1]) + ->all(); + +foreach ($resultsIteratorObject as $article) { + debug($article->id); +} + +$resultsArray = $articles + ->find() + ->where(['id >' => 1]) + ->all() + ->toList(); + +foreach ($resultsArray as $article) { + debug($article->id); +} + +debug($resultsArray[0]->title); +``` + +In the above example, `$resultsIteratorObject` will be an instance of +`Cake\ORM\ResultSet`, an object you can iterate and apply several extracting +and traversing methods on. + +Often, there is no need to call `all()`, you can simply iterate the +SelectQuery object to get its results. Query objects can also be used directly as the +result object; trying to iterate the query, calling `toList()` or `toArray()`, will +result in the query being executed and results returned to you. + +### Selecting A Single Row From A Table + +You can use the `first()` method to get the first result in the query: + +``` php +$article = $articles + ->find() + ->where(['id' => 1]) + ->first(); + +debug($article->title); +``` + +### Getting A List Of Values From A Column + +``` php +// Use the extract() method from the collections library +// This executes the query as well +$allTitles = $articles->find()->all()->extract('title'); + +foreach ($allTitles as $title) { + echo $title; +} +``` + +You can also get a key-value list out of a query result: + +``` php +$list = $articles->find('list')->all(); +foreach ($list as $id => $title) { + echo "$id : $title" +} +``` + +For more information on how to customize the fields used for populating the list +refer to [Table Find List](../orm/retrieving-data-and-resultsets#table-find-list) section. + +### ResultSet Is A Collection Object + +Once you get familiar with the Query object methods, it is strongly encouraged +that you visit the [Collection](../core-libraries/collections) section to +improve your skills in efficiently traversing the results. The ResultSet (returned +by calling the `SelectQuery`'s `all()` method) implements the collection interface: + +``` php +// Use the combine() method from the collections library +// This is equivalent to find('list') +$keyValueList = $articles->find()->all()->combine('id', 'title'); + +// An advanced example +$results = $articles->find() + ->where(['id >' => 1]) + ->orderBy(['title' => 'DESC']) + ->all() + ->map(function ($row) { + $row->trimmedTitle = trim($row->title); + + return $row; + }) + ->combine('id', 'trimmedTitle') // combine() is another collection method + ->toArray(); // Also a collections library method + +foreach ($results as $id => $trimmedTitle) { + echo "$id : $trimmedTitle"; +} +``` + +### Queries Are Lazily Evaluated + +Query objects are lazily evaluated. This means a query is not executed until one +of the following things occur: + +- The query is iterated with `foreach`. +- The query's `execute()` method is called. This will return the underlying + statement object, and is to be used with insert/update/delete queries. +- The query's `first()` method is called. This will return the first result in the set + built by `SELECT` (it adds `LIMIT 1` to the query). +- The query's `all()` method is called. This will return the result set and + can only be used with `SELECT` statements. +- The query's `toList()` or `toArray()` method is called. + +Until one of these conditions are met, the query can be modified without additional +SQL being sent to the database. It also means that if a Query hasn't been +evaluated, no SQL is ever sent to the database. Once executed, modifying and +re-evaluating a query will result in additional SQL being run. Calling the same query without modification multiple times will return same reference. + +If you want to take a look at what SQL CakePHP is generating, you can turn +database [query logging](../orm/database-basics#database-query-logging) on. + +## Selecting Data + +CakePHP makes building `SELECT` queries simple. To limit the fields fetched, +you can use the `select()` method: + +``` php +$query = $articles->find(); +$query->select(['id', 'title', 'body']); +foreach ($query->all() as $row) { + debug($row->title); +} +``` + +You can set aliases for fields by providing fields as an associative array: + +``` php +// Results in SELECT id AS pk, title AS aliased_title, body ... +$query = $articles->find(); +$query->select(['pk' => 'id', 'aliased_title' => 'title', 'body']); +``` + +To select distinct fields, you can use the `distinct()` method: + +``` php +// Results in SELECT DISTINCT country FROM ... +$query = $articles->find(); +$query->select(['country']) + ->distinct(['country']); +``` + +To set some basic conditions you can use the `where()` method: + +``` php +// Conditions are combined with AND +$query = $articles->find(); +$query->where(['title' => 'First Post', 'published' => true]); + +// You can call where() multiple times +$query = $articles->find(); +$query->where(['title' => 'First Post']) + ->where(['published' => true]); +``` + +You can also pass an anonymous function to the `where()` method. The passed +anonymous function will receive an instance of +`\Cake\Database\Expression\QueryExpression` as its first argument, and +`\Cake\ORM\Query\SelectQuery` as its second: + +``` php +$query = $articles->find(); +$query->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->eq('published', true); +}); +``` + +See the [Advanced Query Conditions](#advanced-query-conditions) section to find out how to construct +more complex `WHERE` conditions. + +### Selecting Specific Fields + +By default a query will select all fields from a table, the exception is when you +call the `select()` function yourself and pass certain fields: + +``` php +// Only select id and title from the articles table +$articles->find()->select(['id', 'title']); +``` + +If you wish to still select all fields from a table after having called +`select($fields)`, you can pass the table instance to `select()` for this +purpose: + +``` php +// Only all fields from the articles table including +// a calculated slug field. +$query = $articlesTable->find(); +$query + ->select(['slug' => $query->func()->concat(['title' => 'identifier', '-', 'id' => 'identifier'])]) + ->select($articlesTable); // Select all fields from articles +``` + +You can use `selectAlso()` to select all fields on a table and +*also* select some additional fields: + +``` php +$query = $articlesTable->find(); +$query->selectAlso(['count' => $query->func()->count('*')]); +``` + +If you want to select all but a few fields on a table, you can use +`selectAllExcept()`: + +``` php +$query = $articlesTable->find(); + +// Get all fields except the published field. +$query->selectAllExcept($articlesTable, ['published']); +``` + +You can also pass an `Association` object when working with contained +associations. + + + +### Using SQL Functions + +CakePHP's ORM offers abstraction for some commonly used SQL functions. Using the +abstraction allows the ORM to select the platform specific implementation of the +function you want. For example, `concat` is implemented differently in MySQL, +PostgreSQL and SQL Server. Using the abstraction allows your code to be +portable: + +``` php +// Results in SELECT COUNT(*) count FROM ... +$query = $articles->find(); +$query->select(['count' => $query->func()->count('*')]); +``` + +Note that most of the functions accept an additional argument to specify the types +to bind to the arguments and/or the return type, for example: + +``` php +$query->select(['minDate' => $query->func()->min('date', ['date']); +``` + +For details, see the documentation for `Cake\Database\FunctionsBuilder`. + +You can access existing wrappers for several SQL functions through `SelectQuery::func()`: + +`rand()` +Generate a random value between 0 and 1 via SQL. + +`sum()` +Calculate a sum. Assumes arguments are literal values. + +`avg()` +Calculate an average. Assumes arguments are literal values. + +`min()` +Calculate the min of a column. Assumes arguments are literal values. + +`max()` +Calculate the max of a column. Assumes arguments are literal values. + +`count()` +Calculate the count. Assumes arguments are literal values. + +`cast()` +Convert a field or expression from one data type to another. + +`concat()` +Concatenate two values together. Assumes arguments are bound parameters. + +`coalesce()` +Coalesce values. Assumes arguments are bound parameters. + +`dateDiff()` +Get the difference between two dates/times. Assumes arguments are bound parameters. + +`now()` +Defaults to returning date and time, but accepts 'time' or 'date' to return only +those values. + +`extract()` +Returns the specified data part from the SQL expression. + +`dateAdd()` +Add the time unit to the date expression. + +`dayOfWeek()` +Returns a FunctionExpression representing a call to SQL WEEKDAY function. + +#### Window-Only Functions + +These window-only functions contain a window expression by default: + +`rowNumber()` +Returns an Aggregate expression for the `ROW_NUMBER()` SQL function. + +`lag()` +Returns an Aggregate expression for the `LAG()` SQL function. + +`lead()` +Returns an Aggregate expression for the `LEAD()` SQL function. + +When providing arguments for SQL functions, there are two kinds of parameters +you can use, literal arguments and bound parameters. Identifier/Literal parameters allow +you to reference columns or other SQL literals. Bound parameters can be used to +safely add user data to SQL functions. For example: + +``` php +$query = $articles->find()->innerJoinWith('Categories'); +$concat = $query->func()->concat([ + 'Articles.title' => 'identifier', + ' - CAT: ', + 'Categories.name' => 'identifier', + ' - Age: ', + $query->func()->dateDiff([ + 'NOW()' => 'literal', + 'Articles.created' => 'identifier', + ]) +]); +$query->select(['link_title' => $concat]); +``` + +Both `literal` and `identifier` arguments allow you to reference other columns +and SQL literals while `identifier` will be appropriately quoted if auto-quoting +is enabled. If not marked as literal or identifier, arguments will be bound +parameters allowing you to safely pass user data to the function. + +The above example generates something like this in MYSQL. + +``` sql +SELECT CONCAT( + Articles.title, + :c0, + Categories.name, + :c1, + (DATEDIFF(NOW(), Articles.created)) +) FROM articles; +``` + +The `:c0` argument will have `' - CAT:'` text bound when the query is +executed. The `dateDiff` expression was translated to the appropriate SQL. + +#### Custom Functions + +If `func()` does not already wrap the SQL function you need, you can call +it directly through `func()` and still safely pass arguments and user data +as described. Make sure you pass the appropriate argument type for custom +functions or they will be treated as bound parameters: + +``` php +$query = $articles->find(); +$year = $query->func()->year([ + 'created' => 'identifier' +]); +$time = $query->func()->date_format([ + 'created' => 'identifier', + "'%H:%i'" => 'literal' +]); +$query->select([ + 'yearCreated' => $year, + 'timeCreated' => $time +]); +``` + +These custom function would generate something like this in MYSQL: + +``` sql +SELECT YEAR(created) as yearCreated, + DATE_FORMAT(created, '%H:%i') as timeCreated +FROM articles; +``` + +> [!NOTE] +> Use `func()` to pass untrusted user data to any SQL function. + +### Ordering Results + +To apply ordering, you can use the `orderBy()` method: + +``` php +$query = $articles->find() + ->orderBy(['title' => 'ASC', 'id' => 'ASC']); +``` + +When calling `orderBy()` multiple times on a query, multiple clauses will be +appended. However, when using finders you may sometimes need to overwrite the +`ORDER BY`. Set the second parameter of `orderBy()` (as well as +`orderByAsc()` or `orderByDesc()`) to `SelectQuery::OVERWRITE` or to `true`: + +``` php +$query = $articles->find() + ->orderBy(['title' => 'ASC']); +// Later, overwrite the ORDER BY clause instead of appending to it. +$query = $articles->find() + ->orderBy(['created' => 'DESC'], SelectQuery::OVERWRITE); +``` + +The `orderByAsc` and `orderByDesc` methods can be used when you need to sort on +complex expressions: + +``` php +$query = $articles->find(); +$concat = $query->func()->concat([ + 'title' => 'identifier', + 'synopsis' => 'identifier' +]); +$query->orderByAsc($concat); +``` + +To build complex order clauses, use a Closure to build order expressions: + +``` php +$query->orderByAsc(function (QueryExpression $exp, SelectQuery $query) { + return $exp->addCase(/* ... */); +}); +``` + +### Limiting Results + +To limit the number of rows or set the row offset you can use the `limit()` +and `page()` methods: + +``` php +// Fetch rows 50 to 100 +$query = $articles->find() + ->limit(50) + ->page(2); +``` + +As you can see from the examples above, all the methods that modify the query +provide a fluent interface, allowing you to build a query through chained method +calls. + +### Aggregates - Group and Having + +When using aggregate functions like `count` and `sum` you may want to use +`group by` and `having` clauses: + +``` php +$query = $articles->find(); +$query->select([ + 'count' => $query->func()->count('view_count'), + 'published_date' => 'DATE(created)' +]) +->groupBy('published_date') +->having(['count >' => 3]); +``` + +### Case Statements + +The ORM also offers the SQL `case` expression. The `case` expression allows +for implementing `if ... then ... else` logic inside your SQL. This can be useful +for reporting on data where you need to conditionally sum or count data, or where you +need to specific data based on a condition. + +If we wished to know how many published articles are in our database, we could use the following SQL: + +``` sql +SELECT +COUNT(CASE WHEN published = 'Y' THEN 1 END) AS number_published, +COUNT(CASE WHEN published = 'N' THEN 1 END) AS number_unpublished +FROM articles +``` + +To do this with the query builder, we'd use the following code: + +``` php +$query = $articles->find(); +$publishedCase = $query->expr() + ->case() + ->when(['published' => 'Y']) + ->then(1); +$unpublishedCase = $query->expr() + ->case() + ->when(['published' => 'N']) + ->then(1); + +$query->select([ + 'number_published' => $query->func()->count($publishedCase), + 'number_unpublished' => $query->func()->count($unpublishedCase) +]); +``` + +The `when()` method accepts SQL snippets, array conditions, and `Closure` +for when you need additional logic to build the cases. If we wanted to classify +cities into SMALL, MEDIUM, or LARGE based on population size, we could do the +following: + +``` php +$query = $cities->find(); +$sizing = $query->expr()->case() + ->when(['population <' => 100000]) + ->then('SMALL') + ->when($query->expr()->between('population', 100000, 999000)) + ->then('MEDIUM') + ->when(['population >=' => 999001]) + ->then('LARGE'); +$query = $query->select(['size' => $sizing]); +# SELECT CASE +# WHEN population < 100000 THEN 'SMALL' +# WHEN population BETWEEN 100000 AND 999000 THEN 'MEDIUM' +# WHEN population >= 999001 THEN 'LARGE' +# END AS size +``` + +You need to be careful when including user provided data into case expressions +as it can create SQL injection vulnerabilities: + +``` php +// Unsafe do *not* use +$case->when($requestData['published']); + +// Instead pass user data as values to array conditions +$case->when(['published' => $requestData['published']]); +``` + +For more complex scenarios you can use `QueryExpression` objects and bound +values: + +``` php +$userValue = $query->expr() + ->case() + ->when($query->expr('population >= :userData')) + ->then(123, 'integer'); + +$query->select(['val' => $userValue]) + ->bind(':userData', $requestData['value'], 'integer'); +``` + +By using bindings you can safely embed user data into complex raw SQL snippets. + +`then()`, `when()` and `else()` will try to infer the +value type based on the parameter type. If you need to bind a value as +a different type you can declare the desired type: + +``` php +$case->when(['published' => true])->then('1', 'integer'); +``` + +You can create `if ... then ... else` conditions by using `else()`: + +``` php +$published = $query->expr() + ->case() + ->when(['published' => true]) + ->then('Y') + ->else('N'); + +# CASE WHEN published = true THEN 'Y' ELSE 'N' END; +``` + +Also, it's possible to create the simple variant by passing a value to `case()`: + +``` php +$published = $query->expr() + ->case($query->identifier('published')) + ->when(true) + ->then('Y') + ->else('N'); + +# CASE published WHEN true THEN 'Y' ELSE 'N' END; +``` + +The `addCase` function can also chain together multiple statements to create +`if .. then .. [elseif .. then .. ] [ .. else ]` logic inside your SQL. + +If we wanted to classify cities into SMALL, MEDIUM, or LARGE based on population +size, we could do the following: + +``` php +$query = $cities->find() + ->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->addCase( + [ + $q->expr()->lt('population', 100000), + $q->expr()->between('population', 100000, 999000), + $q->expr()->gte('population', 999001), + ], + ['SMALL', 'MEDIUM', 'LARGE'], # values matching conditions + ['string', 'string', 'string'] # type of each value + ); + }); +# WHERE CASE +# WHEN population < 100000 THEN 'SMALL' +# WHEN population BETWEEN 100000 AND 999000 THEN 'MEDIUM' +# WHEN population >= 999001 THEN 'LARGE' +# END +``` + +Any time there are fewer case conditions than values, `addCase` will +automatically produce an `if .. then .. else` statement: + +``` php +$query = $cities->find() + ->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->addCase( + [ + $q->expr()->eq('population', 0), + ], + ['DESERTED', 'INHABITED'], # values matching conditions + ['string', 'string'] # type of each value + ); + }); +# WHERE CASE +# WHEN population = 0 THEN 'DESERTED' ELSE 'INHABITED' END +``` + +### Fetching Arrays Instead of Entities + +While ORMs and object result sets are powerful, creating entities is sometimes +unnecessary. For example, when accessing aggregated data, building an Entity may +not make sense. The process of converting the database results to entities is +called hydration. If you wish to disable this process you can do this: + +``` php +$query = $articles->find(); +$query->enableHydration(false); // Results as arrays instead of entities +$result = $query->toList(); // Execute the query and return the array +``` + +After executing those lines, your result should look similar to this: + +``` php +[ + ['id' => 1, 'title' => 'First Article', 'body' => 'Article 1 body' ...], + ['id' => 2, 'title' => 'Second Article', 'body' => 'Article 2 body' ...], + ... +] +``` + + + +### Projecting Results Into DTOs + +In addition to fetching results as Entity objects or arrays, you can project +query results directly into Data Transfer Objects (DTOs). DTOs offer several +advantages: + +- **Memory efficiency** - DTOs consume approximately 3x less memory than Entity + objects, making them ideal for large result sets. +- **Type safety** - DTOs provide strong typing and IDE autocompletion support, + unlike plain arrays. +- **Decoupled serialization** - DTOs let you separate your API response + structure from your database schema, making it easier to version APIs or + expose only specific fields. +- **Read-only data** - Using `readonly` classes ensures data integrity and + makes your intent clear. + +The `projectAs()` method allows you to specify a DTO class that results will +be hydrated into: + +``` php +// Define a DTO class +readonly class ArticleDto +{ + public function __construct( + public int $id, + public string $title, + public ?string $body = null, + ) { + } +} + +// Use projectAs() to hydrate results into DTOs +$articles = $articlesTable->find() + ->select(['id', 'title', 'body']) + ->projectAs(ArticleDto::class) + ->toArray(); +``` + +#### DTO Creation Methods + +CakePHP supports two approaches for creating DTOs: + +**Reflection-based constructor mapping** - CakePHP will use reflection to map +database columns to constructor parameters: + +``` php +readonly class ArticleDto +{ + public function __construct( + public int $id, + public string $title, + public ?AuthorDto $author = null, + ) { + } +} +``` + +**Factory method pattern** - If your DTO class has a `createFromArray()` +static method, CakePHP will use that instead: + +``` php +class ArticleDto +{ + public int $id; + public string $title; + + public static function createFromArray( + array $data, + bool $ignoreMissing = false + ): self { + $dto = new self(); + $dto->id = $data['id']; + $dto->title = $data['title']; + + return $dto; + } +} +``` + +The factory method approach is approximately 2.5x faster than reflection-based +hydration. + +#### Nested Association DTOs + +You can project associated data into nested DTOs. Use the `#[CollectionOf]` +attribute to specify the type of elements in array properties: + +``` php +use Cake\ORM\Attribute\CollectionOf; + +readonly class ArticleDto +{ + public function __construct( + public int $id, + public string $title, + public ?AuthorDto $author = null, + #[CollectionOf(CommentDto::class)] + public array $comments = [], + ) { + } +} + +readonly class AuthorDto +{ + public function __construct( + public int $id, + public string $name, + ) { + } +} + +readonly class CommentDto +{ + public function __construct( + public int $id, + public string $body, + ) { + } +} + +// Fetch articles with associations projected into DTOs +$articles = $articlesTable->find() + ->contain(['Authors', 'Comments']) + ->projectAs(ArticleDto::class) + ->toArray(); +``` + +#### Using DTOs for API Responses + +DTOs are particularly useful for building API responses where you want to +control the output structure independently from your database schema. You can +define a DTO that represents your API contract and include custom serialization +logic: + +``` php +readonly class ArticleApiResponse +{ + public function __construct( + public int $id, + public string $title, + public string $slug, + public string $authorName, + public string $publishedAt, + ) { + } + + public static function createFromArray( + array $data, + bool $ignoreMissing = false + ): self { + return new self( + id: $data['id'], + title: $data['title'], + slug: Inflector::slug($data['title']), + authorName: $data['author']['name'] ?? 'Unknown', + publishedAt: $data['created']->format('c'), + ); + } +} + +// In your controller +$articles = $this->Articles->find() + ->contain(['Authors']) + ->projectAs(ArticleApiResponse::class) + ->toArray(); + +return $this->response->withType('application/json') + ->withStringBody(json_encode(['articles' => $articles])); +``` + +This approach keeps your API response format decoupled from your database +schema, making it easier to evolve your API without changing your data model. + +> [!NOTE] +> DTO projection is applied as the final formatting step, after all other +> formatters and behaviors have processed the results. This ensures +> compatibility with existing behavior formatters while still providing the +> benefits of DTOs. + +::: info Added in version 5.3.0 +The `projectAs()` method and `#[CollectionOf]` attribute were added. +::: + + + +### Adding Calculated Fields + +After your queries, you may need to do some post-processing. If you need to add +a few calculated fields or derived data, you can use the `formatResults()` +method. This is a lightweight way to map over the result sets. If you need more +control over the process, or want to reduce results you should use +the [Map/Reduce](../orm/retrieving-data-and-resultsets#map-reduce) feature instead. If you were querying a list +of people, you could calculate their age with a result formatter: + +``` php +// Assuming we have built the fields, conditions and containments. +$query->formatResults(function (\Cake\Collection\CollectionInterface $results) { + return $results->map(function ($row) { + $row['age'] = $row['birth_date']->diff(new \DateTime)->y; + + return $row; + }); +}); +``` + +As you can see in the example above, formatting callbacks will get a +`ResultSetDecorator` as their first argument. The second argument will be +the Query instance the formatter was attached to. The `$results` argument can +be traversed and modified as necessary. + +Result formatters are required to return an iterator object, which will be used +as the return value for the query. Formatter functions are applied after all the +Map/Reduce routines have been executed. Result formatters can be applied from +within contained associations as well. CakePHP will ensure that your formatters +are properly scoped. For example, doing the following would work as you may +expect: + +``` php +// In a method in the Articles table +$query->contain(['Authors' => function ($q) { + return $q->formatResults(function (\Cake\Collection\CollectionInterface $authors) { + return $authors->map(function ($author) { + $author['age'] = $author['birth_date']->diff(new \DateTime)->y; + + return $author; + }); + }); +}]); + +// Get results +$results = $query->all(); + +// Outputs 29 +echo $results->first()->author->age; +``` + +As seen above, the formatters attached to associated query builders are scoped +to operate only on the data in the association. CakePHP will ensure that +computed values are inserted into the correct entity. + +If you want to replace the results of an association finder with +`formatResults` and your replacement data is an associative array, use +`preserveKeys` to retain keys when results are mapped to the parent query. For +example: + +``` php +public function findSlugged(SelectQuery $query): SelectQuery +{ + return $query->applyOptions(['preserveKeys' => true]) + ->formatResults(function ($results) { + return $results->indexBy(function ($record) { + return Text::slug($record->name); + }); + }); +} +``` + +The `preserveKeys` option can be set as a contain option as well. + +::: info Added in version 5.1.0 +The `preserveKeys` option was added. +::: + + + +## Advanced Conditions + +The query builder makes it simple to build complex `where` clauses. +Grouped conditions can be expressed by providing combining `where()` and +expression objects. For simple queries, you can build conditions using +an array of conditions: + +``` php +$query = $articles->find() + ->where([ + 'author_id' => 3, + 'OR' => [['view_count' => 2], ['view_count' => 3]], + ]); +``` + +The above would generate SQL like + +``` sql +SELECT * FROM articles WHERE author_id = 3 AND (view_count = 2 OR view_count = 3) +``` + +If you'd prefer to avoid deeply nested arrays, you can use the callback form of +`where()` to build your queries. The callback accepts a QueryExpression which allows +you to use the expression builder interface to build more complex conditions without arrays. +For example: + +``` php +$query = $articles->find()->where(function (QueryExpression $exp, SelectQuery $query) { + // Use add() to add multiple conditions for the same field. + $author = $query->expr()->or(['author_id' => 3])->add(['author_id' => 2]); + $published = $query->expr()->and(['published' => true, 'view_count' => 10]); + + return $exp->or([ + 'promoted' => true, + $query->expr()->and([$author, $published]) + ]); +}); +``` + +The above generates SQL similar to: + +``` sql +SELECT * +FROM articles +WHERE ( + ( + (author_id = 2 OR author_id = 3) + AND + (published = 1 AND view_count = 10) + ) + OR promoted = 1 +) +``` + +The `QueryExpression` passed to the callback allows you to use both +**combinators** and **conditions** to build the full expression. + +Combinators +These create new `QueryExpression` objects and set how the conditions added +to that expression are joined together. + +- `and()` creates new expression objects that joins all conditions with `AND`. +- `or()` creates new expression objects that joins all conditions with `OR`. + +Conditions +These are added to the expression and automatically joined together +depending on which combinator was used. + +The `QueryExpression` passed to the callback function defaults to `and()`: + +``` php +$query = $articles->find() + ->where(function (QueryExpression $exp) { + return $exp + ->eq('author_id', 2) + ->eq('published', true) + ->notEq('spam', true) + ->gt('view_count', 10); + }); +``` + +Since we started off using `where()`, we don't need to call `and()`, as +that happens implicitly. The above shows a few new condition +methods being combined with `AND`. The resulting SQL would look like: + +``` sql +SELECT * +FROM articles +WHERE ( +author_id = 2 +AND published = 1 +AND spam != 1 +AND view_count > 10) +``` + +However, if we wanted to use both `AND` & `OR` conditions we could do the +following: + +``` php +$query = $articles->find() + ->where(function (QueryExpression $exp) { + $orConditions = $exp->or(['author_id' => 2]) + ->eq('author_id', 5); + + return $exp + ->add($orConditions) + ->eq('published', true) + ->gte('view_count', 10); + }); +``` + +Which would generate the SQL similar to: + +``` sql +SELECT * +FROM articles +WHERE ( + (author_id = 2 OR author_id = 5) + AND published = 1 + AND view_count >= 10 +) +``` + +The **combinators** also allow you pass in a callback which takes +the new expression object as a parameter if you want to separate +the method chaining: + +``` php +$query = $articles->find() + ->where(function (QueryExpression $exp) { + $orConditions = $exp->or(function (QueryExpression $or) { + return $or->eq('author_id', 2) + ->eq('author_id', 5); + }); + + return $exp + ->not($orConditions) + ->lte('view_count', 10); + }); +``` + +You can negate sub-expressions using `not()`: + +``` php +$query = $articles->find() + ->where(function (QueryExpression $exp) { + $orConditions = $exp->or(['author_id' => 2]) + ->eq('author_id', 5); + + return $exp + ->not($orConditions) + ->lte('view_count', 10); + }); +``` + +Which will generate the following SQL looking like: + +``` sql +SELECT * +FROM articles +WHERE ( + NOT (author_id = 2 OR author_id = 5) + AND view_count <= 10 +) +``` + +It is also possible to build expressions using SQL functions: + +``` php +$query = $articles->find() + ->where(function (QueryExpression $exp, SelectQuery $q) { + $year = $q->func()->year([ + 'created' => 'identifier' + ]); + + return $exp + ->gte($year, 2014) + ->eq('published', true); + }); +``` + +Which will generate the following SQL looking like: + +``` sql +SELECT * +FROM articles +WHERE ( + YEAR(created) >= 2014 + AND published = 1 +) +``` + +When using the expression objects you can use the following methods to create +conditions: + +- `eq()` Creates an equality condition: + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->eq('population', '10000'); + }); + # WHERE population = 10000 + ``` + +- `notEq()` Creates an inequality condition: + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->notEq('population', '10000'); + }); + # WHERE population != 10000 + ``` + +- `like()` Creates a condition using the `LIKE` operator: + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->like('name', '%A%'); + }); + # WHERE name LIKE "%A%" + ``` + +- `notLike()` Creates a negated `LIKE` condition: + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->notLike('name', '%A%'); + }); + # WHERE name NOT LIKE "%A%" + ``` + +- `in()` Create a condition using `IN`: + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->in('country_id', ['AFG', 'USA', 'EST']); + }); + # WHERE country_id IN ('AFG', 'USA', 'EST') + ``` + +- `notIn()` Create a negated condition using `IN`: + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->notIn('country_id', ['AFG', 'USA', 'EST']); + }); + # WHERE country_id NOT IN ('AFG', 'USA', 'EST') + ``` + +- `gt()` Create a `>` condition: + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->gt('population', '10000'); + }); + # WHERE population > 10000 + ``` + +- `gte()` Create a `>=` condition: + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->gte('population', '10000'); + }); + # WHERE population >= 10000 + ``` + +- `lt()` Create a `<` condition: + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->lt('population', '10000'); + }); + # WHERE population < 10000 + ``` + +- `lte()` Create a `<=` condition: + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->lte('population', '10000'); + }); + # WHERE population <= 10000 + ``` + +- `isNull()` Create an `IS NULL` condition: + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->isNull('population'); + }); + # WHERE (population) IS NULL + ``` + +- `isNotNull()` Create a negated `IS NULL` condition: + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->isNotNull('population'); + }); + # WHERE (population) IS NOT NULL + ``` + +- `between()` Create a `BETWEEN` condition: + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->between('population', 999, 5000000); + }); + # WHERE population BETWEEN 999 AND 5000000, + ``` + +- `exists()` Create a condition using `EXISTS`: + + ``` php + $subquery = $cities->find() + ->select(['id']) + ->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->equalFields('countries.id', 'cities.country_id'); + }) + ->andWhere(['population >' => 5000000]); + + $query = $countries->find() + ->where(function (QueryExpression $exp, SelectQuery $q) use ($subquery) { + return $exp->exists($subquery); + }); + # WHERE EXISTS (SELECT id FROM cities WHERE countries.id = cities.country_id AND population > 5000000) + ``` + +- `notExists()` Create a negated condition using `EXISTS`: + + ``` php + $subquery = $cities->find() + ->select(['id']) + ->where(function (QueryExpression $exp, SelectQuery $q) { + return $exp->equalFields('countries.id', 'cities.country_id'); + }) + ->andWhere(['population >' => 5000000]); + + $query = $countries->find() + ->where(function (QueryExpression $exp, SelectQuery $q) use ($subquery) { + return $exp->notExists($subquery); + }); + # WHERE NOT EXISTS (SELECT id FROM cities WHERE countries.id = cities.country_id AND population > 5000000) + ``` + +Expression objects should cover many commonly used functions and expressions. If +you find yourself unable to create the required conditions with expressions you +can may be able to use `bind()` to manually bind parameters into conditions: + +``` php +$query = $cities->find() + ->where([ + 'start_date BETWEEN :start AND :end', + ]) + ->bind(':start', '2014-01-01', 'date') + ->bind(':end', '2014-12-31', 'date'); +``` + +In situations when you can't get, or don't want to use the builder methods to +create the conditions you want you can also use snippets of SQL in where +clauses: + +``` php +// Compare two fields to each other +$query->where(['Categories.parent_id != Parents.id']); +``` + +> [!WARNING] +> The field names used in expressions, and SQL snippets should **never** +> contain untrusted content as you will create SQL Injection vectors. See the +> [Using Sql Functions](#using-sql-functions) section for how to safely include unsafe data +> into function calls. + +### Using Identifiers in Expressions + +When you need to reference a column or SQL identifier in your queries you can +use the `identifier()` method: + +``` php +$query = $countries->find(); +$query->select([ + 'year' => $query->func()->year([$query->identifier('created')]) + ]) + ->where(function ($exp, $query) { + return $exp->gt('population', 100000); + }); +``` + +You can use `identifier()` in comparisons to aggregations too: + +``` php +$query = $this->Orders->find(); +$query->select(['Customers.customer_name', 'total_orders' => $query->func()->count('Orders.order_id')]) + ->contain('Customers') + ->groupBy(['Customers.customer_name']) + ->having(['total_orders >=' => $query->identifier('Customers.minimum_order_count')]); +``` + +> [!WARNING] +> To prevent SQL injections, Identifier expressions should never have +> untrusted data passed into them. + +### Collation + +In situations that you need to deal with accented characters, multilingual data +or case-sensitive comparisons, you can use the `$collation` parameter of `IdentifierExpression` +or `StringExpression` to apply a character expression to a certain collation: + +``` php +use Cake\Database\Expression\IdentifierExpression; + +$collation = 'Latin1_general_CI_AI'; //sql server example +$query = $cities->find() + ->where(function (QueryExpression $exp, Query $q) use ($collation) { + return $exp->like(new IdentifierExpression('name', $collation), '%São José%'); + }); +# WHERE name COLLATE LIKE Latin1_general_CI_AI "%São José%" +``` + +### Automatically Creating IN Clauses + +When building queries using the ORM, you will generally not have to indicate the +data types of the columns you are interacting with, as CakePHP can infer the +types based on the schema data. If in your queries you'd like CakePHP to +automatically convert equality to `IN` comparisons, you'll need to indicate +the column data type: + +``` php +$query = $articles->find() + ->where(['id' => $ids], ['id' => 'integer[]']); + +// Or include IN to automatically cast to an array. +$query = $articles->find() + ->where(['id IN' => $ids]); +``` + +The above will automatically create `id IN (...)` instead of `id = ?`. This +can be useful when you do not know whether you will get a scalar or array of +parameters. The `[]` suffix on any data type name indicates to the query +builder that you want the data handled as an array. If the data is not an array, +it will first be cast to an array. After that, each value in the array will +be cast using the [type system](../orm/database-basics#data-types). This works with +complex types as well. For example, you could take a list of DateTime objects +using: + +``` php +$query = $articles->find() + ->where(['post_date' => $dates], ['post_date' => 'date[]']); +``` + +### Automatic IS NULL Creation + +When a condition value is expected to be `null` or any other value, you can +use the `IS` operator to automatically create the correct expression: + +``` php +$query = $categories->find() + ->where(['parent_id IS' => $parentId]); +``` + +The above will generate`parent_id = :c1` or `parent_id IS NULL` depending on +the type of `$parentId` + +### Automatic IS NOT NULL Creation + +When a condition value is expected not to be `null` or any other value, you +can use the `IS NOT` operator to automatically create the correct expression: + +``` php +$query = $categories->find() + ->where(['parent_id IS NOT' => $parentId]); +``` + +The above will generate`parent_id != :c1` or `parent_id IS NOT NULL` +depending on the type of `$parentId` + +### Raw Expressions + +When you cannot construct the SQL you need using the query builder, you can use +expression objects to add snippets of SQL to your queries: + +``` php +$query = $articles->find(); +$expr = $query->expr()->add('1 + 1'); +$query->select(['two' => $expr]); +``` + +`Expression` objects can be used with any query builder methods like +`where()`, `limit()`, `groupBy()`, `select()` and many other methods. + +> [!WARNING] +> Using expression objects leaves you vulnerable to SQL injection. You should +> never use untrusted data into expressions. + +### Using Connection Roles + +If you have configured [Read And Write Connections](../orm/database-basics#read-and-write-connections) in your application, +you can have a query run on the `read` connection using one of the role +methods: + +``` php +// Run a query on the read connection +$query->useReadRole(); + +// Run a query on the write connection (default) +$query->useWriteRole(); +``` + +::: info Added in version 4.5.0 +Query role methods were added in 4.5.0 +::: + +### Expression Conjuction + +It is possible to change the conjunction used to join conditions in a query +expression using the method `setConjunction`: + +``` php +$query = $articles->find(); +$expr = $query->expr(['1','1'])->setConjunction('+'); +$query->select(['two' => $expr]); +``` + +And can be used combined with aggregations too: + +``` php +$query = $products->find(); +$query->select(function ($query) { + $stockQuantity = $query->func()->sum('Stocks.quantity'); + $totalStockValue = $query->func()->sum( + $query->expr(['Stocks.quantity', 'Products.unit_price']) + ->setConjunction('*') + ); + + return [ + 'Products.name', + 'stock_quantity' => $stockQuantity, + 'Products.unit_price', + 'total_stock_value' => $totalStockValue + ]; + }) + ->innerJoinWith('Stocks') + ->groupBy(['Products.id', 'Products.name', 'Products.unit_price']); +``` + +### Tuple Comparison + +Tuple comparison involves comparing two rows of data (tuples) element by element, +typically using comparison operators like `<, >, =`: + +``` php +$products->find() + ->where([ + 'OR' => [ + ['unit_price <' => 20], + ['unit_price' => 20, 'tax_percentage <=' => 5], + ] + ]); + +# WHERE (unit_price < 20 OR (unit_price = 20 AND tax_percentage <= 5)) +``` + +The same result can be achieved using `TupleComparison`: + +``` php +use Cake\Database\Expression\TupleComparison; + +$products->find() + ->where( + new TupleComparison( + ['unit_price', 'tax_percentage'], + [20, 5], + ['integer', 'integer'], # type of each value + '<=' + ) + ); + +# WHERE (unit_price, tax_percentage) <= (20, 5)) +``` + +Tuple Comparison can also be used with `IN` and the result can be transformed +even on DBMS that does not natively support it: + +``` php +$articles->find() + ->where( + new TupleComparison( + ['articles.id', 'articles.author_id'], + [[10, 10], [30, 10]], + ['integer', 'integer'], + 'IN' + ), + ); + +# WHERE (1) = ( SELECT (1) WHERE ( ( articles.id = : 10 AND articles.author_id = : 10 ) OR ( articles.id = : 30 AND articles.author_id = : 30 ) ) ) +``` + +> [!NOTE] +> Tuple comparison transform only supports the `IN` and `=` operators + +### Optimizer Hints + +Optimizer hints allow you to control execution plans at the individual query +level: + +``` php +$query->optimizerHint(['NO_BKA(articles)']); +``` + +Optimizer hints are currently only supported by MySQL and Postgres (via an +extension). + +::: info Added in version 5.3.0 +`Query::optimizerHint()` was added. +::: + +## Getting Results + +Once you've made your query, you'll want to retrieve rows from it. There are +a few ways of doing this: + +``` php +// Iterate the query +foreach ($query as $row) { + // Do stuff. +} + +// Get the results +$results = $query->all(); +``` + +You can use [any of the collection](../core-libraries/collections) methods +on your query objects to pre-process or transform the results: + +``` php +// Use one of the collection methods. +$ids = $query->map(function ($row) { + return $row->id; +}); + +$maxAge = $query->max(function ($max) { + return $max->age; +}); +``` + +You can use `first` or `firstOrFail` to retrieve a single record. These +methods will alter the query adding a `LIMIT 1` clause: + +``` php +// Get just the first row +$row = $query->first(); + +// Get the first row or an exception. +$row = $query->firstOrFail(); +``` + + + +### Returning the Total Count of Records + +Using a single query object, it is possible to obtain the total number of rows +found for a set of conditions: + +``` php +$total = $articles->find()->where(['is_active' => true])->count(); +``` + +The `count()` method will ignore the `limit`, `offset` and `page` +clauses, thus the following will return the same result: + +``` php +$total = $articles->find()->where(['is_active' => true])->limit(10)->count(); +``` + +This is useful when you need to know the total result set size in advance, +without having to construct another `SelectQuery` object. Likewise, all result +formatting and map-reduce routines are ignored when using the `count()` +method. + +Moreover, it is possible to return the total count for a query containing group +by clauses without having to rewrite the query in any way. For example, consider +this query for retrieving article ids and their comments count: + +``` php +$query = $articles->find(); +$query->select(['Articles.id', $query->func()->count('Comments.id')]) + ->matching('Comments') + ->groupBy(['Articles.id']); +$total = $query->count(); +``` + +After counting, the query can still be used for fetching the associated +records: + +``` php +$list = $query->all(); +``` + +Sometimes, you may want to provide an alternate method for counting the total +records of a query. One common use case for this is providing +a cached value or an estimate of the total rows, or to alter the query to remove +expensive unneeded parts such as left joins. This becomes particularly handy +when using the CakePHP built-in pagination system which calls the `count()` +method: + +``` php +$query = $query->where(['is_active' => true])->counter(function ($query) { + return 100000; +}); +$query->count(); // Returns 100000 +``` + +In the example above, when the pagination component calls the count method, it +will receive the estimated hard-coded number of rows. + + + +### Caching Loaded Results + +When fetching entities that don't change often you may want to cache the +results. The `SelectQuery` class makes this simple: + +``` php +$query->cache('recent_articles'); +``` + +Will enable caching on the query's result set. If only one argument is provided +to `cache()` then the 'default' cache configuration will be used. You can +control which caching configuration is used with the second parameter: + +``` php +// String config name. +$query->cache('recent_articles', 'dbResults'); + +// Instance of CacheEngine +$query->cache('recent_articles', $memcache); +``` + +In addition to supporting static keys, the `cache()` method accepts a function +to generate the key. The function you give it will receive the query as an +argument. You can then read aspects of the query to dynamically generate the +cache key: + +``` php +// Generate a key based on a simple checksum +// of the query's where clause +$query->cache(function ($q) { + return 'articles-' . md5(serialize($q->clause('where'))); +}); +``` + +The cache method makes it simple to add cached results to your custom finders or +through event listeners. + +When the results for a cached query are fetched the following happens: + +1. If the query has results set, those will be returned. +2. The cache key will be resolved and cache data will be read. If the cache data + is not empty, those results will be returned. +3. If the cache misses, the query will be executed, the `Model.beforeFind` event + will be triggered, and a new `ResultSet` will be created. This + `ResultSet` will be written to the cache and returned. + +> [!NOTE] +> You cannot cache a streaming query result. + +## Loading Associations + +The builder can help you retrieve data from multiple tables at the same time +with the minimum amount of queries possible. To be able to fetch associated +data, you first need to setup associations between the tables as described in +the [Associations - Linking Tables Together](../orm/associations) section. This technique of combining queries +to fetch associated data from other tables is called **eager loading**. + + + +### Filtering by Associated Data + + + + + +### Adding Joins + +In addition to loading related data with `contain()`, you can also add +additional joins with the query builder: + +``` php +$query = $articles->find() + ->join([ + 'table' => 'comments', + 'alias' => 'c', + 'type' => 'LEFT', + 'conditions' => 'c.article_id = articles.id', + ]); +``` + +You can append multiple joins at the same time by passing an associative array +with multiple joins: + +``` php +$query = $articles->find() + ->join([ + 'c' => [ + 'table' => 'comments', + 'type' => 'LEFT', + 'conditions' => 'c.article_id = articles.id', + ], + 'u' => [ + 'table' => 'users', + 'type' => 'INNER', + 'conditions' => 'u.id = articles.user_id', + ] + ]); +``` + +As seen above, when adding joins the alias can be the outer array key. Join +conditions can also be expressed as an array of conditions: + +``` php +$query = $articles->find() + ->join([ + 'c' => [ + 'table' => 'comments', + 'type' => 'LEFT', + 'conditions' => [ + 'c.created >' => new DateTime('-5 days'), + 'c.moderated' => true, + 'c.article_id = articles.id' + ] + ], + ], ['c.created' => 'datetime', 'c.moderated' => 'boolean']); +``` + +When creating joins by hand and using array based conditions, you need to +provide the datatypes for each column in the join conditions. By providing +datatypes for the join conditions, the ORM can correctly convert data types into +SQL. In addition to `join()` you can use `rightJoin()`, `leftJoin()` and +`innerJoin()` to create joins: + +``` php +// Join with an alias and string conditions +$query = $articles->find(); +$query->leftJoin( + ['Authors' => 'authors'], + ['Authors.id = Articles.author_id']); + +// Join with an alias, array conditions, and types +$query = $articles->find(); +$query->innerJoin( + ['Authors' => 'authors'], + [ + 'Authors.promoted' => true, + 'Authors.created' => new DateTime('-5 days'), + 'Authors.id = Articles.author_id', + ], + [ + 'Authors.promoted' => 'boolean', + 'Authors.created' => 'datetime', + ] +); +``` + +It should be noted that if you set the `quoteIdentifiers` option to `true` when +defining your `Connection`, join conditions between table fields should be set as follow: + +``` php +$query = $articles->find() + ->join([ + 'c' => [ + 'table' => 'comments', + 'type' => 'LEFT', + 'conditions' => [ + 'c.article_id' => new \Cake\Database\Expression\IdentifierExpression('articles.id'), + ], + ], + ]); +``` + +This ensures that all of your identifiers will be quoted across the Query, avoiding errors with +some database Drivers (PostgreSQL notably) + +## Inserting Data + +Unlike earlier examples, you should can't use `find()` to create insert queries. +Instead, create a new `InsertQuery` object using `insertQuery()`: + +``` php +$query = $articles->insertQuery(); +$query->insert(['title', 'body']) + ->values([ + 'title' => 'First post', + 'body' => 'Some body text', + ]) + ->execute(); +``` + +To insert multiple rows with only one query, you can chain the `values()` +method as many times as you need: + +``` php +$query = $articles->insertQuery(); +$query->insert(['title', 'body']) + ->values([ + 'title' => 'First post', + 'body' => 'Some body text', + ]) + ->values([ + 'title' => 'Second post', + 'body' => 'Another body text', + ]) + ->execute(); +``` + +Generally, it is easier to insert data using entities and +`Cake\ORM\Table::save()`. By composing a `SELECT` and +`INSERT` query together, you can create `INSERT INTO ... SELECT` style +queries: + +``` php +$select = $articles->find() + ->select(['title', 'body', 'published']) + ->where(['id' => 3]); + +$query = $articles->insertQuery() + ->insert(['title', 'body', 'published']) + ->values($select) + ->execute(); +``` + +> [!NOTE] +> Inserting records with the query builder will not trigger events such as +> `Model.afterSave`. Instead you should use the [ORM to save +> data](../orm/saving-data). + + + +## Updating Data + +As with insert queries, you should not use `find()` to create update queries. +Instead, create new a `Query` object using `updateQuery()`: + +``` php +$query = $articles->updateQuery(); +$query->set(['published' => true]) + ->where(['id' => $id]) + ->execute(); +``` + +Generally, it is easier to update data using entities and +`Cake\ORM\Table::patchEntity()`. + +> [!NOTE] +> Updating records with the query builder will not trigger events such as +> `Model.afterSave`. Instead you should use the [ORM to save +> data](../orm/saving-data). + +## Deleting Data + +As with insert queries, you can't use `find()` to create delete queries. +Instead, create new a query object using `deleteQuery()`: + +``` php +$query = $articles->deleteQuery(); +$query->where(['id' => $id]) + ->execute(); +``` + +Generally, it is easier to delete data using entities and +`Cake\ORM\Table::delete()`. + +## SQL Injection Prevention + +While the ORM and database abstraction layers prevent most SQL injections +issues, it is still possible to leave yourself vulnerable through improper use. + +When using condition arrays, the key/left-hand side as well as single value +entries must not contain user data: + +``` php +$query->where([ + // Data on the key/left-hand side is unsafe, as it will be + // inserted into the generated query as-is + $userData => $value, + + // The same applies to single value entries, they are not + // safe to use with user data in any form + $userData, + "MATCH (comment) AGAINST ($userData)", + 'created < NOW() - ' . $userData +]); +``` + +When using the expression builder, column names must not contain user data: + +``` php +$query->where(function (QueryExpression $exp) use ($userData, $values) { + // Column names in all expressions are not safe. + return $exp->in($userData, $values); +}); +``` + +When building function expressions, function names should never contain user +data: + +``` php +// Not safe. +$query->func()->{$userData}($arg1); + +// Also not safe to use an array of +// user data in a function expression +$query->func()->coalesce($userData); +``` + +Raw expressions are never safe: + +``` php +$expr = $query->expr()->add($userData); +$query->select(['two' => $expr]); +``` + +### Binding values + +It is possible to protect against many unsafe situations by using bindings. +Values can be bound to queries using the `Cake\Database\Query::bind()` +method. + +The following example would be a safe variant of the unsafe, SQL injection prone +example given above: + +``` php +$query + ->where([ + 'MATCH (comment) AGAINST (:userData)', + 'created < NOW() - :moreUserData', + ]) + ->bind(':userData', $userData, 'string') + ->bind(':moreUserData', $moreUserData, 'datetime'); +``` + +> [!NOTE] +> Unlike `Cake\Database\StatementInterface::bindValue()`, +> `Query::bind()` requires to pass the named placeholders including the +> colon! + +## More Complex Queries + +If your application requires using more complex queries, you can express many +complex queries using the ORM query builder. + +### Unions + +Unions are created by composing one or more select queries together: + +``` php +$inReview = $articles->find() + ->where(['need_review' => true]); + +$unpublished = $articles->find() + ->where(['published' => false]); + +$unpublished->union($inReview); +``` + +You can create `UNION ALL` queries using the `unionAll()` method: + +``` php +$inReview = $articles->find() + ->where(['need_review' => true]); + +$unpublished = $articles->find() + ->where(['published' => false]); + +$unpublished->unionAll($inReview); +``` + +### Intersections + +Intersections allow you to combine the result sets of two queries together and +finding results with overlapping results. Intersections are created by composing +one or more select queries together: + +``` php +$inReview = $articles->find() + ->where(['need_review' => true]); + +$unpublished = $articles->find() + ->where(['published' => false]); + +$unpublished->intersect($inReview); +``` + +You can create `INTERSECT ALL` queries using the `intersectAll()` method: + +``` php +$inReview = $articles->find() + ->where(['need_review' => true]); + +$unpublished = $articles->find() + ->where(['published' => false]); + +$unpublished->intersectAll($inReview); +``` + +::: info Added in version 5.1.0 +`intersect()` and `intersectAll()` were added. +::: + +### Subqueries + +Subqueries enable you to compose queries together and build conditions and +results based on the results of other queries: + +``` php +$matchingComment = $articles->getAssociation('Comments')->find() + ->select(['article_id']) + ->distinct() + ->where(['comment LIKE' => '%CakePHP%']); + +// Use a subquery to create conditions +$query = $articles->find() + ->where(['id IN' => $matchingComment]); + +// Join the results of a subquery into another query. +// Giving the subquery an alias provides a way to reference +// results in subquery. +$query = $articles->find(); +$query->from(['matches' => $matchingComment]) + ->innerJoin( + ['Articles' => 'articles'], + ['Articles.id' => $query->identifier('matches.id') ] + ); +``` + +Subqueries are accepted anywhere a query expression can be used. For example, in +the `select()`, `from()` and `join()` methods. The above example uses a standard +`ORM\Query\SelectQuery` object that will generate aliases, these aliases can make +referencing results in the outer query more complex. As of 4.2.0 you can use +`Table::subquery()` to create a specialized query instance that will not +generate aliases: + +``` php +$comments = $articles->getAssociation('Comments')->getTarget(); + +$matchingComment = $comments->subquery() + ->select(['article_id']) + ->distinct() + ->where(['comment LIKE' => '%CakePHP%']); + +$query = $articles->find() + ->where(['id IN' => $matchingComment]); +``` + +### Adding Locking Statements + +Most relational database vendors support taking out locks when doing select +operations. You can use the `epilog()` method for this: + +``` php +// In MySQL +$query->epilog('FOR UPDATE'); +``` + +The `epilog()` method allows you to append raw SQL to the end of queries. You +should never put raw user data into `epilog()`. + +### Window Functions + +Window functions allow you to perform calculations using rows related to the +current row. They are commonly used to calculate totals or offsets on partial sets of rows +in the query. For example if we wanted to find the date of the earliest and latest comment on +each article we could use window functions: + +``` php +$query = $articles->find(); +$query->select([ + 'Articles.id', + 'Articles.title', + 'Articles.user_id' + 'oldest_comment' => $query->func() + ->min('Comments.created') + ->partition('Comments.article_id'), + 'latest_comment' => $query->func() + ->max('Comments.created') + ->partition('Comments.article_id'), +]) +->innerJoinWith('Comments'); +``` + +The above would generate SQL similar to: + +``` sql +SELECT + Articles.id, + Articles.title, + Articles.user_id + MIN(Comments.created) OVER (PARTITION BY Comments.article_id) AS oldest_comment, + MAX(Comments.created) OVER (PARTITION BY Comments.article_id) AS latest_comment, +FROM articles AS Articles +INNER JOIN comments AS Comments +``` + +Window expressions can be applied to most aggregate functions. Any aggregate function +that cake abstracts with a wrapper in `FunctionsBuilder` will return an `AggregateExpression` +which lets you attach window expressions. You can create custom aggregate functions +through `FunctionsBuilder::aggregate()`. + +These are the most commonly supported window features. Most features are provided +by `AggregateExpresion`, but make sure you follow your database documentation on use and restrictions. + +- `orderBy($fields)` Order the aggregate group the same as a query ORDER BY. +- `partition($expressions)` Add one or more partitions to the window based on column + names. +- `rows($start, $end)` Define a offset of rows that precede and/or follow the + current row that should be included in the aggregate function. +- `range($start, $end)` Define a range of row values that precede and/or follow + the current row that should be included in the aggregate function. This + evaluates values based on the `orderBy()` field. + +If you need to re-use the same window expression multiple times you can create +named windows using the `window()` method: + +``` php +$query = $articles->find(); + +// Define a named window +$query->window('related_article', function ($window, $query) { + $window->partition('Comments.article_id'); + + return $window; +}); + +$query->select([ + 'Articles.id', + 'Articles.title', + 'Articles.user_id' + 'oldest_comment' => $query->func() + ->min('Comments.created') + ->over('related_article'), + 'latest_comment' => $query->func() + ->max('Comments.created') + ->over('related_article'), +]); +``` + +### Common Table Expressions + +[Common Table Expressions or CTE](https://en.wikipedia.org/wiki/Hierarchical_and_recursive_queries_in_SQL#Common_table_expression) +are useful when building reporting queries where you need to compose the results +of several smaller query results together. They can serve a similar purpose +to database views or subquery results. Common Table Expressions differ from +derived tables and views in a couple ways: + +1. Unlike views, you don't have to maintain schema for common table expressions. + The schema is implicitly based on the result set of the table expression. +2. You can reference the results of a common table expression multiple times + without incurring performance penalties unlike subquery joins. + +As an example lets fetch a list of customers and the number of orders each of +them has made. In SQL we would use: + +``` sql +WITH orders_per_customer AS ( + SELECT COUNT(*) AS order_count, customer_id FROM orders GROUP BY customer_id +) +SELECT name, orders_per_customer.order_count +FROM customers +INNER JOIN orders_per_customer ON orders_per_customer.customer_id = customers.id +``` + +To build that query with the ORM query builder we would use: + +``` php +// Start the final query +$query = $this->Customers->find(); + +// Attach a common table expression +$query->with(function ($cte) { + // Create a subquery to use in our table expression + $q = $this->Orders->subquery(); + $q->select([ + 'order_count' => $q->func()->count('*'), + 'customer_id', + ]) + ->groupBy('customer_id'); + + // Attach the new query to the table expression + return $cte + ->name('orders_per_customer') + ->query($q); +}); + +// Finish building the final query +$query->select([ + 'name', + 'order_count' => 'orders_per_customer.order_count', +]) +->join([ + // Define the join with our table expression + 'orders_per_customer' => [ + 'table' => 'orders_per_customer', + 'conditions' => 'orders_per_customer.customer_id = Customers.id', + ], +]); +``` + +If you need to build a recursive query (`WITH RECURSIVE …`), chain `->recursive()` onto `return $cte`. + +### Executing Complex Queries + +While the query builder makes most queries possible through builder methods, +very complex queries can be tedious and complicated to build. You may want to +[execute the desired SQL directly](../orm/database-basics#running-select-statements). + +Executing SQL directly allows you to fine tune the query that will be run. +However, doing so doesn't let you use `contain` or other higher level ORM +features. diff --git a/docs/en/orm/retrieving-data-and-resultsets.md b/docs/en/orm/retrieving-data-and-resultsets.md new file mode 100644 index 0000000000..7cb770c61e --- /dev/null +++ b/docs/en/orm/retrieving-data-and-resultsets.md @@ -0,0 +1,1410 @@ +# Retrieving Data & Results Sets + +`class` Cake\\ORM\\**Table** + +While table objects provide an abstraction around a 'repository' or collection +of objects, when you query for individual records you get 'entity' objects. +While this section discusses the different ways you can find and load entities, +you should read the [Entities](../orm/entities) section for more information on +entities. + +## Debugging Queries and ResultSets + +Since the ORM now returns Collections and Entities, debugging these objects can +be more complicated than in previous CakePHP versions. There are now various +ways to inspect the data returned by the ORM. + +- `debug($query)` Shows the SQL and bound parameters, does not show results. +- `sql($query)` Shows the final rendered SQL when DebugKit is installed. +- `debug($query->all())` Shows the ResultSet properties (not the results). +- `debug($query->toList())` Show results in an array. +- `debug(iterator_to_array($query))` Shows query results in an array format. +- `debug(json_encode($query, JSON_PRETTY_PRINT))` More human readable results. +- `debug($query->first())` Show the properties of a single entity. +- `debug((string)$query->first())` Show the properties of a single entity as JSON. + +## Getting a Single Entity by Primary Key + +`method` Cake\\ORM\\Table::**get**(mixed $primaryKey, array|string $finder = 'all', CacheInterface|string|null $cache = null, Closure|string|null $cacheKey = null, mixed ...$args): EntityInterface + +It is often convenient to load a single entity from the database when editing or +viewing entities and their related data. You can do this by using `get()`: + +``` php +// In a controller or table method. + +// Get a single article +$article = $articles->get($id); + +// Get a single article, and related comments +$article = $articles->get($id, contain: ['Comments']); +``` + +If the get operation does not find any results a +`Cake\Datasource\Exception\RecordNotFoundException` will be raised. You can +either catch this exception yourself, or allow CakePHP to convert it into a 404 +error. + +Like `find()`, `get()` also has caching integrated. You can use the +`cache` option when calling `get()` to perform read-through caching: + +``` php +// In a controller or table method. + +// Use any cache config or CacheEngine instance & a generated key +$article = $articles->get($id, cache: 'custom'); + +// Use any cache config or CacheEngine instance & specific key +$article = $articles->get($id, cache: 'custom', key: 'mykey'); + +// Explicitly disable caching +$article = $articles->get($id, cache: false); +``` + +Optionally you can `get()` an entity using [Custom Find Methods](#custom-find-methods). For +example you may want to get all translations for an entity. You can achieve that +by using the `finder` option: + +``` php +$article = $articles->get($id, 'translations'); +``` + +The parameters supported by `get()` are: + +- `$primaryKey` The primary key value to look up. +- `$finder` The finder to use. Can be a string finder name or an array of finder name and options. +- `$cache` A cache config name or `CacheInterface` instance to use for read-through caching. +- `$cacheKey` A custom cache key or `Closure` to generate one. +- `...$args` Additional arguments passed to the finder. + +## Using Finders to Load Data + +`method` Cake\\ORM\\Table::**find**(string $type = 'all', mixed ...$args): SelectQuery + +Before you can work with entities, you'll need to load them. The easiest way to +do this is using the `find()` method. The find method provides a short and +extensible way to find the data you are interested in: + +``` php +// In a controller or table method. + +// Find all the articles +$query = $articles->find('all'); +``` + +The return value of any `find()` method is always +a `Cake\ORM\Query\SelectQuery` object. The SelectQuery class allows you to further +refine a query after creating it. SelectQuery objects are evaluated lazily, and do not +execute until you start fetching rows, convert it to an array, or when the +`all()` method is called: + +``` php +// In a controller or table method. + +// Find all the articles. +// At this point the query has not run. +$query = $articles->find('all'); + +// Calling all() will execute the query +// and return the result set. +$results = $query->all(); + +// Once we have a result set we can get all the rows +$data = $results->toList(); + +// Converting the query to a key-value array will also execute it. +$data = $query->toArray(); +``` + +> [!NOTE] +> Once you've started a query you can use the [Query Builder](../orm/query-builder) +> interface to build more complex queries, adding additional conditions, +> limits, or include associations using the fluent interface. + +``` php +// In a controller or table method. +$query = $articles->find('all') + ->where(['Articles.created >' => new DateTime('-10 days')]) + ->contain(['Comments', 'Authors']) + ->limit(10); +``` + +You can also provide many commonly used options to `find()`: + +``` php +// In a controller or table method. +$query = $articles->find('all', + conditions: ['Articles.created >' => new DateTime('-10 days')], + contain: ['Authors', 'Comments'], + limit: 10 +); +``` + +If your finder options are in an array, you can use the [splat operator](https://www.php.net/manual/en/functions.arguments.php#functions.variable-arg-list) (`...`) +to pass them into `find()`: + +``` php +$options = [ + 'conditions' => ['Articles.created >' => new DateTime('-10 days')], + 'contain' => ['Authors', 'Comments'], + 'limit' => 10, +] +$query = $articles->find('all', ...$options); +``` + +The list of named arguments supported by find() by default are: + +- `conditions` provide conditions for the WHERE clause of your query. +- `limit` Set the number of rows you want. +- `offset` Set the page offset you want. You can also use `page` to make + the calculation simpler. +- `contain` define the associations to eager load. +- `fields` limit the fields loaded into the entity. Only loading some fields + can cause entities to behave incorrectly. +- `group` add a GROUP BY clause to your query. This is useful when using + aggregating functions. +- `having` add a HAVING clause to your query. +- `join` define additional custom joins. +- `order` order the result set. + +Any options that are not in this list will be passed to `beforeFind` listeners +where they can be used to modify the query object. You can use the +`getOptions()` method on a query object to retrieve the options used. While +you can pass query objects to your controllers, we recommend that you package +your queries up as [Custom Find Methods](#custom-find-methods) instead. Using custom finder +methods will let you re-use your queries and make testing easier. + +By default queries and result sets will return [Entities](../orm/entities) objects. You +can retrieve basic arrays by disabling hydration: + +``` php +$query->disableHydration(); + +// $data is ResultSet that contains array data. +$data = $query->all(); +``` + + + +## Getting the First Result + +The `first()` method allows you to fetch only the first row from a query. If +the query has not been executed, a `LIMIT 1` clause will be applied: + +``` php +// In a controller or table method. +$query = $articles->find('all', order: ['Articles.created' => 'DESC']); +$row = $query->first(); +``` + +This approach replaces `find('first')` in previous versions of CakePHP. You +may also want to use the `get()` method if you are loading entities by primary +key. + +> [!NOTE] +> The `first()` method will return `null` if no results are found. + +## Getting a Count of Results + +Once you have created a query object, you can use the `count()` method to get +a result count of that query: + +``` php +// In a controller or table method. +$query = $articles->find('all', conditions: ['Articles.title LIKE' => '%Ovens%']); +$number = $query->count(); +``` + +See [Query Count](../orm/query-builder#query-count) for additional usage of the `count()` method. + + + +## Finding Key/Value Pairs + +It is often useful to generate an associative array of data from your +application's data. For example, this is very useful when creating ` +``` + +> [!NOTE] +> Since this is an *edit* form, a hidden `input` field is generated to +> override the default HTTP method. + +In some cases, the entity's ID is automatically appended to the end of the form's `action` URL. If you would like to *avoid* an ID being added to the URL, you can pass a string to `$options['url']`, such as `'/my-account'` or `\Cake\Routing\Router::url(['controller' => 'Users', 'action' => 'myAccount'])`. + +### Options for Form Creation + +The `$options` array is where most of the form configuration +happens. This special array can contain a number of different +key-value pairs that affect the way the form tag is generated. +Valid values: + +- `'type'` - Allows you to choose the type of form to create. If no type is + provided then it will be autodetected based on the form context. + Valid values: + - `'get'` - Will set the form method to HTTP GET. + - `'file'` - Will set the form method to POST and the `'enctype'` to + "multipart/form-data". + - `'post'` - Will set the method to POST. + - `'put', 'delete', 'patch'` - Will override the HTTP method with PUT, + DELETE or PATCH respectively, when the form is submitted. +- `'method'` - Valid values are the same as above. Allows you to explicitly + override the form's method. +- `'url'` - Specify the URL the form will submit to. Can be a string or a URL + array. +- `'encoding'` - Sets the `accept-charset` encoding for the form. Defaults + to `Configure::read('App.encoding')`. +- `'enctype'` - Allows you to set the form encoding explicitly. +- `'templates'` - The templates you want to use for this form. Any templates + provided will be merged on top of the already loaded templates. Can be either + a filename (without extension) from `/config` or an array of templates to use. +- `'context'` - Additional options for the form context class. (For example + the `EntityContext` accepts a `'table'` option that allows you to set the + specific Table class the form should be based on.) +- `'idPrefix'` - Prefix for generated ID attributes. +- `'templateVars'` - Allows you to provide template variables for the + `formStart` template. +- `autoSetCustomValidity` - Set to `true` to use custom required and notBlank + validation messages in the control's HTML5 validity message. Default is `true`. + +> [!TIP] +> Besides the above options you can provide, in the `$options` argument, +> any valid HTML attributes that you want to pass to the created `form` +> element. + + + +### Getting form values from other values sources + +A FormHelper's values sources define where its rendered elements, such as +input-tags, receive their values from. + +The supported sources are `context`, `data` and `query`. You can use one +or more sources by setting `valueSources` option or by using `setValuesSource()`. +Any widgets generated by `FormHelper` will gather their values from the sources, +in the order you setup. + +By default `FormHelper` draws its values from `data` or `context`, i.e. it will +fetch data from `$request->getData()` or, if not present, from the active context's +data, that are the entity's data in the case of `EntityContext`. + +If however, you are building a form that needs to read from the query string, you can +change where `FormHelper` reads input data from: + +``` php +// Use query string instead of request data: +echo $this->Form->create($article, [ + 'type' => 'get', + 'valueSources' => ['query', 'context'], +]); + +// Same effect: +echo $this->Form + ->setValueSources(['query', 'context']) + ->create($articles, ['type' => 'get']); +``` + +When input data has to be processed by the entity, i.e. marshal transformations, table +query result or entity computations, and displayed after one or multiple form submissions +where request data is retained, you need to put `context` first: + +``` php +// Prioritize context over request data: +echo $this->Form->create($article, + 'valueSources' => ['context', 'data'], +]); +``` + +The value sources will be reset to the default `['data', 'context']` when `end()` +is called. + +### Changing the HTTP Method for a Form + +By using the `type` option you can change the HTTP method a form will use: + +``` php +echo $this->Form->create($article, ['type' => 'get']); +``` + +Output: + +``` html +
    +``` + +Specifying a `'file'` value for `type`, changes the form submission method +to 'post', and includes an `enctype` of "multipart/form-data" on the form tag. +This is to be used if there are any file elements inside the form. The absence +of the proper `enctype` attribute will cause the file uploads not to function. + +For example: + +``` php +echo $this->Form->create($article, ['type' => 'file']); +``` + +Output: + +``` html + +``` + +When using `'put'`, `'patch'` or `'delete'` as `'type'` values, your +form will be functionally equivalent to a 'post' form, but when submitted, the +HTTP request method will be overridden with 'PUT', 'PATCH' or 'DELETE', +respectively. +This allows CakePHP to emulate proper REST support in web browsers. + +### Setting a URL for the Form + +Using the `'url'` option allows you to point the form to a specific action in +your current controller or another controller in your application. + +For example, +if you'd like to point the form to the `publish()` action of the current +controller, you would supply an `$options` array, like the following: + +``` php +echo $this->Form->create($article, ['url' => ['action' => 'publish']]); +``` + +Output: + +``` html + +``` + +If the desired form action isn't in the current controller, you can specify +a complete URL for the form action. The supplied URL can be relative to your +CakePHP application: + +``` php +echo $this->Form->create(null, [ + 'url' => [ + 'controller' => 'Articles', + 'action' => 'publish', + ], +]); +``` + +Output: + +``` html + +``` + +Or you can point to an external domain: + +``` php +echo $this->Form->create(null, [ + 'url' => 'https://www.google.com/search', + 'type' => 'get', +]); +``` + +Output: + +``` html + +``` + +Use `'url' => false` if you don't want to output a URL as the form action. + +### Using Custom Validators + +Often models will have multiple validator sets, you can have FormHelper +mark fields required based on the specific validator your controller +action is going to apply. For example, your Users table has specific validation +rules that only apply when an account is being registered: + +``` php +echo $this->Form->create($user, [ + 'context' => ['validator' => 'register'], +]); +``` + +The above will use validation rules defined in the `register` validator, which +are defined by `UsersTable::validationRegister()`, for `$user` and all +related associations. If you are creating a form for associated entities, you +can define validation rules for each association by using an array: + +``` php +echo $this->Form->create($user, [ + 'context' => [ + 'validator' => [ + 'Users' => 'register', + 'Comments' => 'default', + ], + ], +]); +``` + +The above would use `register` for the user, and `default` for the user's +comments. FormHelper uses validators to generate HTML5 required attributes, +relevant ARIA attributes, and set error messages with the [browser validator API](https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation#Customized_error_messages) +. If you would like to disable HTML5 validation messages use: + +``` php +$this->Form->setConfig('autoSetCustomValidity', false); +``` + +This will not disable `required`/`aria-required` attributes. + +### Creating context classes + +While the built-in context classes are intended to cover the basic cases you'll +encounter you may need to build a new context class if you are using a different +ORM. In these situations you need to implement the +[Cake\View\Form\ContextInterface](https://api.cakephp.org/5.x/interface-Cake.View.Form.ContextInterface.html) . Once +you have implemented this interface you can wire your new context into the +FormHelper. It is often best to do this in a `View.beforeRender` event +listener, or in an application view class: + +``` php +$this->Form->addContextProvider('myprovider', function ($request, $data) { + if ($data['entity'] instanceof MyOrmClass) { + return new MyProvider($data); + } +}); +``` + +Context factory functions are where you can add logic for checking the form +options for the correct type of entity. If matching input data is found you can +return an object. If there is no match return null. + + + +## Creating Form Controls + +`method` Cake\\View\\Helper\\FormHelper::**control**(string $fieldName, array $options = []): string + +- `$fieldName` - A field name in the form `'Modelname.fieldname'`. +- `$options` - An optional array that can include both + [Control Specific Options](#control-specific-options), and options of the other methods (which + `control()` employs internally to generate various HTML elements) as + well as any valid HTML attributes. + +The `control()` method lets you generate complete form controls. These +controls will include a wrapping `div`, `label`, control widget, and validation error if +necessary. By using the metadata in the form context, this method will choose an +appropriate control type for each field. Internally `control()` uses the other +methods of FormHelper. + +> [!TIP] +> Please note that while the fields generated by the `control()` method are +> called generically "inputs" on this page, technically speaking, the +> `control()` method can generate not only all of the HTML `input` type +> elements, but also other HTML form elements such as `select`, +> `button`, `textarea`. + +By default the `control()` method will employ the following widget templates: + +``` text +'inputContainer' => '
    {{content}}
    ' +'input' => '' +'requiredClass' => 'required' +'containerClass' => 'input' +``` + +In case of validation errors it will also use: + +``` text +'inputContainerError' => '
    {{content}}{{error}}
    ' +``` + +The type of control created (when we provide no additional options to specify the +generated element type) is inferred via model introspection and +depends on the column datatype: + +Column Type +Resulting Form Field + +string, uuid (char, varchar, etc.) +text + +boolean, tinyint(1) +checkbox + +decimal +number + +float +number + +integer +number + +text +textarea + +text, with name of password, passwd +password + +text, with name of email +email + +text, with name of tel, telephone, or phone +tel + +date +date + +datetime, timestamp +datetime-local + +datetimefractional, timestampfractional +datetime-local + +time +time + +month +month + +year +select with years + +binary +file + +The `$options` parameter allows you to choose a specific control type if +you need to: + +``` php +echo $this->Form->control('published', ['type' => 'checkbox']); +``` + +> [!TIP] +> As a small subtlety, generating specific elements via the `control()` +> form method will always also generate the wrapping `div`, by default. +> Generating the same type of element via one of the specific form methods +> (e.g. `$this->Form->checkbox('published');`) in most cases won't generate +> the wrapping `div`. Depending on your needs you can use one or another. + +
    + +The wrapping `div` will have a `required` class name appended if the +validation rules for the model's field indicate that it is required and not +allowed to be empty. You can disable automatic `required` flagging using the +`'required'` option: + +``` php +echo $this->Form->control('title', ['required' => false]); +``` + +
    + +To skip browser validation triggering for the whole form you can set option +`'formnovalidate' => true` for the input button you generate using +`Cake\View\Helper\FormHelper::submit()` or set `'novalidate' => true` in options for `Cake\View\Helper\FormHelper::create()`. + +For example, let's assume that your Users model includes fields for a +*username* (varchar), *password* (varchar), *approved* (datetime) and +*quote* (text). You can use the `control()` method of the FormHelper to +create appropriate controls for all of these form fields: + +``` php +echo $this->Form->create($user); +// The following generates a Text input +echo $this->Form->control('username'); +// The following generates a Password input +echo $this->Form->control('password'); +// Assuming 'approved' is a datetime or timestamp field the following +//generates an input of type "datetime-local" +echo $this->Form->control('approved'); +// The following generates a Textarea element +echo $this->Form->control('quote'); + +echo $this->Form->button('Add'); +echo $this->Form->end(); +``` + +A more extensive example showing some options for a date field: + +``` php +echo $this->Form->control('birth_date', [ + 'label' => 'Date of birth', + 'min' => date('Y') - 70, + 'max' => date('Y') - 18, +]); +``` + +Besides the specific [Control Specific Options](#control-specific-options), +you also can specify any option accepted by corresponding specific method +for the chosen (or inferred by CakePHP) +control type and any HTML attribute (for instance `onfocus`). + +If you want to create a `select` form field while using a *belongsTo* (or +*hasOne*) relation, you can add the following to your UsersController +(assuming your User *belongsTo* Group): + +``` php +$this->set('groups', $this->Users->Groups->find('list')->all()); +``` + +Afterwards, add the following to your view template: + +``` php +echo $this->Form->control('group_id', ['options' => $groups]); +``` + +To make a `select` box for a *belongsToMany* Groups association you can +add the following to your UsersController: + +``` php +$this->set('groups', $this->Users->Groups->find('list')->all()); +``` + +Afterwards, add the following to your view template: + +``` php +echo $this->Form->control('groups._ids', ['options' => $groups]); +``` + +If your model name consists of two or more words (e.g. +"UserGroups"), when passing the data using `set()` you should name your +data in a pluralised and +[lower camelCased](https://en.wikipedia.org/wiki/Camel_case#Variations_and_synonyms) +format as follows: + +``` php +$this->set('userGroups', $this->UserGroups->find('list')->all()); +``` + +> [!NOTE] +> You should not use `FormHelper::control()` to generate submit buttons. Use +> `Cake\View\Helper\FormHelper::submit()` instead. + +### Field Naming Conventions + +When creating control widgets you should name your fields after the matching +attributes in the form's entity. For example, if you created a form for an +`$article` entity, you would create fields named after the properties. E.g. +`title`, `body` and `published`. + +You can create controls for associated models, or arbitrary models by passing in +`association.fieldname` as the first parameter: + +``` php +echo $this->Form->control('association.fieldname'); +``` + +Any dots in your field names will be converted into nested request data. For +example, if you created a field with a name `0.comments.body` you would get +a name attribute that looks like `0[comments][body]`. This convention matches +the conventions you use with the ORM. Details for the various association types +can be found in the [Associated Form Inputs](#associated-form-inputs) section. + +When creating datetime related controls, FormHelper will append a field-suffix. +You may notice additional fields named `year`, `month`, `day`, `hour`, +`minute`, or `meridian` being added. These fields will be automatically +converted into `DateTime` objects when entities are marshalled. + + + +### Options for Control + +`FormHelper::control()` supports a large number of options via its `$options` +argument. In addition to its own options, `control()` accepts options for the +inferred/chosen generated control types (e.g. for `checkbox` or `textarea`), +as well as HTML attributes. This subsection will cover the options specific to +`FormHelper::control()`. + +- `$options['type']` - A string that specifies the widget type + to be generated. In addition to the field types found in the + [Automagic Form Elements](#automagic-form-elements), you can also create `'file'`, + `'password'`, and any other type supported by HTML5. By specifying a + `'type'` you will force the type of the generated control, overriding model + introspection. Defaults to `null`. + + For example: + + ``` php + echo $this->Form->control('field', ['type' => 'file']); + echo $this->Form->control('email', ['type' => 'email']); + ``` + + Output: + + ``` html +
    + + +
    + + ``` + +- `$options['label']` - Either a string caption or an array of + [options for the label](#create-label). You can set this key to the + string you would like to be displayed within the label that usually + accompanies the `input` HTML element. Defaults to `null`. + + For example: + + ``` php + echo $this->Form->control('name', [ + 'label' => 'The User Alias' + ]); + ``` + + Output: + + ``` html +
    + + +
    + ``` + + Alternatively, set this key to `false` to disable the generation of the + `label` element. + + For example: + + ``` php + echo $this->Form->control('name', ['label' => false]); + ``` + + Output: + + ``` html +
    + +
    + ``` + + If the label is disabled, and a `placeholder` attribute is provided, the + generated input will have `aria-label` set. + + Set the `label` option to an array to provide additional options for the + `label` element. If you do this, you can use a `'text'` key in + the array to customize the label text. + + For example: + + ``` php + echo $this->Form->control('name', [ + 'label' => [ + 'class' => 'thingy', + 'text' => 'The User Alias' + ] + ]); + ``` + + Output: + + ``` html +
    + + +
    + ``` + +- `$options['options']` - You can provide in here an array containing + the elements to be generated for widgets such as `radio` or `select`, + which require an array of items as an argument (see + [Create Radio Button](#create-radio-button) and [Create Select Picker](#create-select-picker) for more details). + Defaults to `null`. + +- `$options['error']` - Using this key allows you to override the default + model error messages and can be used, for example, to set i18n messages. To + disable the error message output & field classes set the `'error'` key to + `false`. Defaults to `null`. + + For example: + + ``` php + echo $this->Form->control('name', ['error' => false]); + ``` + + To override the model error messages use an array with + the keys matching the original validation error messages. + + For example: + + ``` php + $this->Form->control('name', [ + 'error' => ['Not long enough' => __('This is not long enough')] + ]); + ``` + + As seen above you can set the error message for each validation + rule you have in your models. In addition you can provide i18n + messages for your forms. + + To disable the HTML entity encoding for error messages only, the `'escape'` + sub key can be used: + + ``` php + $this->Form->control('name', [ + 'error' => ['escape' => false], + ]); + ``` + +- `$options['nestedInput']` - Used with checkboxes and radio buttons. + Controls whether the input element is generated + inside or outside the `label` element. When `control()` generates a + checkbox or a radio button, you can set this to `false` to force the + generation of the HTML `input` element outside of the `label` element. + + On the other hand you can set this to `true` for any control type to force the + generated input element inside the label. If you change this for radio buttons + then you might want to also modify the default [radioWrapper](#create-radio-button) + template to add a wrapping `div`. Depending on the generated control type it + defaults to `true` or `false`. + + If you want to disable the nesting of checkbox and radio inputs globally you can + set `nestedCheckboxAndRadio` option of `FormHelper` to `false`. + +- `$options['templates']` - The templates you want to use for this input. Any + specified templates will be merged on top of the already loaded templates. + This option can be either a filename (without extension) in `/config` that + contains the templates you want to load, or an array of templates to use. + +- `$options['labelOptions']` - Set this to `false` to disable labels around + nestedWidgets or set it to an array of attributes to be provided to the + `label` tag. + +- `$options['readonly']` - Set the field to `readonly` in form. + + For example: + + ``` php + echo $this->Form->control('name', ['readonly' => true]); + ``` + +## Generating Specific Types of Controls + +In addition to the generic `control()` method, `FormHelper` has specific +methods for generating a number of different types of controls. These can be used +to generate just the control widget itself, and combined with other methods like +`Cake\View\Helper\FormHelper::label()` and +`Cake\View\Helper\FormHelper::error()` to generate fully custom +form layouts. + + + +### Common Options For Specific Controls + +Many of the various control element methods support a common set of options which, +depending on the form method used, must be provided inside the `$options` or +in the `$attributes` array argument. All of these options are also supported +by the `control()` method. +To reduce repetition, the common options shared by all control methods are +as follows: + +- `'id'` - Set this key to force the value of the DOM id for the control. + This will override the `'idPrefix'` that may be set. + +- `'default'` - Used to set a default value for the control field. The + value is used if the data passed to the form does not contain a value for the + field (or if no data is passed at all). If no default value is provided, the + column's default value will be used. + + Example usage: + + ``` php + echo $this->Form->text('ingredient', ['default' => 'Sugar']); + ``` + + Example with `select` field (size "Medium" will be selected as + default): + + ``` php + $sizes = ['s' => 'Small', 'm' => 'Medium', 'l' => 'Large']; + echo $this->Form->select('size', $sizes, ['default' => 'm']); + ``` + + > [!NOTE] + > You cannot use `default` to check a checkbox - instead you might + > set the value in `$this->request->getData()` in your controller, + > or set the control option `'checked'` to `true`. + > + > Beware of using `false` to assign a default value. A `false` value is + > used to disable/exclude options of a control field, so `'default' => false` + > would not set any value at all. Instead use `'default' => 0`. + +- `'value'` - Used to set a specific value for the control field. This + will override any value that may else be injected from the context, such as + Form, Entity or `request->getData()` etc. + + > [!NOTE] + > If you want to set a field to not render its value fetched from + > context or valuesSource you will need to set `'value'` to `''` + > (instead of setting it to `null`). + +In addition to the above options, you can mixin any HTML attribute you wish to +use. Any non-special option name will be treated as an HTML attribute, and +applied to the generated HTML control element. + +## Creating Input Elements + +The rest of the methods available in the FormHelper are for +creating specific form elements. Many of these methods also make +use of a special `$options` or `$attributes` parameter. In this case, +however, this parameter is used primarily to specify HTML tag attributes +(such as the value or DOM id of an element in the form). + +### Creating Text Inputs + +`method` Cake\\View\\Helper\\FormHelper::**text**(string $name, array $options) + +- `$name` - A field name in the form `'Modelname.fieldname'`. +- `$options` - An optional array including any of the + [General Control Options](#general-control-options) as well as any valid HTML attributes. + +Creates a simple `input` HTML element of `text` type. + +For example: + +``` php +echo $this->Form->text('username', ['class' => 'users']); +``` + +Will output: + +``` html + +``` + +### Creating Password Inputs + +`method` Cake\\View\\Helper\\FormHelper::**password**(string $fieldName, array $options) + +- `$fieldName` - A field name in the form `'Modelname.fieldname'`. +- `$options` - An optional array including any of the + [General Control Options](#general-control-options) as well as any valid HTML attributes. + +Creates a simple `input` element of `password` type. + +For example: + +``` php +echo $this->Form->password('password'); +``` + +Will output: + +``` html + +``` + +### Creating Hidden Inputs + +`method` Cake\\View\\Helper\\FormHelper::**hidden**(string $fieldName, array $options): string + +- `$fieldName` - A field name in the form `'Modelname.fieldname'`. +- `$options` - An optional array including any of the + [General Control Options](#general-control-options) as well as any valid HTML attributes. + +Creates a hidden form input. + +For example: + +``` php +echo $this->Form->hidden('id'); +``` + +Will output: + +``` html + +``` + +### Creating Textareas + +`method` Cake\\View\\Helper\\FormHelper::**textarea**(string $fieldName, array $options): string + +- `$fieldName` - A field name in the form `'Modelname.fieldname'`. +- `$options` - An optional array including any of the + [General Control Options](#general-control-options), of the specific textarea options (see below) + as well as any valid HTML attributes. + +Creates a textarea control field. The default widget template used is: + +``` text +'textarea' => '' +``` + +For example: + +``` php +echo $this->Form->textarea('notes'); +``` + +Will output: + +``` html + +``` + +If the form is being edited (i.e. the array `$this->request->getData()` +contains the information previously saved for the `User` entity), the value +corresponding to `notes` field will automatically be added to the HTML +generated. + +Example: + +``` html + +``` + +**Options for Textarea** + +In addition to the [General Control Options](#general-control-options), `textarea()` supports a +couple of specific options: + +- `'escape'` - Determines whether or not the contents of the textarea should + be escaped. Defaults to `true`. + + For example: + + ``` php + echo $this->Form->textarea('notes', ['escape' => false]); + // OR.... + echo $this->Form->control('notes', ['type' => 'textarea', 'escape' => false]); + ``` + +- `'rows', 'cols'` - You can use these two keys to set the HTML attributes + which specify the number of rows and columns for the `textarea` field. + + For example: + + ``` php + echo $this->Form->textarea('comment', ['rows' => '5', 'cols' => '5']); + ``` + + Output: + + ``` html + + ``` + +### Creating Select, Checkbox and Radio Controls + +These controls share some commonalities and a few options and thus, they are +all grouped in this subsection for easier reference. + + + +#### Options for Select, Checkbox and Radio Controls + +You can find below the options which are shared by `select()`, +`checkbox()` and `radio()` (the options particular only to one of the +methods are described in each method's own section.) + +- `'value'` - Sets or selects the value of the affected element(s): + + - For checkboxes, it sets the HTML `'value'` attribute assigned + to the `input` element to whatever you provide as value. + + - For radio buttons or select pickers it defines which element will be + selected when the form is rendered (in this case `'value'` must be + assigned a valid, existent element value). May also be used in + combination with any select-type control, + such as `date()`, `time()`, `dateTime()`: + + ``` php + echo $this->Form->time('close_time', [ + 'value' => '13:30:00', + ]); + ``` + + > [!NOTE] + > The `'value'` key for `date()` and `dateTime()` controls may also have + > as value a UNIX timestamp, or a DateTime object. + + For a `select` control where you set the `'multiple'` attribute to + `true`, you can provide an array with the values you want to select + by default: + + ``` php + // HTML ' +``` + +May also use: + +``` text +'optgroup' => '{{content}}' +'selectMultiple' => '' +``` + +**Attributes for Select Pickers** + +- `'multiple'` - If set to `true` allows multiple selections in the select + picker. If set to `'checkbox'`, multiple checkboxes will be created instead. + Defaults to `null`. +- `'escape'` - Boolean. If `true` the contents of the `option` elements + inside the select picker will be HTML entity encoded. Defaults to `true`. +- `'val'` - Allows preselecting a value in the select picker. +- `'disabled'` - Controls the `disabled` attribute. If set to `true` + disables the whole select picker. If set to an array it will disable + only those specific `option` elements whose values are provided in + the array. + +The `$options` argument allows you to manually specify +the contents of the `option` elements of a `select` control. + +For example: + +``` php +echo $this->Form->select('field', [1, 2, 3, 4, 5]); +``` + +Output: + +``` html + +``` + +The array for `$options` can also be supplied as key-value pairs. + +For example: + +``` php +echo $this->Form->select('field', [ + 'Value 1' => 'Label 1', + 'Value 2' => 'Label 2', + 'Value 3' => 'Label 3' +]); +``` + +Output: + +``` html + +``` + +If you would like to generate a `select` with optgroups, just pass +data in hierarchical format (nested array). This works on multiple +checkboxes and radio buttons too, but instead of `optgroup` it wraps +the elements in `fieldset` elements. + +For example: + +``` php +$options = [ + 'Group 1' => [ + 'Value 1' => 'Label 1', + 'Value 2' => 'Label 2', + ], + 'Group 2' => [ + 'Value 3' => 'Label 3', + ], +]; +echo $this->Form->select('field', $options); +``` + +Output: + +``` html + +``` + +To generate HTML attributes within an `option` tag: + +``` php +$options = [ + ['text' => 'Description 1', 'value' => 'value 1', 'attr_name' => 'attr_value 1'], + ['text' => 'Description 2', 'value' => 'value 2', 'attr_name' => 'attr_value 2'], + ['text' => 'Description 3', 'value' => 'value 3', 'other_attr_name' => 'other_attr_value'], +]; +echo $this->Form->select('field', $options); +``` + +Output: + +``` html + +``` + +**Controlling Select Pickers via Attributes** + +By using specific options in the `$attributes` parameter you can control +certain behaviors of the `select()` method. + +- `'empty'` - Set the `'empty'` key in the `$attributes` argument + to `true` (the default value is `false`) to add a blank option with an + empty value at the top of your dropdown list. + + For example: + + ``` php + $options = ['M' => 'Male', 'F' => 'Female']; + echo $this->Form->select('gender', $options, ['empty' => true]); + ``` + + Will output: + + ``` html + + ``` + +- `'escape'` - The `select()` method allows for an attribute + called `'escape'` which accepts a boolean value and determines + whether to HTML entity encode the contents of the `select`'s `option` + elements. + + For example: + + ``` php + // This will prevent HTML-encoding the contents of each option element + $options = ['M' => 'Male', 'F' => 'Female']; + echo $this->Form->select('gender', $options, ['escape' => false]); + ``` + +- `'multiple'` - If set to `true`, the select picker will allow + multiple selections. + + For example: + + ``` php + echo $this->Form->select('field', $options, ['multiple' => true]); + ``` + + Alternatively, set `'multiple'` to `'checkbox'` in order to output a + list of related checkboxes: + + ``` php + $options = [ + 'Value 1' => 'Label 1', + 'Value 2' => 'Label 2' + ]; + echo $this->Form->select('field', $options, [ + 'multiple' => 'checkbox' + ]); + ``` + + Output: + + ``` html + +
    + +
    +
    + +
    + ``` + +- `'disabled'` - This option can be set in order to disable all or some + of the `select`'s `option` items. To disable all items set `'disabled'` + to `true`. To disable only certain items, assign to `'disabled'` + an array containing the keys of the items to be disabled. + + For example: + + ``` php + $options = [ + 'M' => 'Masculine', + 'F' => 'Feminine', + 'N' => 'Neuter' + ]; + echo $this->Form->select('gender', $options, [ + 'disabled' => ['M', 'N'] + ]); + ``` + + Will output: + + ``` html + + ``` + + This option also works when `'multiple'` is set to `'checkbox'`: + + ``` php + $options = [ + 'Value 1' => 'Label 1', + 'Value 2' => 'Label 2' + ]; + echo $this->Form->select('field', $options, [ + 'multiple' => 'checkbox', + 'disabled' => ['Value 1'] + ]); + ``` + + Output: + + ``` html + +
    + +
    +
    + +
    + ``` + +### Creating File Inputs + +`method` Cake\\View\\Helper\\FormHelper::**file**(string $fieldName, array $options): string + +- `$fieldName` - A field name in the form `'Modelname.fieldname'`. +- `$options` - An optional array including any of the + [General Control Options](#general-control-options) as well as any valid HTML attributes. + +Creates a file upload field in the form. +The widget template used by default is: + +``` text +'file' => '' +``` + +To add a file upload field to a form, you must first make sure that +the form enctype is set to `'multipart/form-data'`. + +So start off with a `create()` method such as the following: + +``` php +echo $this->Form->create($document, ['enctype' => 'multipart/form-data']); +// OR +echo $this->Form->create($document, ['type' => 'file']); +``` + +Next add a line that looks like either of the following two lines +to your form's view template file: + +``` php +echo $this->Form->control('submittedfile', [ + 'type' => 'file' +]); + +// OR +echo $this->Form->file('submittedfile'); +``` + +> [!NOTE] +> Due to the limitations of HTML itself, it is not possible to put +> default values into input fields of type 'file'. Each time the form +> is displayed, the value inside will be empty. + +To prevent the `submittedfile` from being over-written as blank, remove it +from `$_accessible`. Alternatively, you can unset the index by using +`beforeMarshal`: + +``` php +public function beforeMarshal(\Cake\Event\EventInterface $event, \ArrayObject $data, \ArrayObject $options): void +{ + if ($data['submittedfile'] === '') { + unset($data['submittedfile']); + } +} +``` + +Upon submission, file fields can be accessed though `UploadedFileInterface` +objects on the request. To move uploaded files to a permanent location, you can +use: + +``` php +$fileobject = $this->request->getData('submittedfile'); +$destination = UPLOAD_DIRECTORY . $fileobject->getClientFilename(); + +// Existing files with the same name will be replaced. +$fileobject->moveTo($destination); +``` + +> [!NOTE] +> When using `$this->Form->file()`, remember to set the form +> encoding-type, by setting the `'type'` option to `'file'` in +> `$this->Form->create()`. + + + +### Creating Date & Time Related Controls + +`method` Cake\\View\\Helper\\FormHelper::**dateTime**(string $fieldName, array $options = []): string + +- `$fieldName` - A string that will be used as a prefix for the HTML `name` + attribute of the `select` elements. +- `$options` - An optional array including any of the + [General Control Options](#general-control-options) as well as any valid HTML attributes. + +This method will generate an input tag with type "datetime-local". + +For example : + +``` php +form->dateTime('registered') ?> +``` + +Output: + +``` html + +``` + +The value for the input can be any valid datetime string or `DateTime` instance. + +For example : + +``` php +form->dateTime('registered', ['value' => new DateTime()]) ?> +``` + +Output: + +``` html + +``` + +#### Creating Date Controls + +`method` Cake\\View\\Helper\\FormHelper::**date**(string $fieldName, array $options = []): string + +- `$fieldName` - A field name that will be used as a prefix for the HTML + `name` attribute of the `select` elements. +- `$options` - An optional array including any of the + [General Control Options](#general-control-options) as well as any valid HTML attributes. + +This method will generate an input tag with type "date". + +For example : + +``` php +form->date('registered') ?> +``` + +Output: + +``` html + +``` + +#### Creating Time Controls + +`method` Cake\\View\\Helper\\FormHelper::**time**(string $fieldName, array $options = []): string + +- `$fieldName` - A field name that will be used as a prefix for the HTML + `name` attribute of the `select` elements. +- `$options` - An optional array including any of the + [General Control Options](#general-control-options) as well as any valid HTML attributes. + +This method will generate an input tag with type "time". + +For example : + +``` php +echo $this->Form->time('released'); +``` + +Output: + +``` html + +``` + +#### Creating Month Controls + +`method` Cake\\View\\Helper\\FormHelper::**month**(string $fieldName, array $attributes): string + +- `$fieldName` - A field name that will be used as a prefix for the HTML + `name` attribute of the `select` element. +- `$options` - An optional array including any of the + [General Control Options](#general-control-options) as well as any valid HTML attributes. + +This method will generate an input tag with type "month". + +For example: + +``` php +echo $this->Form->month('mob'); +``` + +Will output: + +``` html + +``` + +#### Creating Year Controls + +`method` Cake\\View\\Helper\\FormHelper::**year**(string $fieldName, array $options = []): string + +- `$fieldName` - A field name that will be used as a prefix for the HTML + `name` attribute of the `select` element. +- `$options` - An optional array including any of the + [General Control Options](#general-control-options) as well as any valid HTML attributes. + Other valid options are: + - `min`: The lowest value to use in the year select picker. + - `max`: The maximum value to use in the year select picker. + - `order`: The order of year values in the year select picker. + Possible values are `'asc'` and `'desc'`. Defaults to `'desc'`. + +Creates a `select` element populated with the years from `min` to `max` +(when these options are provided) or else with values starting from -5 years +to +5 years counted from today. Additionally, HTML attributes may be supplied +in `$options`. If `$options['empty']` is `false`, the select picker will +not include an empty item in the list. + +For example, to create a year range from 2000 to the current year you +would do the following: + +``` php +echo $this->Form->year('purchased', [ + 'min' => 2000, + 'max' => date('Y') +]); +``` + +If it was 2009, you would get the following: + +``` html + +``` + + + +## Creating Labels + +`method` Cake\\View\\Helper\\FormHelper::**label**(string $fieldName, string $text, array $options): string + +- `$fieldName` - A field name in the form `'Modelname.fieldname'`. +- `$text` - An optional string providing the label caption text. +- `$options` - Optional. Array containing any of the + [General Control Options](#general-control-options) as well as any valid HTML attributes. + +Creates a `label` element. The argument `$fieldName` is used for generating +the HTML `for` attribute of the element; if `$text` is undefined, +`$fieldName` will also be used to inflect the label's `text` attribute. + +For example: + +``` php +echo $this->Form->label('name'); +echo $this->Form->label('name', 'Your username'); +``` + +Output: + +``` html + + +``` + +With the third parameter `$options` you can set the id or class: + +``` php +echo $this->Form->label('name', null, ['id' => 'user-label']); +echo $this->Form->label('name', 'Your username', ['class' => 'highlight']); +``` + +Output: + +``` html + + +``` + +## Displaying and Checking Errors + +FormHelper exposes a couple of methods that allow us to easily check for +field errors and when necessary display customized error messages. + +### Displaying Errors + +`method` Cake\\View\\Helper\\FormHelper::**error**(string $fieldName, mixed $text, array $options): string + +- `$fieldName` - A field name in the form `'Modelname.fieldname'`. +- `$text` - Optional. A string or array providing the error message(s). If an + array, then it should be a hash of key names =\> messages. Defaults to + `null`. +- `$options` - An optional array that can only contain a boolean with the key + `'escape'`, which will define whether to HTML escape the + contents of the error message. Defaults to `true`. + +Shows a validation error message, specified by `$text`, for the given +field, in the event that a validation error has occurred. If `$text` is not +provided then the default validation error message for that field will be used. + +Uses the following template widgets: + +``` text +'error' => '
    {{content}}
    ' +'errorList' => '
      {{content}}
    ' +'errorItem' => '
  • {{text}}
  • ' +``` + +The `'errorList'` and `'errorItem'` templates are used to format mutiple +error messages per field. + +Example: + +``` php +// If in TicketsTable you have a 'notEmpty' validation rule: +public function validationDefault(Validator $validator): Validator +{ + $validator + ->requirePresence('ticket', 'create') + ->notEmpty('ticket'); +} + +// And inside templates/Tickets/add.php you have: +echo $this->Form->text('ticket'); + +if ($this->Form->isFieldError('ticket')) { + echo $this->Form->error('ticket', 'Completely custom error message!'); +} +``` + +If you would click the *Submit* button of your form without providing a value +for the *Ticket* field, your form would output: + +``` html + +
    Completely custom error message!
    +``` + +> [!NOTE] +> When using `Cake\View\Helper\FormHelper::control()`, errors are +> rendered by default, so you don't need to use `isFieldError()` or call +> `error()` manually. + +> [!TIP] +> If you use a certain model field to generate multiple form fields via +> `control()`, and you want the same validation error message displayed for +> each one, you will probably be better off defining a custom error message +> inside the respective [validator rules](../../core-libraries/validation#creating-validators). + +### Checking for Errors + +`method` Cake\\View\\Helper\\FormHelper::**isFieldError**(string $fieldName): bool + +- `$fieldName` - A field name in the form `'Modelname.fieldname'`. + +Returns `true` if the supplied `$fieldName` has an active validation +error, otherwise returns `false`. + +Example: + +``` php +if ($this->Form->isFieldError('gender')) { + echo $this->Form->error('gender'); +} +``` + + + +### Displaying validation messages in HTML5 validity messages + +If the `autoSetCustomValidity` FormHelper option is set to `true`, error messages for +the field's required and notBlank validation rules will be used in lieu of the default +browser HTML5 required messages. Enabling the option will add the `onvalid` and `oninvalid` +event attributes to your fields, for example: + +``` html + +``` + +If you want to manually set those events with custom JavaScript, you can set the `autoSetCustomValidity` +option to `false` and use the special `customValidityMessage` template variable instead. This +template variable is added when a field is required: + +``` text +// example template +[ + 'input' => '', +] + +// would create an input like this + +``` + +You could then use JavaScript to set the `onvalid` and `oninvalid` events as you like. + +## Creating Buttons and Submit Elements + +### Creating Submit Elements + +`method` Cake\\View\\Helper\\FormHelper::**submit**(string $caption, array $options): string + +- `$caption` - An optional string providing the button's text caption or a + path to an image. Defaults to `'Submit'`. +- `$options` - An optional array including any of the + [General Control Options](#general-control-options), or of the specific submit options (see below) + as well as any valid HTML attributes. + +Creates an `input` element of `submit` type, with `$caption` as value. +If the supplied `$caption` is a URL pointing to an image (i.e. if the string +contains '://' or contains any of the extensions '.jpg, .jpe, .jpeg, .gif'), +an image submit button will be generated, using the specified image if it +exists. If the first character is '/' then the image path is relative to +*webroot*, else if the first character is not '/' then the image path is +relative to *webroot/img*. + +By default it will use the following widget templates: + +``` text +'inputSubmit' => '' +'submitContainer' => '
    {{content}}
    ' +``` + +**Options for Submit** + +- `'type'` - Set this option to `'reset'` in order to generate reset buttons. + It defaults to `'submit'`. +- `'templateVars'` - Set this array to provide additional template variables + for the input element and its container. +- Any other provided attributes will be assigned to the `input` element. + +The following: + +``` php +echo $this->Form->submit('Click me'); +``` + +Will output: + +``` html +
    +``` + +You can pass a relative or absolute URL of an image to the +caption parameter instead of the caption text: + +``` php +echo $this->Form->submit('ok.png'); +``` + +Will output: + +``` html +
    +``` + +Submit inputs are useful when you only need basic text or images. If you need +more complex button content you should use `button()`. + +### Creating Button Elements + +`method` Cake\\View\\Helper\\FormHelper::**button**(string $title, array $options = []): string + +- `$title` - Mandatory string providing the button's text caption. +- `$options` - An optional array including any of the + [General Control Options](#general-control-options), or of the specific button options (see below) + as well as any valid HTML attributes. + +Creates an HTML button with the specified title and a default type +of `'button'`. + +**Options for Button** + +- `'type'` - You can set this to one of the following three + possible values: + 1. `'submit'` - Similarly to the `$this->Form->submit()` method it will + create a submit button. However this won't generate a wrapping `div` + as `submit()` does. This is the default type. + 2. `'reset'` - Creates a form reset button. + 3. `'button'` - Creates a standard push button. +- `'escapeTitle'` - Boolean. If set to `true` it will HTML encode + the value provided inside `$title`. Defaults to `true`. +- `'escape'` - Boolean. If set to `true` it will HTML encode + all the HTML attributes generated for the button. Defaults to `true`. +- `'confirm'` - The confirmation message to display on click. Defaults to + `null`. + +For example: + +``` php +echo $this->Form->button('A Button'); +echo $this->Form->button('Another Button', ['type' => 'button']); +echo $this->Form->button('Reset the Form', ['type' => 'reset']); +echo $this->Form->button('Submit Form', ['type' => 'submit']); +``` + +Will output: + +``` html + + + + +``` + +Example use of the `'escapeTitle'` option: + +``` php +// Will render unescaped HTML. +echo $this->Form->button('Submit Form', [ + 'type' => 'submit', + 'escapeTitle' => false, +]); +``` + +## Closing the Form + +`method` Cake\\View\\Helper\\FormHelper::**end**($secureAttributes = []): string + +- `$secureAttributes` - Optional. Allows you to provide secure attributes + which will be passed as HTML attributes into the hidden input elements + generated for the FormProtectionComponent. + +The `end()` method closes and completes a form. Often, `end()` will only +output a closing form tag, but using `end()` is a good practice as it +enables FormHelper to insert the hidden form elements that +`Cake\Controller\Component\FormProtectionComponent` requires: + +``` php +Form->create(); ?> + + + +Form->end(); ?> +``` + +If you need to add additional attributes to the generated hidden inputs +you can use the `$secureAttributes` argument. + +For example: + +``` php +echo $this->Form->end(['data-type' => 'hidden']); +``` + +Will output: + +``` html +
    + + +
    +``` + +> [!NOTE] +> If you are using +> `Cake\Controller\Component\FormProtectionComponent` in your +> application you should always end your forms with `end()`. + +## Creating Standalone Buttons and POST Links + +### Creating POST Buttons + +`method` Cake\\View\\Helper\\FormHelper::**postButton**(string $title, mixed $url, array $options = []): string + +- `$title` - Mandatory string providing the button's text caption. By default + not HTML encoded. +- `$url` - The URL of the form provided as a string or as array. +- `$options` - An optional array including any of the + [General Control Options](#general-control-options), or of the specific options (see below) as well + as any valid HTML attributes. + +Creates a ` +
    + + + +
    +
    +``` + +Since this method generates a `form` element, do not use this method in an +already opened form. Instead use +`Cake\View\Helper\FormHelper::submit()` +or `Cake\View\Helper\FormHelper::button()` to create buttons +inside opened forms. + +### Creating POST Links + +`method` Cake\\View\\Helper\\FormHelper::**postLink**(string $title, array|string|null $url = null, array $options = []): string + +- `$title` - Mandatory string providing the text to be wrapped in `` + tags. +- `$url` - Optional. String or array which contains the URL + of the form (Cake-relative or external URL starting with `http://`). +- `$options` - An optional array including any of the + [General Control Options](#general-control-options), or of the specific options (see below) as well + as any valid HTML attributes. + +Creates an HTML link, but accesses the URL using the method you specify +(defaults to POST). Requires JavaScript to be enabled in browser: + +``` php +// In your template, to delete an article, for example +Form->postLink( + 'Delete', + ['action' => 'delete', $article->id], + ['confirm' => 'Are you sure?']) +?> +``` + +**Options for POST Link** + +- `'data'` - Array with key/value to pass in hidden input. +- `'method'` - Request method to use. For example, setting it to `'delete'` + will simulate a HTTP/1.1 DELETE request. Defaults to `'post'`. +- `'confirm'` - The confirmation message to display on click. Defaults to + `null`. +- `'block'` - Set this option to `true` to append the form to view block + `'postLink'` or provide a custom block name. Defaults to `null`. +- Also, the `postLink` method will accept the options which are valid for + the `link()` method. + +This method creates a `
    ` element. If you want to use this method +inside of an existing form, you must use the `block` option so that the +new form is being set to a [view block](../../views#view-blocks) that can be +rendered outside of the main form. + +If all you are looking for is a button to submit your form, then you should +use `Cake\View\Helper\FormHelper::button()` or +`Cake\View\Helper\FormHelper::submit()` instead. + +> [!NOTE] +> Be careful to not put a postLink inside an open form. Instead use the +> `block` option to buffer the form into a [view block](../../views#view-blocks) + + + +### Creating DELETE Links + +`method` Cake\\View\\Helper\\FormHelper::**deleteLink**(string $title, array|string|null $url = null, array $options = []): string + +- `$title` - Mandatory string providing the text to be wrapped in `` + tags. +- `$url` - Optional. String or array which contains the URL + of the form (Cake-relative or external URL starting with `http://`). +- `$options` - An optional array including any of the + [General Control Options](#general-control-options), or of the specific options (see below) as well + as any valid HTML attributes. + +Creates an HTML link, but accesses the URL using the method you specify +(defaults to DELETE). Requires JavaScript to be enabled in browser: + +``` php +// In your template, to delete an article, for example +Form->deleteLink( + 'Delete', + ['action' => 'delete', $article->id], + ['confirm' => 'Are you sure?']) +?> +``` + +::: info Added in version 5.2.0 +The `deleteLink` method was added. +::: + +## Customizing the Templates FormHelper Uses + +Like many helpers in CakePHP, FormHelper uses string templates to format the +HTML it creates. While the default templates are intended to be a reasonable set +of defaults, you may need to customize the templates to suit your application. + +To change the templates when the helper is loaded you can set the `'templates'` +option when including the helper in your controller: + +``` php +// In a View class +$this->loadHelper('Form', [ + 'templates' => 'app_form', +]); +``` + +This would load the tags found in **config/app_form.php**. This file should +contain an array of templates *indexed by name*: + +``` text +// in config/app_form.php +return [ + 'inputContainer' => '
    {{content}}
    ', +]; +``` + +Any templates you define will replace the default ones included in the helper. +Templates that are not replaced, will continue to use the default values. + +You can also change the templates at runtime using the `setTemplates()` method: + +``` php +$myTemplates = [ + 'inputContainer' => '
    {{content}}
    ', +]; +$this->Form->setTemplates($myTemplates); +``` + +> [!WARNING] +> Template strings containing a percentage sign (`%`) need special attention; +> you should prefix this character with another percentage so it looks like +> `%%`. The reason is that internally templates are compiled to be used with +> `sprintf()`. Example: `'
    {{content}}
    '` + +### List of Templates + +The list of default templates, their default format and the variables they +expect can be found in the +[FormHelper API documentation](https://api.cakephp.org/5.x/class-Cake.View.Helper.FormHelper.html#%24_defaultConfig). + +#### Using Distinct Custom Control Containers + +In addition to these templates, the `control()` method will attempt to use +distinct templates for each control container. For example, when creating +a datetime control the `datetimeContainer` will be used if it is present. +If that container is missing the `inputContainer` template will be used. + +For example: + +``` php +// Add custom radio wrapping HTML +$this->Form->setTemplates([ + 'radioContainer' => '
    {{content}}
    ' +]); + +// Create a radio set with our custom wrapping div. +echo $this->Form->control('email_notifications', [ + 'options' => ['y', 'n'], + 'type' => 'radio' +]); +``` + +#### Using Distinct Custom Form Groups + +Similar to controlling containers, the `control()` method will also attempt to use +distinct templates for each form group. A form group is a combo of label and +control. For example, when creating a radio control the `radioFormGroup` will be +used if it is present. If that template is missing by default each set of `label` +& `input` is rendered using the default `formGroup` template. + +For example: + +``` php +// Add custom radio form group +$this->Form->setTemplates([ + 'radioFormGroup' => '
    {{label}}{{input}}
    ' +]); +``` + +### Adding Additional Template Variables to Templates + +You can add additional template placeholders in custom templates, and populate +those placeholders when generating controls. + +For example: + +``` php +// Add a template with the help placeholder. +$this->Form->setTemplates([ + 'inputContainer' => '
    + {{content}} {{help}}
    ' +]); + +// Generate an input and populate the help variable +echo $this->Form->control('password', [ + 'templateVars' => ['help' => 'At least 8 characters long.'], +]); +``` + +Output: + +``` html +
    + + + At least 8 characters long. +
    +``` + +### Moving Checkboxes & Radios Outside of a Label + +By default CakePHP nests checkboxes created via `control()` and radio buttons +created by both `control()` and `radio()` within label elements. +This helps make it easier to integrate popular CSS frameworks. If you need to +place checkbox/radio inputs outside of the label you can do so by modifying the +templates: + +``` php +$this->Form->setTemplates([ + 'nestingLabel' => '{{hidden}}{{input}}{{text}}', + 'formGroup' => '{{input}}{{label}}', +]); +``` + +This will make radio buttons and checkboxes render outside of their labels. + +## Generating Entire Forms + +### Creating Multiple Controls + +`method` Cake\\View\\Helper\\FormHelper::**controls**(array $fields = [], array $options = []): string + +- `$fields` - An array of fields to generate. Allows setting + custom types, labels and other options for each specified field. +- `$options` - Optional. An array of options. Valid keys are: + 1. `'fieldset'` - Set this to `false` to disable the fieldset. + If empty, the fieldset will be enabled. Can also be an array of parameters + to be applied as HTML attributes to the `fieldset` tag. + 2. `legend` - String used to customize the `legend` text. Set this to + `false` to disable the legend for the generated input set. + +Generates a set of controls for the given context wrapped in a +`fieldset`. You can specify the generated fields by including them: + +``` php +echo $this->Form->controls([ + 'name', + 'email' +]); +``` + +You can customize the legend text using an option: + +``` php +echo $this->Form->controls($fields, ['legend' => 'Update news post']); +``` + +You can customize the generated controls by defining additional options in the +`$fields` parameter: + +``` php +echo $this->Form->controls([ + 'name' => ['label' => 'custom label'], +]); +``` + +When customizing, `$fields`, you can use the `$options` parameter to +control the generated legend/fieldset. + +For example: + +``` php +echo $this->Form->controls( + [ + 'name' => ['label' => 'custom label'], + ], + [ + 'legend' => 'Update your post', + ] +); +``` + +If you disable the `fieldset`, the `legend` will not print. + +### Creating Controls for a Whole Entity + +`method` Cake\\View\\Helper\\FormHelper::**allControls**(array $fields, array $options = []): string + +- `$fields` - Optional. An array of customizations for the fields that will + be generated. Allows setting custom types, labels and other options. +- `$options` - Optional. An array of options. Valid keys are: + 1. `'fieldset'` - Set this to `false` to disable the fieldset. + If empty, the fieldset will be enabled. Can also be an array of + parameters to be applied as HTMl attributes to the `fieldset` tag. + 2. `legend` - String used to customize the `legend` text. Set this to + `false` to disable the legend for the generated control set. + +This method is closely related to `controls()`, however the `$fields` argument +is defaulted to *all* fields in the current top-level entity. To exclude +specific fields from the generated controls, set them to `false` in the +`$fields` parameter: + +``` php +echo $this->Form->allControls(['password' => false]); +``` + +
    + +## Creating Inputs for Associated Data + +Creating forms for associated data is straightforward and is closely related to +the paths in your entity's data. Assuming the following table relations: + +- Authors HasOne Profiles +- Authors HasMany Articles +- Articles HasMany Comments +- Articles BelongsTo Authors +- Articles BelongsToMany Tags + +If we were editing an article with its associations loaded we could +create the following controls: + +``` php +$this->Form->create($article); + +// Article controls. +echo $this->Form->control('title'); + +// Author controls (belongsTo) +echo $this->Form->control('author.id'); +echo $this->Form->control('author.first_name'); +echo $this->Form->control('author.last_name'); + +// Author profile (belongsTo + hasOne) +echo $this->Form->control('author.profile.id'); +echo $this->Form->control('author.profile.username'); + +// Tags controls (belongsToMany) +// as separate inputs +echo $this->Form->control('tags.0.id'); +echo $this->Form->control('tags.0.name'); +echo $this->Form->control('tags.1.id'); +echo $this->Form->control('tags.1.name'); + +// Inputs for the joint table (articles_tags) +echo $this->Form->control('tags.0._joinData.starred'); +echo $this->Form->control('tags.1._joinData.starred'); + +// Comments controls (hasMany) +echo $this->Form->control('comments.0.id'); +echo $this->Form->control('comments.0.comment'); +echo $this->Form->control('comments.1.id'); +echo $this->Form->control('comments.1.comment'); +``` + +The above controls could then be marshalled into a completed entity graph using +the following code in your controller: + +``` php +$article = $this->Articles->patchEntity($article, $this->request->getData(), [ + 'associated' => [ + 'Authors', + 'Authors.Profiles', + 'Tags', + 'Comments', + ], +]); +``` + +The above example shows an expanded example for belongs to many associations, +with separate inputs for each entity and join data record. You can also create +a multiple select input for belongs to many associations: + +``` php +// Multiple select element for belongsToMany +// Does not support _joinData +echo $this->Form->control('tags._ids', [ + 'type' => 'select', + 'multiple' => true, + 'options' => $tags, // $tags is the output of $this->Articles->Tags->find('list')->all() in the controller +]); +``` + +## Adding Custom Widgets + +You can add custom control widgets in CakePHP, and use them like any other +control type. All of the core control types are implemented as widgets, which +means you can override any core widget with your own implementation as well. + +### Building a Widget Class + +Widget classes have a very simple required interface. They must implement the +`Cake\View\Widget\WidgetInterface`. This interface requires +the `render(array $data)` and `secureFields(array $data)` methods to be +implemented. The `render()` method expects an array of data to build the +widget and is expected to return a string of HTML for the widget. +The `secureFields()` method expects an array of data as well and is expected +to return an array containing the list of fields to secure for this widget. +If CakePHP is constructing your widget you can expect to +get a `Cake\View\StringTemplate` instance as the first argument, followed by +any dependencies you define. If we wanted to build an Autocomplete widget you +could do the following: + +``` php +namespace App\View\Widget; + +use Cake\View\Form\ContextInterface; +use Cake\View\StringTemplate; +use Cake\View\Widget\WidgetInterface; + +class AutocompleteWidget implements WidgetInterface +{ + /** + * StringTemplate instance. + * + * @var \Cake\View\StringTemplate + */ + protected $_templates; + + /** + * Constructor. + * + * @param \Cake\View\StringTemplate $templates Templates list. + */ + public function __construct(StringTemplate $templates) + { + $this->_templates = $templates; + } + + /** + * Methods that render the widget. + * + * @param array $data The data to build an input with. + * @param \Cake\View\Form\ContextInterface $context The current form context. + * + * @return string + */ + public function render(array $data, ContextInterface $context): string + { + $data += [ + 'name' => '', + ]; + + return $this->_templates->format('autocomplete', [ + 'name' => $data['name'], + 'attrs' => $this->_templates->formatAttributes($data, ['name']) + ]); + } + + public function secureFields(array $data): array + { + return [$data['name']]; + } +} +``` + +Obviously, this is a very simple example, but it demonstrates how a custom +widget could be built. This widget would render the "autocomplete" string +template, such as: + +``` php +$this->Form->setTemplates([ + 'autocomplete' => '' +]); +``` + +For more information on string templates, see [Customizing Templates](#customizing-templates). + +### Using Widgets + +You can load custom widgets when loading FormHelper or by using the +`addWidget()` method. When loading FormHelper, widgets are defined as +a setting: + +``` php +// In View class +$this->loadHelper('Form', [ + 'widgets' => [ + 'autocomplete' => ['Autocomplete'], + ], +]); +``` + +If your widget requires other widgets, you can have FormHelper populate those +dependencies by declaring them: + +``` php +$this->loadHelper('Form', [ + 'widgets' => [ + 'autocomplete' => [ + 'App\View\Widget\AutocompleteWidget', + 'text', + 'label', + ], + ], +]); +``` + +In the above example, the `autocomplete` widget would depend on the `text` and +`label` widgets. If your widget needs access to the View, you should use the +`_view` 'widget'. When the `autocomplete` widget is created, it will be passed +the widget objects that are related to the `text` and `label` names. To add +widgets using the `addWidget()` method would look like: + +``` php +// Using a classname. +$this->Form->addWidget( + 'autocomplete', + ['Autocomplete', 'text', 'label'] +); + +// Using an instance - requires you to resolve dependencies. +$autocomplete = new AutocompleteWidget( + $this->Form->getTemplater(), + $this->Form->getWidgetLocator()->get('text'), + $this->Form->getWidgetLocator()->get('label'), +); +$this->Form->addWidget('autocomplete', $autocomplete); +``` + +Once added/replaced, widgets can be used as the control 'type': + +``` php +echo $this->Form->control('search', ['type' => 'autocomplete']); +``` + +This will create the custom widget with a `label` and wrapping `div` just +like `controls()` always does. Alternatively, you can create just the control +widget using the magic method: + +``` php +echo $this->Form->autocomplete('search', $options); +``` + +## Working with FormProtectionComponent + +`Cake\Controller\Component\FormProtectionComponent` offers several +features that make your forms safer and more secure. By simply including the +`FormProtectionComponent` in your controller, you'll automatically benefit from +form tampering-prevention features. + +As mentioned previously when using FormProtectionComponent, you should always close +your forms using `Cake\View\Helper\FormHelper::end()`. This will +ensure that the special `_Token` inputs are generated. + +`method` Cake\\View\\Helper\\FormHelper::**unlockField**($name) + +- `$name` - Optional. The dot-separated name for the field. + +Unlocks a field making it exempt from the `FormProtectionComponent` field +hashing. This also allows the fields to be manipulated by JavaScript. +The `$name` parameter should be the entity property name for the field: + +``` php +$this->Form->unlockField('id'); +``` + +`method` Cake\\View\\Helper\\FormHelper::**secure**(array $fields = [], array $secureAttributes = []): string + +- `$fields` - Optional. An array containing the list of fields to use when + generating the hash. If not provided, then `$this->fields` will be used. +- `$secureAttributes` - Optional. An array of HTML attributes to be passed + into the generated hidden input elements. + +Generates a hidden `input` field with a security hash based on the fields used +in the form or an empty string when secured forms are not in use. +If `$secureAttributes` is set, these HTML attributes will be +merged into the hidden input tags generated for the FormProtectionComponent. This is +especially useful to set HTML5 attributes like `'form'`. diff --git a/docs/en/views/helpers/html.md b/docs/en/views/helpers/html.md new file mode 100644 index 0000000000..ebb2c44e88 --- /dev/null +++ b/docs/en/views/helpers/html.md @@ -0,0 +1,930 @@ +# Html + +`class` Cake\\View\\Helper\\**HtmlHelper**(View $view, array $config = []) + +The role of the HtmlHelper in CakePHP is to make HTML-related +options easier, faster, and more resilient to change. Using this +helper will enable your application to be more light on its feet, +and more flexible on where it is placed in relation to the root of +a domain. + +Many HtmlHelper methods include a `$attributes` parameter, +that allow you to tack on any extra attributes on your tags. Here +are a few examples of how to use the `$attributes` parameter: + +``` html +Desired attributes: +Array parameter: ['class' => 'someClass'] + +Desired attributes: +Array parameter: ['name' => 'foo', 'value' => 'bar'] +``` + +## Inserting Well-Formatted Elements + +The most important task the HtmlHelper accomplishes is creating +well formed markup. This section will cover some of the +methods of the HtmlHelper and how to use them. + +### Creating Charset Tags + +`method` Cake\\View\\Helper\\HtmlHelper::**charset**($charset=null): string + +Used to create a meta tag specifying the document's character. The default value +is UTF-8. An example use: + +``` php +echo $this->Html->charset(); +``` + +Will output: + +``` html + +``` + +Alternatively, : + +``` php +echo $this->Html->charset('ISO-8859-1'); +``` + +Will output: + +``` html + +``` + +### Linking to CSS Files + +`method` Cake\\View\\Helper\\HtmlHelper::**css**(mixed $path, array $options = []): string|null + +Creates a link(s) to a CSS style-sheet. If the `block` option is set to +`true`, the link tags are added to the `css` block which you can print +inside the head tag of the document. + +You can use the `block` option to control which block the link element +will be appended to. By default it will append to the `css` block. + +If key 'rel' in `$options` array is set to 'import' the stylesheet will be imported. + +This method of CSS inclusion assumes that the CSS file specified +resides inside the **webroot/css** directory if path doesn't start with a '/'. : + +``` php +echo $this->Html->css('forms'); +``` + +Will output: + +``` html + +``` + +The first parameter can be an array to include multiple files. : + +``` php +echo $this->Html->css(['forms', 'tables', 'menu']); +``` + +Will output: + +``` html + + + +``` + +You can include CSS files from any loaded plugin using +`plugin syntax`. To include **plugins/DebugKit/webroot/css/toolbar.css** +you could use the following: + +``` php +echo $this->Html->css('DebugKit.toolbar.css'); +``` + +If you want to include a CSS file which shares a name with a loaded +plugin you can do the following. For example if you had a `Blog` plugin, +and also wanted to include **webroot/css/Blog.common.css**, you would: + +``` php +echo $this->Html->css('Blog.common.css', ['plugin' => false]); +``` + +### Creating CSS Programatically + +`method` Cake\\View\\Helper\\HtmlHelper::**style**(array $data, boolean $oneline = true): string + +Builds CSS style definitions based on the keys and values of the +array passed to the method. Especially handy if your CSS file is +dynamic. : + +``` php +echo $this->Html->style([ + 'background' => '#633', + 'border-bottom' => '1px solid #000', + 'padding' => '10px' +]); +``` + +Will output: + +``` css +background:#633; border-bottom:1px solid #000; padding:10px; +``` + +### Creating meta Tags + +`method` Cake\\View\\Helper\\HtmlHelper::**meta**(string|array $type, string $url = null, array $options = []): string|null + +This method is handy for linking to external resources like RSS/Atom feeds +and favicons. Like css(), you can specify whether or not you'd like this tag +to appear inline or appended to the `meta` block by setting the 'block' +key in the \$attributes parameter to `true`, ie - `['block' => true]`. + +If you set the "type" attribute using the \$attributes parameter, +CakePHP contains a few shortcuts: + +| type | translated value | +|-----------|------------------------| +| html | text/html | +| rss | application/rss+xml | +| atom | application/atom+xml | +| icon | image/x-icon | +| csrfToken | The current CSRF token | + +``` php +echo $this->Html->meta( + 'favicon.ico', + '/favicon.ico', + ['type' => 'icon'] +); +// Output (line breaks added) +// Note: The helper code makes two meta tags to ensure the +// icon is downloaded by both newer and older browsers +// which require different rel attribute values. + + + +echo $this->Html->meta( + 'Comments', + '/comments/index.rss', + ['type' => 'rss'] +); +// Output (line breaks added) + +``` + +This method can also be used to add the meta keywords and +descriptions. Example: + +``` php +echo $this->Html->meta( + 'keywords', + 'enter any meta keyword here' +); +// Output + + +echo $this->Html->meta( + 'description', + 'enter any meta description here' +); +// Output + + +echo $this->Html->meta('csrfToken'); +// The CsrfProtection middleware must be loaded for your application + +``` + +In addition to making predefined meta tags, you can create link elements: + +``` php +Html->meta([ + 'link' => 'http://example.com/manifest', + 'rel' => 'manifest' +]); +?> +// Output + +``` + +Any attributes provided to meta() when called this way will be added to the +generated link tag. + +::: info Changed in version 5.1.0 +The `csrfToken` type was added. +::: + +### Linking to Images + +`method` Cake\\View\\Helper\\HtmlHelper::**image**(string $path, array $options = []): string + +Creates a formatted image tag. The path supplied should be relative +to **webroot/img/**. : + +``` php +echo $this->Html->image('cake_logo.png', ['alt' => 'CakePHP']); +``` + +Will output: + +``` html +CakePHP +``` + +To create an image link specify the link destination using the +`url` option in `$attributes`. : + +``` php +echo $this->Html->image("recipes/6.jpg", [ + "alt" => "Brownies", + 'url' => ['controller' => 'Recipes', 'action' => 'view', 6] +]); +``` + +Will output: + +``` html + + Brownies + +``` + +If you are creating images in emails, or want absolute paths to images you +can use the `fullBase` option: + +``` php +echo $this->Html->image("logo.png", ['fullBase' => true]); +``` + +Will output: + +``` html + +``` + +You can include image files from any loaded plugin using +`plugin syntax`. To include **plugins/DebugKit/webroot/img/icon.png** +You could use the following: + +``` php +echo $this->Html->image('DebugKit.icon.png'); +``` + +If you want to include an image file which shares a name with a loaded +plugin you can do the following. For example if you had a `Blog` plugin, +and also wanted to include **webroot/img/Blog.icon.png**, you would: + +``` php +echo $this->Html->image('Blog.icon.png', ['plugin' => false]); +``` + +If you would like the prefix of the URL to not be `/img`, you can override this setting by specifying the prefix in the `$options` array : + +``` php +echo $this->Html->image("logo.png", ['pathPrefix' => '']); +``` + +Will output: + +``` html + +``` + +### Creating Links + +`method` Cake\\View\\Helper\\HtmlHelper::**link**($title, $url = null, array $options = []): string + +General purpose method for creating HTML links. Use `$options` to +specify attributes for the element and whether or not the +`$title` should be escaped. : + +``` php +echo $this->Html->link( + 'Enter', + '/pages/home', + ['class' => 'button', 'target' => '_blank'] +); +``` + +Will output: + +``` html +Enter +``` + +Use `'_full'=>true` option for absolute URLs: + +``` php +echo $this->Html->link( + 'Dashboard', + ['controller' => 'Dashboards', 'action' => 'index', '_full' => true] +); +``` + +Will output: + +``` html +Dashboard +``` + +Specify `confirm` key in options to display a JavaScript `confirm()` +dialog: + +``` php +echo $this->Html->link( + 'Delete', + ['controller' => 'Recipes', 'action' => 'delete', 6], + ['confirm' => 'Are you sure you wish to delete this recipe?'] +); +``` + +Will output: + +``` html + + Delete + +``` + +Query strings can also be created with `link()`. : + +``` php +echo $this->Html->link('View image', [ + 'controller' => 'Images', + 'action' => 'view', + 1, + '?' => ['height' => 400, 'width' => 500] +]); +``` + +Will output: + +``` html +View image +``` + +HTML special characters in `$title` will be converted to HTML +entities. To disable this conversion, set the escape option to +`false` in the `$options` array. : + +``` php +echo $this->Html->link( + $this->Html->image("recipes/6.jpg", ["alt" => "Brownies"]), + "recipes/view/6", + ['escape' => false] +); +``` + +Will output: + +``` html + + Brownies + +``` + +Setting `escape` to `false` will also disable escaping of attributes of the +link. You can use the option `escapeTitle` to disable just +escaping of title and not the attributes. : + +``` php +echo $this->Html->link( + $this->Html->image('recipes/6.jpg', ['alt' => 'Brownies']), + 'recipes/view/6', + ['escapeTitle' => false, 'title' => 'hi "howdy"'] +); +``` + +Will output: + +``` html + + Brownies + +``` + +Also check `Cake\View\Helper\UrlHelper::build()` method +for more examples of different types of URLs. + +### Creating Links from Route Paths + +`method` Cake\\View\\Helper\\HtmlHelper::**linkFromPath**(string $title, string $path, array $params = [], array $options = []): string + +If you want to use route path strings, you can do that using this method: + +``` php +echo $this->Html->linkFromPath('Index', 'Articles::index'); +// outputs: Index + +echo $this->Html->linkFromPath('View', 'MyBackend.Admin/Articles::view', [3]); +// outputs: View +``` + +### Linking to Videos and Audio Files + +`method` Cake\\View\\Helper\\HtmlHelper::**media**(string|array $path, array $options): string + +Options: + +- `type` Type of media element to generate, valid values are "audio" + or "video". If type is not provided media type is guessed based on + file's mime type. +- `text` Text to include inside the video tag +- `pathPrefix` Path prefix to use for relative URLs, defaults to + 'files/' +- `fullBase` If provided the src attribute will get a full address + including domain name + +Returns a formatted audio/video tag: + +``` php +Html->media('audio.mp3') ?> + +// Output + + +Html->media('video.mp4', [ + 'fullBase' => true, + 'text' => 'Fallback text' +]) ?> + +// Output + + +Html->media( + ['video.mp4', ['src' => 'video.ogg', 'type' => "video/ogg; codecs='theora, vorbis'"]], + ['autoplay'] +) ?> + +// Output + +``` + +### Linking to Javascript Files + +`method` Cake\\View\\Helper\\HtmlHelper::**script**(mixed $url, mixed $options): string|null + +Include a script file(s), contained either locally or as a remote URL. + +By default, script tags are added to the document inline. If you override +this by setting `$options['block']` to `true`, the script tags will instead +be added to the `script` block which you can print elsewhere in the document. +If you wish to override which block name is used, you can do so by setting +`$options['block']`. + +`$options['once']` controls whether or +not you want to include this script once per request or more than +once. This defaults to `true`. + +You can use \$options to set additional properties to the +generated script tag. If an array of script tags is used, the +attributes will be applied to all of the generated script tags. + +This method of JavaScript file inclusion assumes that the +JavaScript file specified resides inside the **webroot/js** +directory: + +``` php +echo $this->Html->script('scripts'); +``` + +Will output: + +``` html + +``` + +You can link to files with absolute paths as well to link files +that are not in **webroot/js**: + +``` php +echo $this->Html->script('/otherdir/script_file'); +``` + +You can also link to a remote URL: + +``` php +echo $this->Html->script('https://code.jquery.com/jquery.min.js'); +``` + +Will output: + +``` html + +``` + +The first parameter can be an array to include multiple files. : + +``` php +echo $this->Html->script(['jquery', 'wysiwyg', 'scripts']); +``` + +Will output: + +``` html + + + +``` + +You can append the script tag to a specific block using the `block` +option: + +``` php +$this->Html->script('wysiwyg', ['block' => 'scriptBottom']); +``` + +In your layout you can output all the script tags added to 'scriptBottom': + +``` php +echo $this->fetch('scriptBottom'); +``` + +You can include script files from any loaded plugin using +`plugin syntax`. To include **plugins/DebugKit/webroot/js/toolbar.js** +You could use the following: + +``` php +echo $this->Html->script('DebugKit.toolbar.js'); +``` + +If you want to include a script file which shares a name with a loaded +plugin you can do the following. For example if you had a `Blog` plugin, +and also wanted to include **webroot/js/Blog.plugins.js**, you would: + +``` php +echo $this->Html->script('Blog.plugins.js', ['plugin' => false]); +``` + +### Creating Inline Javascript Blocks + +`method` Cake\\View\\Helper\\HtmlHelper::**scriptBlock**(string $code, array $options = []): string|null + +To generate Javascript blocks from PHP view code, you can use one of the script +block methods. Scripts can either be output in place, or buffered into a block: + +``` php +// Define a script block all at once, with the defer attribute. +$this->Html->scriptBlock('alert("hi")', ['defer' => true]); + +// Buffer a script block to be output later. +$this->Html->scriptBlock('alert("hi")', ['block' => true]); +``` + +### Starting and Ending Script Blocks + +`method` Cake\\View\\Helper\\HtmlHelper::**scriptStart**(array $options = []): void + +`method` Cake\\View\\Helper\\HtmlHelper::**scriptEnd**(): string|null + +You can use the `scriptStart()` method to create a capturing block that will +output into a `` tags inside the script block to +enable syntax highlighting and LSP support in many editors: + +``` php +Html->scriptStart(['block' => true]) ?> + +Html->scriptEnd() ?> +``` + +The wrapping `script` tag will be removed and replaced with a script tag +generated by the helper that includes a CSP nonce if available. + +Once you have buffered javascript, you can output it as you would any other +[View Block](../../views#view-blocks): + +``` php +// In your layout +echo $this->fetch('script'); +``` + +::: info Changed in version 5.3.0 +Support for `script` tags inside `scriptStart()`/`scriptEnd()` was added. +::: + +### Creating Javascript Importmap + +`method` Cake\\View\\Helper\\HtmlHelper::**importmap(array $map, array $options = []): string**() + +Creates an importmap script tag for your JavaScript files: + +``` php +// In the head tag of your layout +echo $this->Html->importmap([ + 'jquery' => 'jquery.js', + 'wysiwyg' => '/editor/wysiwyg.js' +]); +``` + +Will output: + +``` html + +``` + +Generating maps with imports, scopes and integrity: + +``` php +echo $this->Html->importmap([ + 'imports' => [ + 'jquery' => 'jquery-3.7.1.min.js', + 'wysiwyg' => '/editor/wysiwyg.js' + ], + 'scopes' => [ + 'scoped/' => [ + 'foo' => 'inner/foo', + ], + ], + 'integrity' => [ + 'jquery' => 'sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=', + ], +]); +``` + +Will output: + +``` html + +``` + +### Creating Nested Lists + +`method` Cake\\View\\Helper\\HtmlHelper::**nestedList**(array $list, array $options = [], array $itemOptions = []): string + +Build a nested list (UL/OL) out of an associative array: + +``` php +$list = [ + 'Languages' => [ + 'English' => [ + 'American', + 'Canadian', + 'British', + ], + 'Spanish', + 'German', + ] +]; +echo $this->Html->nestedList($list); +``` + +Output: + +``` html +// Output (minus the whitespace) +
      +
    • Languages +
        +
      • English +
          +
        • American
        • +
        • Canadian
        • +
        • British
        • +
        +
      • +
      • Spanish
      • +
      • German
      • +
      +
    • +
    +``` + +### Creating Table Headings + +`method` Cake\\View\\Helper\\HtmlHelper::**tableHeaders**(array $names, array $trOptions = null, array $thOptions = null): string + +Creates a row of table header cells to be placed inside of \ +tags. : + +``` php +echo $this->Html->tableHeaders(['Date', 'Title', 'Active']); +``` + +Output: + +``` html + + Date + Title + Active + +``` + +``` php +echo $this->Html->tableHeaders( + ['Date', 'Title','Active'], + ['class' => 'status'], + ['class' => 'product_table'] +); +``` + +Output: + +``` html + + Date + Title + Active + +``` + +You can set attributes per column, these are used instead of the +defaults provided in the `$thOptions`: + +``` php +echo $this->Html->tableHeaders([ + 'id', + ['Name' => ['class' => 'highlight']], + ['Date' => ['class' => 'sortable']] +]); +``` + +Output: + +``` html + + id + Name + Date + +``` + +### Creating Table Cells + +`method` Cake\\View\\Helper\\HtmlHelper::**tableCells**(array $data, array $oddTrOptions = null, array $evenTrOptions = null, $useCount = false, $continueOddEven = true): string + +Creates table cells, in rows, assigning \ attributes differently +for odd- and even-numbered rows. Wrap a single table cell within an +\[\] for specific \-attributes. : + +``` php +echo $this->Html->tableCells([ + ['Jul 7th, 2007', 'Best Brownies', 'Yes'], + ['Jun 21st, 2007', 'Smart Cookies', 'Yes'], + ['Aug 1st, 2006', 'Anti-Java Cake', 'No'], +]); +``` + +Output: + +``` html +Jul 7th, 2007Best BrowniesYes +Jun 21st, 2007Smart CookiesYes +Aug 1st, 2006Anti-Java CakeNo +``` + +``` php +echo $this->Html->tableCells([ + ['Jul 7th, 2007', ['Best Brownies', ['class' => 'highlight']] , 'Yes'], + ['Jun 21st, 2007', 'Smart Cookies', 'Yes'], + ['Aug 1st, 2006', 'Anti-Java Cake', ['No', ['id' => 'special']]], +]); +``` + +Output: + +``` html + + + Jul 7th, 2007 + + + Best Brownies + + + Yes + + + + + Jun 21st, 2007 + + + Smart Cookies + + + Yes + + + + + Aug 1st, 2006 + + + Anti-Java Cake + + + No + + +``` + +``` php +echo $this->Html->tableCells( + [ + ['Red', 'Apple'], + ['Orange', 'Orange'], + ['Yellow', 'Banana'], + ], + ['class' => 'darker'] +); +``` + +Output: + +``` html +RedApple +OrangeOrange +YellowBanana +``` + +## Changing the Tags Output by HtmlHelper + +`method` Cake\\View\\Helper\\HtmlHelper::**setTemplates**(array $templates) + +Load an array of templates to add/replace templates: + +``` php +// Load specific templates. +$this->Html->setTemplates([ + 'javascriptlink' => '' +]); +``` + +You can load a configuration file containing templates using the templater +directly: + +``` php +// Load a configuration file with templates. +$this->Html->templater()->load('my_tags'); +``` + +When loading files of templates, your file should look like: + +``` php + '' +]; +``` + +> [!WARNING] +> Template strings containing a percentage sign (`%`) need special attention, +> you should prefix this character with another percentage so it looks like +> `%%`. The reason is that internally templates are compiled to be used with +> `sprintf()`. Example: `
    {{content}}
    ` diff --git a/docs/en/views/helpers/number.md b/docs/en/views/helpers/number.md new file mode 100644 index 0000000000..0fbe4caada --- /dev/null +++ b/docs/en/views/helpers/number.md @@ -0,0 +1,14 @@ +# Number + +`class` Cake\\View\\Helper\\**NumberHelper**(View $view, array $config = []) + +The NumberHelper contains convenient methods that enable display +numbers in common formats in your views. These methods include ways +to format currency, percentages, data sizes, format numbers to +specific precisions and also to give you more flexibility with +formatting numbers. + + + +> [!WARNING] +> All symbols are UTF-8. diff --git a/docs/en/views/helpers/paginator.md b/docs/en/views/helpers/paginator.md new file mode 100644 index 0000000000..5c2920af47 --- /dev/null +++ b/docs/en/views/helpers/paginator.md @@ -0,0 +1,569 @@ +# Paginator + +`class` Cake\\View\\Helper\\**PaginatorHelper**(View $view, array $config = []) + +The PaginatorHelper is used to output pagination controls such as page numbers +and next/previous links. + +See also [Pagination](../../controllers/pagination) for information on how to +create paginated datasets and do paginated queries. + +## Setting the paginated resultset + +`method` Cake\\View\\Helper\\PaginatorHelper::**setPaginated**($paginated, $options): void + +By default the helper uses the first instance of `Cake\Datasource\Paging\PaginatedInterface` +it finds in the view variables. (Generally the result of `Controller::paginate()`). + +You can use `PaginatorHelper::setPaginated()` to explicitly set the paginated +resultset that the helper should use. + + + +### PaginatorHelper Templates + +Internally PaginatorHelper uses a series of simple HTML templates to generate +markup. You can modify these templates to customize the HTML generated by the +PaginatorHelper. + +Templates use `{{var}}` style placeholders. It is important to not add any +spaces around the `{{}}` or the replacements will not work. + +## Loading Templates from a File + +When adding the PaginatorHelper in your controller, you can define the +'templates' setting to define a template file to load. This allows you to +customize multiple templates and keep your code DRY: + +``` php +// In your AppView.php +public function initialize(): void +{ + ... + $this->loadHelper('Paginator', ['templates' => 'paginator-templates']); +} +``` + +This will load the file located at **config/paginator-templates.php**. See the +example below for how the file should look like. You can also load templates +from a plugin using `plugin syntax`: + +``` php +// In your AppView.php +public function initialize(): void +{ + ... + $this->loadHelper('Paginator', ['templates' => 'MyPlugin.paginator-templates']); +} +``` + +Whether your templates are in the primary application or a plugin, your +templates file should look something like: + +``` text +return [ + 'number' => '{{text}}', +]; +``` + +## Changing Templates at Run-time + +`method` Cake\\View\\Helper\\PaginatorHelper::**setTemplates**($templates) + +This method allows you to change the templates used by PaginatorHelper at +runtime. This can be useful when you want to customize templates for a +particular method call: + +``` php +// Read the current template value. +$result = $this->Paginator->getTemplates('number'); + +// Change a template +$this->Paginator->setTemplates([ + 'number' => '{{text}}' +]); +``` + +> [!WARNING] +> Template strings containing a percentage sign (`%`) need special +> attention, you should prefix this character with another percentage so it +> looks like `%%`. The reason is that internally templates are compiled to +> be used with `sprintf()`. +> Example: '\
    {{content}}\' + +## Template Names + +PaginatorHelper uses the following templates: + +- `nextActive` The active state for a link generated by next(). +- `nextDisabled` The disabled state for next(). +- `prevActive` The active state for a link generated by prev(). +- `prevDisabled` The disabled state for prev() +- `counterRange` The template counter() uses when format == range. +- `counterPages` The template counter() uses when format == pages. +- `first` The template used for a link generated by first(). +- `last` The template used for a link generated by last() +- `number` The template used for a link generated by numbers(). +- `current` The template used for the current page. +- `ellipsis` The template used for ellipses generated by numbers(). +- `sort` The template for a sort link with no direction. +- `sortAsc` The template for a sort link with an ascending direction. +- `sortDesc` The template for a sort link with a descending direction. + +### Creating Sort Links + +`method` Cake\\View\\Helper\\PaginatorHelper::**sort**($key, $title = null, $options = []): string + +Generates a sorting link. Sets querystring parameters for the sort and +direction. Links will default to sorting by asc. After the first click, links +generated with `sort()` will handle direction switching automatically. If the +resultset is sorted 'asc' by the specified key the returned link will sort by +'desc'. Uses the `sort`, `sortAsc`, `sortDesc`, `sortAscLocked` and +`sortDescLocked` templates. + +Accepted keys for `$options`: + +- `escape` Whether you want the contents HTML entity encoded, defaults to + `true`. +- `direction` The default direction to use when this link isn't active. +- `lock` Lock direction. Will only use the default direction then, defaults to `false`. + +Assuming you are paginating some posts, and are on page one: + +``` php +echo $this->Paginator->sort('user_id'); +``` + +Output: + +``` html +User Id +``` + +You can use the title parameter to create custom text for your link: + +``` php +echo $this->Paginator->sort('user_id', 'User account'); +``` + +Output: + +``` html +User account +``` + +If you are using HTML like images in your links remember to set escaping off: + +``` php +echo $this->Paginator->sort( + 'user_id', + 'User account', + ['escape' => false] +); +``` + +Output: + +``` html +User account +``` + +The direction option can be used to set the default direction for a link. Once a +link is active, it will automatically switch directions like normal: + +``` php +echo $this->Paginator->sort('user_id', null, ['direction' => 'desc']); +``` + +Output: + +``` html +User Id +``` + +The lock option can be used to lock sorting into the specified direction: + +``` php +echo $this->Paginator->sort('user_id', null, ['direction' => 'asc', 'lock' => true]); +``` + +### Getting Sort Direction and Key + +`method` Cake\\View\\Helper\\PaginatorHelper::**sortDir**(string $model = null, mixed $options = []): string + +`method` Cake\\View\\Helper\\PaginatorHelper::**sortKey**(string $model = null, mixed $options = []) + +### Creating Page Number Links + +`method` Cake\\View\\Helper\\PaginatorHelper::**numbers**($options = []): string + +Returns a set of numbers for the paged result set. Uses a modulus to +decide how many numbers to show on each side of the current page By default +8 links on either side of the current page will be created if those pages exist. +Links will not be generated for pages that do not exist. The current page is +also not a link. The `number`, `current` and `ellipsis` templates will be +used. + +Supported options are: + +- `before` Content to be inserted before the numbers. + +- `after` Content to be inserted after the numbers. + +- `modulus` how many numbers to include on either side of the current page, + defaults to 8. + +- `first` Whether you want first links generated, set to an integer to + define the number of 'first' links to generate. Defaults to `false`. If a + string is set a link to the first page will be generated with the value as the + title: + + ``` php + echo $this->Paginator->numbers(['first' => 'First page']); + ``` + +- `last` Whether you want last links generated, set to an integer to define + the number of 'last' links to generate. Defaults to `false`. Follows the same + logic as the `first` option. There is a + `~PaginatorHelper::last()` method to be used separately as well if + you wish. + +While this method allows a lot of customization for its output. It is +also ok to just call the method without any parameters. : + +``` php +echo $this->Paginator->numbers(); +``` + +Using the first and last options you can create links to the beginning +and end of the page set. The following would create a set of page links that +include links to the first 2 and last 2 pages in the paged results: + +``` php +echo $this->Paginator->numbers(['first' => 2, 'last' => 2]); +``` + +### Creating Jump Links + +In addition to generating links that go directly to specific page numbers, +you'll often want links that go to the previous and next links, first and last +pages in the paged data set. + +### prev() + +`method` Cake\\View\\Helper\\PaginatorHelper::**prev**($title = '<< Previous', $options = []): string + +### next() + +`method` Cake\\View\\Helper\\PaginatorHelper::**next**($title = 'Next >>', $options = []): string + +### first() + +`method` Cake\\View\\Helper\\PaginatorHelper::**first**($first = '<< first', $options = []): string + +### last() + +`method` Cake\\View\\Helper\\PaginatorHelper::**last**($last = 'last >>', $options = []): string + +### Creating Header Link Tags + +PaginatorHelper can be used to create pagination link tags in your page +`` elements: + +``` php +// Create next/prev links for the current model. +echo $this->Paginator->meta(); + +// Create next/prev & first/last links for the current model. +echo $this->Paginator->meta(['first' => true, 'last' => true]); +``` + +### Checking the Pagination State + +#### current() + +`method` Cake\\View\\Helper\\PaginatorHelper::**current**(): int + +#### hasNext() + +`method` Cake\\View\\Helper\\PaginatorHelper::**hasNext**(string $model = null): bool + +#### hasPrev() + +`method` Cake\\View\\Helper\\PaginatorHelper::**hasPrev**(): bool + +#### hasPage() + +`method` Cake\\View\\Helper\\PaginatorHelper::**hasPage**(int $page = 1): bool + +#### total() + +`method` Cake\\View\\Helper\\PaginatorHelper::**total**(): int|null + +### Creating a Page Counter + +`method` Cake\\View\\Helper\\PaginatorHelper::**counter**(string $format = 'pages', array $options = []): string + +Returns a counter string for the paged result set. Using a provided format +string and a number of options you can create localized and application +specific indicators of where a user is in the paged data set. Uses the +`counterRange`, and `counterPages` templates. + +Supported formats are 'range', 'pages' and custom. Defaults to pages which would +output like '1 of 10'. In the custom mode the supplied string is parsed and +tokens are replaced with actual values. The available tokens are: + +- `{{page}}` - the current page displayed. +- `{{pages}}` - total number of pages. +- `{{current}}` - current number of records being shown. +- `{{count}}` - the total number of records in the result set. +- `{{start}}` - number of the first record being displayed. +- `{{end}}` - number of the last record being displayed. +- `{{model}}` - The pluralized human form of the model name. + If your model was 'RecipePage', `{{model}}` would be 'recipe pages'. + +You could also supply only a string to the counter method using the tokens +available. For example: + +``` php +echo $this->Paginator->counter( + 'Page {{page}} of {{pages}}, showing {{current}} records out of + {{count}} total, starting on record {{start}}, ending on {{end}}' +); +``` + +Setting 'format' to range would output like '1 - 3 of 13': + +``` php +echo $this->Paginator->counter('range'); +``` + +### Generating Pagination URLs + +`method` Cake\\View\\Helper\\PaginatorHelper::**generateUrl**(array $options = [], ?string $model = null, array $url = [], array $urlOptions = []): string + +By default returns a full pagination URL string for use in non-standard contexts +(i.e. JavaScript). : + +``` php +// Generates a URL similar to: /articles?sort=title&page=2 +echo $this->Paginator->generateUrl(['sort' => 'title']); + +// Generates a URL for a different model +echo $this->Paginator->generateUrl(['sort' => 'title'], 'Comments'); + +// Generates a URL to a different controller. +echo $this->Paginator->generateUrl( + ['sort' => 'title'], + null, + ['controller' => 'Comments'] +); +``` + +### Creating a Limit Selectbox Control + +`method` Cake\\View\\Helper\\PaginatorHelper::**limitControl**(array $limits = [], $default = null, array $options = []): string + +Create a dropdown control that changes the `limit` query parameter: + +``` php +// Use the defaults. +echo $this->Paginator->limitControl(); + +// Define which limit options you want. +echo $this->Paginator->limitControl([25 => 25, 50 => 50]); + +// Custom limits and set the selected option +echo $this->Paginator->limitControl([25 => 25, 50 => 50], $user->perPage); +``` + +The generated form and control will automatically submit on change. + +## Automatic Limit Generation with Steps + +Instead of manually defining limit options, you can use the `steps` option to +automatically generate limits in multiples of a specific value: + +``` php +// Generate limits in steps of 10 up to maxLimit +echo $this->Paginator->limitControl([], null, ['steps' => 10]); +// With maxLimit of 50, this generates: 10, 20, 30, 40, 50 + +// Steps of 25 up to maxLimit or 100 (whichever is lower) +echo $this->Paginator->limitControl([], null, ['steps' => 25]); +``` + +When using `steps`, you cannot also provide explicit limits in the `$limits` +parameter - you must use one or the other. + +## Respecting maxLimit + +The `limitControl()` method automatically respects the `maxLimit` configuration +from your paginator settings. Any limit options that exceed the `maxLimit` will +be automatically filtered out: + +``` php +// In your controller with maxLimit of 50 +$this->paginate = [ + 'limit' => 20, + 'maxLimit' => 50, +]; + +// In your template - limits above 50 are filtered out +echo $this->Paginator->limitControl([20 => 20, 50 => 50, 100 => 100]); +// Only shows: 20, 50 +``` + +If all default or provided limits exceed the `maxLimit`, the control will +automatically use the `maxLimit` value as the only available option. + +### Configuring Pagination Options + +`method` Cake\\View\\Helper\\PaginatorHelper::**options**($options = []): void + +Sets all the options for the PaginatorHelper. Supported options are: + +- `url` The URL of the paginating action. + + The option allows your to set/override any element for URLs generated by + the helper: + + ``` php + $this->Paginator->options([ + 'url' => [ + 'lang' => 'en', + '?' => [ + 'sort' => 'email', + 'direction' => 'desc', + 'page' => 6, + ], + ] + ]); + ``` + + The example above adds the `en` route parameter to all links the helper will + generate. It will also create links with specific sort, direction and page + values. By default `PaginatorHelper` will merge in all of the current passed + arguments and query string parameters. + +- `escape` Defines if the title field for links should be HTML escaped. + Defaults to `true`. + +### Example Usage + +It's up to you to decide how to show records to the user, but most often this +will be done inside HTML tables. The examples below assume a tabular layout, but +the PaginatorHelper available in views doesn't always need to be restricted as +such. + +See the details on +[PaginatorHelper](https://api.cakephp.org/5.x/class-Cake.View.Helper.PaginatorHelper.html) in +the API. As mentioned, the PaginatorHelper also offers sorting features which +can be integrated into your table column headers: + +``` php + + + + + + + + + + + + +
    Paginator->sort('id', 'ID') ?>Paginator->sort('title', 'Title') ?>
    id ?> title) ?>
    +``` + +The links output from the `sort()` method of the `PaginatorHelper` allow +users to click on table headers to toggle the sorting of the data by a given +field. + +It is also possible to sort a column based on associations: + +``` php + + + + + + + + + + + +
    Paginator->sort('title', 'Title') ?>Paginator->sort('Authors.name', 'Author') ?>
    title) ?> name) ?>
    +``` + +> [!NOTE] +> Sorting by columns in associated models requires setting these in the +> `PaginationComponent::paginate` property. Using the example above, the +> controller handling the pagination would need to set its `sortableFields` +> key as follows: +> +> ``` php +> $this->paginate = [ +> 'sortableFields' => [ +> 'Posts.title', +> 'Authors.name', +> ], +> ]; +> ``` +> +> For more information on using the `sortableFields` option, please see +> [Control Which Fields Used For Ordering](../../controllers/pagination#control-which-fields-used-for-ordering). + +The final ingredient to pagination display in views is the addition of page +navigation, also supplied by the PaginationHelper: + +``` php +// Shows the page numbers +Paginator->numbers() ?> + +// Shows the next and previous links +Paginator->prev('« Previous') ?> +Paginator->next('Next »') ?> + +// Prints X of Y, where X is current page and Y is number of pages +Paginator->counter() ?> +``` + +The wording output by the counter() method can also be customized using special +markers: + +``` php +Paginator->counter( + 'Page {{page}} of {{pages}}, showing {{current}} records out of + {{count}} total, starting on record {{start}}, ending on {{end}}' +) ?> +``` + + + +### Paginating Multiple Results + +If you are [paginating multiple queries](../../controllers/pagination#paginating-multiple-queries) +you'll need to use `PaginatorHelper::setPaginated()` first before calling +other methods of the helper, so that they generate expected output. + +`PaginatorHelper` will automatically use the `scope` defined in when the +query was paginated. To set additional URL parameters for multiple pagination +you can include the scope names in `options()`: + +``` php +$this->Paginator->options([ + 'url' => [ + // Additional URL parameters for the 'articles' scope + 'articles' => [ + '?' => ['articles' => 'yes'] + ], + // Additional URL parameters for the 'comments' scope + 'comments' => [ + 'articleId' => 1234, + ], + ], +]); +``` diff --git a/docs/en/views/helpers/text.md b/docs/en/views/helpers/text.md new file mode 100644 index 0000000000..b1f4bb1910 --- /dev/null +++ b/docs/en/views/helpers/text.md @@ -0,0 +1,88 @@ +# Text + +`class` Cake\\View\\Helper\\**TextHelper**(View $view, array $config = []) + +The TextHelper contains methods to make text more usable and +friendly in your views. It aids in enabling links, formatting URLs, +creating excerpts of text around chosen words or phrases, +highlighting key words in blocks of text, and gracefully +truncating long stretches of text. + +## Linking Email addresses + +`method` Cake\\View\\Helper\\TextHelper::**autoLinkEmails**(string $text, array $options = []): string + +Adds links to the well-formed email addresses in \$text, according +to any options defined in `$options` (see +`HtmlHelper::link()`). : + +``` php +$myText = 'For more information regarding our world-famous ' . + 'pastries and desserts, contact info@example.com'; +$linkedText = $this->Text->autoLinkEmails($myText); +``` + +Output: + +``` text +For more information regarding our world-famous pastries and desserts, +contact info@example.com +``` + +This method automatically escapes its input. Use the `escape` +option to disable this if necessary. + +## Linking URLs + +`method` Cake\\View\\Helper\\TextHelper::**autoLinkUrls**(string $text, array $options = []): string + +Same as `autoLinkEmails()`, only this method searches for +strings that start with https, http, ftp, or nntp and links them +appropriately. + +This method automatically escapes its input. Use the `escape` +option to disable this if necessary. + +## Linking Both URLs and Email Addresses + +`method` Cake\\View\\Helper\\TextHelper::**autoLink**(string $text, array $options = []): string + +Performs the functionality in both `autoLinkUrls()` and +`autoLinkEmails()` on the supplied `$text`. All URLs and emails +are linked appropriately given the supplied `$options`. + +This method automatically escapes its input. Use the `escape` +option to disable this if necessary. + +Further options: + +- `stripProtocol`: Strips `http://` and `https://` from the beginning of + the link label. Default off. +- `maxLength`: The maximum length of the link label. Default off. +- `ellipsis`: The string to append to the end of the link label. Defaults to + UTF8 ellipsis. + +## Converting Text into Paragraphs + +`method` Cake\\View\\Helper\\TextHelper::**autoParagraph**(string $text): string + +Adds proper \ around text where double-line returns are found, and \ where +single-line returns are found. : + +``` php +$myText = 'For more information +regarding our world-famous pastries and desserts. + +contact info@example.com'; +$formattedText = $this->Text->autoParagraph($myText); +``` + +Output: + +``` html +

    For more information
    +regarding our world-famous pastries and desserts.

    +

    contact info@example.com

    +``` + + diff --git a/docs/en/views/helpers/time.md b/docs/en/views/helpers/time.md new file mode 100644 index 0000000000..95e8b31bdb --- /dev/null +++ b/docs/en/views/helpers/time.md @@ -0,0 +1,43 @@ +# Time + +`class` Cake\\View\\Helper\\**TimeHelper**(View $view, array $config = []) + +The TimeHelper allows for the quick processing of time related information. +The TimeHelper has two main tasks that it can perform: + +1. It can format time strings. +2. It can test time. + +## Using the Helper + +A common use of the TimeHelper is to offset the date and time to match a +user's time zone. Lets use a forum as an example. Your forum has many users who +may post messages at any time from any part of the world. A way to +manage the time is to save all dates and times as GMT+0 or UTC. Uncomment the +line `date_default_timezone_set('UTC');` in **config/bootstrap.php** to ensure +your application's time zone is set to GMT+0. + +Next add a time zone field to your users table and make the necessary +modifications to allow your users to set their time zone. Now that we know +the time zone of the logged in user we can correct the date and time on our +posts using the TimeHelper: + +``` php +echo $this->Time->format( + $post->created, + \IntlDateFormatter::FULL, + false, + $user->time_zone +); +// Will display 'Saturday, August 22, 2011 at 11:53:00 PM GMT' +// for a user in GMT+0. While displaying, +// 'Saturday, August 22, 2011 at 03:53 PM GMT-8:00' +// for a user in GMT-8 +``` + +Most of TimeHelper's features are intended as backwards compatible interfaces +for applications that are upgrading from older versions of CakePHP. Because the +ORM returns `Cake\I18n\Time` instances for every `timestamp` +and `datetime` column, you can use the methods there to do most tasks. +For example, to read about the accepted formatting strings take a look at the +[Cake\I18n\Time::i18nFormat()](https://api.cakephp.org/5.x/class-Cake.I18n.Time.html#i18nFormat()) method. diff --git a/docs/en/views/helpers/url.md b/docs/en/views/helpers/url.md new file mode 100644 index 0000000000..f119739703 --- /dev/null +++ b/docs/en/views/helpers/url.md @@ -0,0 +1,177 @@ +# Url + +`class` Cake\\View\\Helper\\**UrlHelper**(View $view, array $config = []) + +The UrlHelper helps you to generate URLs from your other helpers. +It also gives you a single place to customize how URLs are generated by +overriding the core helper with an application one. See the +[Aliasing Helpers](../../views/helpers#aliasing-helpers) section for how to do this. + +## Generating URLs + +`method` Cake\\View\\Helper\\UrlHelper::**build**($url = null, array $options = []): string + +Returns a URL pointing to a combination of controller and action. +If `$url` is empty, it returns the `REQUEST_URI`, otherwise it +generates the URL for the controller and action combo. If `fullBase` is +`true`, the full base URL will be prepended to the result: + +``` php +echo $this->Url->build([ + 'controller' => 'Posts', + 'action' => 'view', + 'bar', +]); + +// Output +/posts/view/bar +``` + +Here are a few more usage examples: + +URL with extension: + +``` php +echo $this->Url->build([ + 'controller' => 'Posts', + 'action' => 'list', + '_ext' => 'rss', +]); + +// Output +/posts/list.rss +``` + +URL with prefix: + +``` php +echo $this->Url->build([ + 'controller' => 'Posts', + 'action' => 'list', + 'prefix' => 'Admin', +]); + +// Output +/admin/posts/list +``` + +URL (starting with '/') with the full base URL prepended: + +``` php +echo $this->Url->build('/posts', ['fullBase' => true]); + +// Output +http://somedomain.com/posts +``` + +URL with GET parameters and fragment anchor: + +``` php +echo $this->Url->build([ + 'controller' => 'Posts', + 'action' => 'search', + '?' => ['foo' => 'bar'], + '#' => 'first', +]); + +// Output +/posts/search?foo=bar#first +``` + +The above example uses the `?` special key for specifying query string +parameters and `#` key for URL fragment. + +URL for named route: + +``` php +// Assuming a route is setup as a named route: +// $router->connect( +// '/products/{slug}', +// [ +// 'controller' => 'Products', +// 'action' => 'view', +// ], +// [ +// '_name' => 'product-page', +// ] +// ); + +echo $this->Url->build(['_name' => 'product-page', 'slug' => 'i-m-slug']); +// Will result in: +/products/i-m-slug +``` + +The 2nd parameter allows you to define options controlling HTML escaping, and +whether or not the base path should be added: + +``` php +$this->Url->build('/posts', [ + 'escape' => false, + 'fullBase' => true, +]); +``` + +### Building URLs from Route Paths + +`method` Cake\\View\\Helper\\UrlHelper::**buildFromPath**(string $path, array $params = [], array $options = []): string + +If you want to use route path strings, you can do that using this method: + +``` php +echo $this->Url->buildFromPath('Articles::index'); +// outputs: /articles + +echo $this->Url->buildFromPath('MyBackend.Admin/Articles::view', [3]); +// outputs: /admin/my-backend/articles/view/3 +``` + +URL with asset timestamp wrapped by a ``, here pre-loading +a font. Note: The file must exist and `Configure::read('Asset.timestamp')` +must return `true` or `'force'` for the timestamp to be appended: + +``` php +echo $this->Html->meta([ + 'rel' => 'preload', + 'href' => $this->Url->assetUrl( + '/assets/fonts/your-font-pack/your-font-name.woff2' + ), + 'as' => 'font', +]); +``` + +If you are generating URLs for CSS, Javascript or image files there are helper +methods for each of these asset types: + +``` php +// Outputs /img/icon.png +$this->Url->image('icon.png'); + +// Outputs /js/app.js +$this->Url->script('app.js'); + +// Outputs /css/app.css +$this->Url->css('app.css'); + +// Force timestamps for one method call. +$this->Url->css('app.css', ['timestamp' => 'force']); + +// Or disable timestamps for one method call. +$this->Url->css('app.css', ['timestamp' => false]); +``` + +## Customizing Asset URL generation + +If you need to customize how asset URLs are generated, or want to use custom +asset cache busting parameters you can use the `assetUrlClassName` option: + +``` php +// In view initialize +$this->loadHelper('Url', ['assetUrlClassName' => AppAsset::class]); +``` + +When using the `assetUrlClassName` you must implement the same methods as +`Cake\Routing\Asset` does. + +For further information check +[Router::url](https://api.cakephp.org/5.x/class-Cake.Routing.Router.html#_url) +in the API. diff --git a/docs/en/views/json-and-xml-views.md b/docs/en/views/json-and-xml-views.md new file mode 100644 index 0000000000..3ee380a693 --- /dev/null +++ b/docs/en/views/json-and-xml-views.md @@ -0,0 +1,250 @@ +# JSON and XML views + +The `JsonView` and `XmlView` integration with CakePHP's +[Controller Viewclasses](../controllers#controller-viewclasses) features and let you create JSON and XML responses. + +These view classes are most commonly used alongside `Cake\Controller\Controller::viewClasses()`. + +There are two ways you can generate data views. The first is by using the +`serialize` option, and the second is by creating normal template files. + +## Defining View Classes to Negotiate With + +In your `AppController` or in an individual controller you can implement the +`viewClasses()` method and provide all of the views you want to support: + +``` php +use Cake\View\JsonView; +use Cake\View\XmlView; + +public function viewClasses(): array +{ + return [JsonView::class, XmlView::class]; +} +``` + +You can optionally enable the json and/or xml extensions with +[File Extensions](../development/routing#file-extensions). This will allow you to access the `JSON`, `XML` or +any other special format views by using a custom URL ending with the name of the +response type as a file extension such as `http://example.com/articles.json`. + +By default, when not enabling [File Extensions](../development/routing#file-extensions), the `Accept` +header in the request is used for selecting which type of format should be rendered to the +user. An example `Accept` format that is used to render `JSON` responses is +`application/json`. + +## Using Data Views with the Serialize Key + +The `serialize` option indicates which view variable(s) should be +serialized when using a data view. This lets you skip defining template files +for your controller actions if you don't need to do any custom formatting before +your data is converted into json/xml. + +If you need to do any formatting or manipulation of your view variables before +generating the response, you should use template files. The value of +`serialize` can be either a string or an array of view variables to +serialize: + +``` php +namespace App\Controller; + +use Cake\View\JsonView; + +class ArticlesController extends AppController +{ + public function viewClasses(): array + { + return [JsonView::class]; + } + + public function index() + { + // Set the view vars + $this->set('articles', $this->paginate()); + // Specify which view vars JsonView should serialize. + $this->viewBuilder()->setOption('serialize', 'articles'); + } +} +``` + +You can also define `serialize` as an array of view variables to combine: + +``` php +namespace App\Controller; + +use Cake\View\JsonView; + +class ArticlesController extends AppController +{ + public function viewClasses(): array + { + return [JsonView::class]; + } + + public function index() + { + // Some code that created $articles and $comments + + // Set the view vars + $this->set(compact('articles', 'comments')); + + // Specify which view vars JsonView should serialize. + $this->viewBuilder()->setOption('serialize', ['articles', 'comments']); + } +} +``` + +Defining `serialize` as an array has added the benefit of automatically +appending a top-level `` element when using `XmlView`. +If you use a string value for `serialize` and XmlView, make sure that your +view variable has a single top-level element. Without a single top-level +element the Xml will fail to generate. + +## Using a Data View with Template Files + +You should use template files if you need to manipulate your view +content before creating the final output. For example, if we had articles with a field containing generated HTML, we would probably want to omit that from a +JSON response. This is a situation where a view file would be useful: + +``` php +// Controller code +class ArticlesController extends AppController +{ + public function index() + { + $articles = $this->paginate('Articles'); + $this->set(compact('articles')); + } +} + +// View code - templates/Articles/json/index.php +foreach ($articles as $article) { + unset($article->generated_html); +} +echo json_encode(compact('articles')); +``` + +You can do more complex manipulations, or use helpers to do formatting as well. +The data view classes don't support layouts. They assume that the view file will +output the serialized content. + +## Creating XML Views + +`class` **XmlView** + +By default when using `serialize` the XmlView will wrap your serialized +view variables with a `` node. You can set a custom name for +this node using the `rootNode` option. + +The XmlView class supports the `xmlOptions` option that allows you to +customize the options, such as `tags` or `attributes`, used to generate XML. + +An example of using `XmlView` would be to generate a [sitemap.xml](https://www.sitemaps.org/protocol.html). This document type requires that you +change `rootNode` and set attributes. Attributes are defined using the `@` +prefix: + +``` php +use Cake\View\XmlView; + +public function viewClasses(): array +{ + return [XmlView::class]; +} + +public function sitemap() +{ + $pages = $this->Pages->find()->all(); + $urls = []; + foreach ($pages as $page) { + $urls[] = [ + 'loc' => Router::url(['controller' => 'Pages', 'action' => 'view', $page->slug, '_full' => true]), + 'lastmod' => $page->modified->format('Y-m-d'), + 'changefreq' => 'daily', + 'priority' => '0.5', + ]; + } + + // Define a custom root node in the generated document. + $this->viewBuilder() + ->setOption('rootNode', 'urlset') + ->setOption('serialize', ['@xmlns', 'url']); + $this->set([ + // Define an attribute on the root node. + '@xmlns' => 'http://www.sitemaps.org/schemas/sitemap/0.9', + 'url' => $urls, + ]); +} +``` + +## Creating JSON Views + +`class` **JsonView** + +The JsonView class supports the `jsonOptions` option that allows you to +customize the bit-mask used to generate JSON. See the +[json_encode](https://php.net/json_encode) documentation for the valid +values of this option. + +For example, to serialize validation error output of CakePHP entities in a consistent form of JSON do: + +``` php +// In your controller's action when saving failed +$this->set('errors', $articles->errors()); +$this->viewBuilder() + ->setOption('serialize', ['errors']) + ->setOption('jsonOptions', JSON_FORCE_OBJECT); +``` + +### JSONP Responses + +When using `JsonView` you can use the special view variable `jsonp` to +enable returning a JSONP response. Setting it to `true` makes the view class +check if query string parameter named "callback" is set and if so wrap the json +response in the function name provided. If you want to use a custom query string +parameter name instead of "callback" set `jsonp` to required name instead of +`true`. + +## Choosing a View Class + +While you can use the `viewClasses` hook method most of the time, if you want +total control over view class selection you can directly choose the view class: + +``` php +// src/Controller/VideosController.php +namespace App\Controller; + +use App\Controller\AppController; +use Cake\Http\Exception\NotFoundException; + +class VideosController extends AppController +{ + public function export($format = '') + { + $format = strtolower($format); + + // Format to view mapping + $formats = [ + 'xml' => 'Xml', + 'json' => 'Json', + ]; + + // Error on unknown type + if (!isset($formats[$format])) { + throw new NotFoundException(__('Unknown format.')); + } + + // Set Out Format View + $this->viewBuilder()->setClassName($formats[$format]); + + // Get data + $videos = $this->Videos->find('latest')->all(); + + // Set Data View + $this->set(compact('videos')); + $this->viewBuilder()->setOption('serialize', ['videos']); + + // Set Force Download + return $this->response->withDownload('report-' . date('YmdHis') . '.' . $format); + } +} +``` diff --git a/docs/en/views/themes.md b/docs/en/views/themes.md new file mode 100644 index 0000000000..36a8f1f93f --- /dev/null +++ b/docs/en/views/themes.md @@ -0,0 +1,71 @@ +# Themes + +Themes in CakePHP are simply plugins that focus on providing template files. +See the section on [Plugin Create Your Own](../plugins#plugin-create-your-own). +You can take advantage of themes, allowing you to switch the look and feel of +your page quickly. In addition to template files, they can also provide helpers +and cells if your theming requires that. When using cells and helpers from your +theme, you will need to continue using the `plugin syntax`. + +First ensure your theme plugin is loaded in your application's `bootstrap` +method. For example: + +``` php +// Load our plugin theme residing in the folder /plugins/Modern +$this->addPlugin('Modern'); +``` + +To use themes, set the theme name in your controller's action or +`beforeRender()` callback: + +``` php +class ExamplesController extends AppController +{ + public function beforeRender(\Cake\Event\EventInterface $event): void + { + $this->viewBuilder()->setTheme('Modern'); + } +} +``` + +Theme template files need to be within a plugin with the same name. For example, +the above theme would be found in **plugins/Modern/templates**. +It's important to remember that CakePHP expects PascalCase plugin/theme names. Beyond +that, the folder structure within the **plugins/Modern/templates** folder is +exactly the same as **templates/**. + +For example, the view file for an edit action of a Posts controller would reside +at **plugins/Modern/templates/Posts/edit.php**. Layout files would reside in +**plugins/Modern/templates/layout/**. You can provide customized templates +for plugins with a theme as well. If you had a plugin named 'Cms', that +contained a TagsController, the Modern theme could provide +**plugins/Modern/templates/plugin/Cms/Tags/edit.php** to replace the edit +template in the plugin. + +If a view file can't be found in the theme, CakePHP will try to locate the view +file in the **templates/** folder. This way, you can create master template files +and simply override them on a case-by-case basis within your theme folder. + +## Theme Assets + +Because themes are standard CakePHP plugins, they can include any necessary +assets in their webroot directory. This allows for packaging and +distribution of themes. Whilst in development, requests for theme assets will be +handled by `Cake\Routing\Middleware\AssetMiddleware` (which is loaded +by default in cakephp/app `Application::middleware()`). To improve +performance for production environments, it's recommended that you [Symlink Assets](../deployment#symlink-assets). + +All of CakePHP's built-in helpers are aware of themes and will create the +correct paths automatically. Like template files, if a file isn't in the theme +folder, it will default to the main webroot folder: + +``` php +// When in a theme with the name of 'purple_cupcake' +$this->Html->css('main.css'); + +// creates a path like +/purple_cupcake/css/main.css + +// and links to +plugins/PurpleCupcake/webroot/css/main.css +``` diff --git a/docs/ja/404.md b/docs/ja/404.md new file mode 100644 index 0000000000..cf821dc68a --- /dev/null +++ b/docs/ja/404.md @@ -0,0 +1,6 @@ +orphan +True + +# ページが見つかりません。 + +お探しのページが見つかりません。お探しのページを見つけるために検索バーをお試しください。 diff --git a/docs/ja/appendices.md b/docs/ja/appendices.md new file mode 100644 index 0000000000..9bf9165555 --- /dev/null +++ b/docs/ja/appendices.md @@ -0,0 +1,29 @@ +# 付録 + +ここでは、各バージョンで導入された新機能に関する情報と、 +バージョン間の移行手順を解説します。 + +## 5.x 移行ガイド + +- [5.0 アップグレードガイド](appendices/5-0-upgrade-guide) +- [5.0 移行ガイド](appendices/5-0-migration-guide) + +## 後方互換性の補完 + +4.x の挙動に関する対応が必要な場合、または段階的な移行に関する助けが必要な場合、 +[Shim プラグイン](https://github.com/dereuromark/cakephp-shim) を確認してください。 +後方互換性を損なう変更を移行するのに役立ちます。 + +## 前方互換性の補完 + +前方互換性の補完は、次のメジャーリリース (4.x) のために、3.x アプリを準備できます。 + +既存の 3.x で 4.x の挙動に合わせたい場合、 +[Shim プラグイン](https://github.com/dereuromark/cakephp-shim) を確認してください。 +後方互換性を損なう変更を移行するのに役立つ可能性があります。 +3.x アプリが 4.x に近いほど、変更の差分は小さくなり、最終的なアップグレードはよりスムーズになります。 + +## 一般的な情報 + +- [CakePHP の開発プロセス](appendices/cakephp-development-process) +- [用語集](appendices/glossary) diff --git a/docs/ja/appendices/5-0-migration-guide.md b/docs/ja/appendices/5-0-migration-guide.md new file mode 100644 index 0000000000..eba58cfc44 --- /dev/null +++ b/docs/ja/appendices/5-0-migration-guide.md @@ -0,0 +1,335 @@ +# 5.0 移行ガイド + +CakePHP 5.0 には、破壊的な変更が含まれており、4.x リリースとの後方互換性はありません。 +5.0 にアップグレードする前に、最初に 4.5 にアップグレードし、すべての非推奨警告を解消してください。 + +5.0 にアップグレードする方法の段階的な手順については、 +[5.0 アップグレードガイド](../appendices/5-0-upgrade-guide) を参照してください。 + +## 非推奨機能の削除 + +4.5 で非推奨の警告を発していたすべてのメソッド、プロパティと機能が削除されました。 + +## 破壊的変更 + +非推奨機能の削除に加えて、破壊的変更が行われました。 + +### 全体 + +- 全ての関数について、可能な限り、引数や返り値の型が明示されるようになりました。DockBlockに書かれた注釈(annotation)に合致するように意図しましたが、DockBlock側を修正した箇所もあります。 + +- クラスのpropertyについても同様に型が可能な限り明示されるようになりました。こちらでも注釈を修正した箇所もあります。 + +- 次の定数が削除されました : `SECOND`, `MINUTE`, `HOUR`, `DAY`, `WEEK`, `MONTH`, `YEAR` + +- 全ての `#[\AllowDynamicProperties]` 指定が削除されました。これは以下のクラスで使用されていました。 + - `Command/Command` + - `Console/Shell` + - `Controller/Component` + - `Controller/Controller` + - `Mailer/Mailer` + - `View/Cell` + - `View/Helper` + - `View/View` + +- サポート対象のデータベースエンジンのバージョンが更新されました。 + - MySQL (5.7 以上) + - MariaDB (10.1 以上) + - PostgreSQL (9.6 以上) + - Microsoft SQL Server (2012 以上) + - SQLite 3 (3.16 以上) + +### Auth + +- Auth は削除されました。代わりに [cakephp/authentication](https://book.cakephp.org/authentication/3/ja/index.html) および + [cakephp/authorization](https://book.cakephp.org/authorization/3/ja/index.html) プラグインを使用してください。 + +### Cache + +- `Wincache` エンジンは削除されました。wincache拡張は PHP 8 ではサポートされていないため、です。 + +### Collection + +- `combine()` は、指定した key path や group path が存在しないまたは null 値の場合に、例外を投げるようになりました。 + この挙動は、 `indexBy()` や `groupBy()` と同じです。 + +### Console + +- `BaseCommand::__construct()` は削除されました。 + +- `ConsoleIntegrationTestTrait::useCommandRunner()` は、もはや必要無いため削除されました。 + +- `Shell` は削除されました。代わりに [Command](https://book.cakephp.org/5/en/console-commands/commands.html) をお使い下さい。 + +- `ConsoleOptionParser::addSubcommand()` は `Shell` の削除とともに削除されました。 + サブコマンドは `Command` クラスで置き換えてください。 + 必要なコマンド名は `Command::defaultName()` を実装することで定義できます。 + +- `BaseCommand` は `execute()` メソッドがフレームワークから呼び出される前後に `Command.beforeExecute` および + `Command.afterExecute` のイベントを発行するようになりました。 + +### Connection + +- `Connection::prepare()` は削除されました。これは、SQL文、パラメータ、パラメータ型の情報を渡すことで `Connection::execute()` の1回の呼び出しに置き換えることができます。 +- `Connection::enableQueryLogging()` は削除されました。データベース接続設定においてロギングを有効にしていない場合、 `$connection->getDriver()->setLogger()` を呼び出して logger インスタンスを設定することにより、後からロギングを有効にできます。 + +### Controller + +- `Controller::__construct()` は削除されました。サブクラスでoverrideしていた場合は、コードの調整をお願いします。 +- Component は、 Controller の読み込み後に dynamic property として設定されることは無くなりました。代わりに Controller は `__get()` を用いて Component へのアクセスを提供します。この変更は、 Component の存在有無を `property_exists()` でチェックしているアプリケーションに影響を与えます。 +- Component が発行していた `Controller.shutdown` イベントは、 `shutdown` から `afterFilter` に名称変更されました。Controller に合わせるためです。これにより callback はより首尾一貫したものとなります。 +- `PaginatorComponent` は削除されました。代わりに、 Controller で `$this->paginate()` を呼び出すか、直接 `Cake\Datasource\Paging\NumericPaginator` を用いて下さい。 +- `RequestHandlerComponent` は削除されました。アップグレード方法は [4.4 移行ガイド](https://book.cakephp.org/4/ja/appendices/4-4-migration-guide.html#requesthandlercomponent) を参照して下さい。 +- `SecurityComponent` は削除されました。代わりに `FormProtectionComponent` で耐タンパ性を得たり、 `HttpsEnforcerMiddleware` を使って HTTPS を強制したりして下さい。 +- `Controller::paginate()` は、その引数 `$settings` において `contain` オプション等を受け付けなくなりました。代わりに `finder` オプションを使って `$this->paginate($this->Articles, ['finder' => 'published'])` などとして下さい。事前にクエリを組み立てておいてそれを `paginate()` に渡すこともできます。例えば `$query = $this->Articles->find()->where(['is_published' => true]); $this->paginate($query);` となります。 + +### Core + +- 関数 `getTypeName()` は削除されました。代わりに、PHPの `get_debug_type()` をお使い下さい。 +- 依存ライブラリ `league/container` は `4.x` に更新されました。これによって `ServiceProvider` の実装には追加の型ヒントが必要となります。 +- `deprecationWarning()` には引数 `$version` が追加されました。 +- `App.uploadedFilesAsObjects` オプションは、PHPそのもののファイルアップロードにおける array 生成に倣い、削除されました。 +- `ClassLoader` は削除されました。代わりに Composer の autoload の仕組みをお使い下さい。 + +### Database + +- `DateTimeType` および `DateType` は、常に変更不可能(immutable)なオブジェクトを返すようになりました。また、 `Date` オブジェクトの interface は `ChronosDate` の interface を反映するようになり、CakePHP 4.x で存在した時刻関連のメソッドが無くなりました。 +- `DateType::setLocaleFormat()` は array を受け付けないようになりました。 +- `Query` は `callable` ではなく `\Closure` なパラメータのみを受け付けるようになりました。 callable なオブジェクトは PHP 8.1 で導入された、第一級 callable の記法で書き換え可能です。(訳注 : [PHPのマニュアル 「第一級callableを生成する記法」](https://www.php.net/manual/ja/functions.first_class_callable_syntax.php) ) +- `Query::execute()` は、結果を整形するコールバックを呼ばないようになりました。代わりに `Query::all()` をお使い下さい。 +- `TableSchemaAwareInterface` は削除されました。 +- `Driver::quote()` は削除されました。代わりに prepared statement をお使い下さい。 +- `Query::orderBy()` は `Query::order()` の代わりに追加されました。 +- `Query::groupBy()` は `Query::group()` の代わりに追加されました。 +- `SqlDialectTrait` は削除されました。ここで提供されていた全ての機能は `Driver` クラスそのものに実装されました。 +- `CaseExpression` は削除されました。代わりに `QueryExpression::case()` または `CaseStatementExpression` をお使い下さい。 +- `Connection::connect()` は削除されました。代わりに `$connection->getDriver()->connect()` をお使い下さい。 +- `Connection::disconnect()` は削除されました。代わりに `$connection->getDriver()->disconnect()` をお使い下さい。 +- クエリのログの scope として `queriesLog` だけではなく `cake.database.queries` も使えるようになりました。 +- 結果セットのバッファリングを有効化・無効化する機能は削除されました。常にバッファリングされます。 + +### Datasource + +- `getAccessible()` メソッドが `EntityInterface` に追加されました。ORM外でこの interface を実装している場合は、このメソッドも実装する必要があります。 +- `aliasField()` メソッドが `RepositoryInterface` に追加されました。ORM外でこの interface を実装している場合は、このメソッドも実装する必要があります。 + +### Event + +- Event に載せるデータ(payload) は、配列である必要があります。配列ではないオブジェクト、例えば `ArrayAccess` は array へのキャストで失敗して `TypeError` を出すようになります。 +- イベントハンドラは void メソッドとして実装し、結果は返り値として返却するのではなく `$event->setResult()` に渡す方法が推奨されます。 + +### Error + +- `ErrorHandler` および `ConsoleErrorHandler` は削除されました。対応方法は [4.4 移行ガイド](https://book.cakephp.org/4/ja/appendices/4-4-migration-guide.html#errorhandler-consoleerrorhandler) をご覧下さい。 +- `ExceptionRenderer` は削除されました。代わりに `WebExceptionRenderer` をお使い下さい。 +- `ErrorLoggerInterface::log()` は削除されました。代わりに `ErrorLoggerInterface::logException()` をお使い下さい。 +- `ErrorLoggerInterface::logMessage()` は削除されました。代わりに `ErrorLoggerInterface::logError()` をお使い下さい。 + +### Filesystem + +- Filesystem というパッケージは削除されました。 `Filesystem` というクラスは Utility のパッケージに移動されました。 + +### Http + +- `ServerRequest` の `files` は、 array とは互換性は無くなりました。この挙動は 4.1.0 でデフォルトでは停止されていました。この `files` は常に `UploadedFileInterfaces` オブジェクトを持つようになります。 + +### I18n + +- `FrozenDate` は Date に名称変更され、また `FrozenTime` も DateTime に名称変更されました。 + +- `Time` は `Cake\Chronos\ChronosTime` を継承するようになりました。その結果として変更不可能(immutable)になりました。 + +- `Date` オブジェクトは `DateTimeInterface` を継承しなくなりました。そのため、 `DateTime` オブジェクトと比較することはできません。 + 詳細は [cakephp/chronos のリリースドキュメント](https://github.com/cakephp/chronos/releases/tag/3.0.2) を参照してください。 + +- `Date::parseDateTime()` は削除されました。 + +- `Date::parseTime()` は削除されました。 + +- `Date::setToStringFormat()` および `Date::setJsonEncodeFormat()` は、配列を受け付けないようになりました。 + +- `Date::i18nFormat()` および `Date::nice()` は、タイムゾーンの引数を受け付けないようになりました。 + +- ベンダ名が接頭辞に付いたプラグイン(例えば `FooBar/Awesome`)への翻訳ファイルは、接頭辞を含むファイル名として下さい(例えば `foo_bar_awesome.po`)。これは、同名の接頭辞無しのプラグイン(例えば `Awesome`)の翻訳ファイル(この例では `awesome.po`)との衝突を避けるためのものです。 + +### Log + +- Logエンジンの設定において、特定のスコープを無効化する際には `false` ではなくて `null` を用いるようになりました。設定ファイルにおいて `'scopes' => false` となっている箇所は `'scopes' => null` と書き換えて下さい。 + +### Mailer + +- `Email` は削除されました。代わりに [Mailer](https://book.cakephp.org/5/ja/core-libraries/email.html) をお使い下さい。 +- ログのスコープとして `email` の代わりに `cake.mailer` も指定できるようになりました。 + +### ORM + +- `EntityTrait::has()` は、属性が存在してその値が `null` である場合、 `true` を返すようになりました。過去のCakePHPのバージョンにおいては `false` を返していました。4.x の挙動が必要な場合の対応方法は、4.5.0 のリリースノートを参照して下さい。(訳注 : 4.5のリリースノートは日本語には翻訳されていません。 [英語版の 4.5 の Migration Guide](https://book.cakephp.org/4/en/appendices/4-5-migration-guide.html#orm) の中では `EntityTrait::hasValue()` を使うように案内されています。) +- `EntityTrait::extractOriginal()` は `extractOriginalChanged()` と同様に、存在するフィールドのみを返すようになりました。 +- Finder の引数は連想配列である必要があります。過去にはこれは推奨事項という位置付けでした。 +- `TranslateBehavior` はデフォルトでは `ShadowTable` ストラテジを採用するようになりました。もしも `Eav` ストラテジを利用中で、その挙動を維持する必要があるのならば、設定を変更する必要があります。 +- `isUnique` ルールの `allowMultipleNulls` オプションは、デフォルトではtrueとなり、本来の 3.x の挙動に合致するようになりました。 +- `Table::query()` は、後述のクエリタイプごとのメソッドが提供されたことに伴い、削除されました。(訳注 : 5.0.5 時点においては実際には削除されておらず、 `Table::query()` は `Table::selectQuery()` を呼び出しているようです。) +- `Table::updateQuery()`, `Table::selectQuery()`, `Table::insertQuery()`, `Table::deleteQuery()` の4つのメソッドが追加されました。これらは以下に示すクエリタイプごとのオブジェクトを返します。 +- `SelectQuery`, `InsertQuery`, `UpdateQuery`, `DeleteQuery` の4つの型が追加されました。クエリのタイプが型として指定されることで、クエリタイプが変更されたり、無関係な別のクエリタイプの関数を呼んだりするのを防ぐようになりました。 +- `Table::_initializeSchema()` は削除されました。代わりに `initialize()` の中で `$this->getSchema()` を呼んで下さい。 +- `SaveOptionsBuilder` は削除されました。通常の配列をお使い下さい。 + +### Routing + +- `Router` のstaticメソッドの `connect()`, `prefix()`, `scope()`, `plugin()` は削除されました。代わりに `RouteBuilder` のインスタンスのメソッドをお使い下さい。 +- `RedirectException` は削除されました。代わりに `\Cake\Http\Exception\RedirectException` をお使い下さい。 + +### TestSuite + +- `TestSuite` は削除されました。単体テストの設定をカスタマイズするには、環境変数を使って下さい。 +- `TestListenerTrait` は削除されました。PHPUnitがこれらの listener のサポートを打ち切ったためです。詳細は [PHPUnit 10 へのアップグレード](../appendices/phpunit10) を参照して下さい。 +- `IntegrationTestTrait::configRequest()` が複数回呼ばれた際、設定を上書きするのではなく merge するようになりました。 + +### Validation + +- `Validation::isEmpty()` は、ファイルアップロードの配列には対応しないようになりました。PHPのファイルアップロードの配列への対応は `ServerRequest` からも削除されていますので、この問題はテストの外側の問題だとは捉えないようにして下さい。 +- 以前は、ほとんどの validation エラーの文言は `The provided value is invalid` という単純なものでした。今では例えば `` The provided value must be greater than or equal to \`5\ `` のように、もう少し詳細に言及するようになりました。 + +### View + +- `ViewBuilder` のオプションは、本当の意味で連想配列となりました(stringのキーを用います)。 +- `NumberHelper` および `TextHelper` は `engine` 設定を受け付けないようになりました。 +- `ViewBuilder::setHelpers()` のパラメータ `$merge` は削除されました。代わりに `ViewBuilder::addHelpers()` をお使い下さい。 +- `View::initialize()` の中では、 `loadHelper()` よりも `addHelper()` の方が望ましいようになりました。設定されたヘルパーはいずれにせよ後で読み込まれます。 +- `View\Widget\FileWidget` は、PHPのファイルアップロードの配列とは互換性が無くなりました。この変更は `ServerRequest` や `Validation` と同じ趣旨のものです。 +- `FormHelper` は、CSRF対策トークンのフィールドでは `autocomplete=off` を設定しないようになりました。これはSafariのバグへの応急措置として設定されましたが、今ではもう関係はありません。 + +## 非推奨 + +以下は非推奨となったメソッド、プロパティ、挙動の一覧です。これらの機能は 5.x では動作し続けますが、 6.0 では削除される予定です。 + +### Database + +- `Query::order()` は非推奨となりました。代わりに `Query::orderBy()` をお使い下さい。この変更はSQL文の機能名称に合わせたものになります。 +- `Query::group()` は非推奨となりました。代わりに `Query::groupBy()` をお使い下さい。この変更はSQL文の機能名称に合わせたものになります。 + +### ORM + +- `Table::find()` のオプションを配列で指定することは非推奨となりました。代わりに [名前付き引数](https://www.php.net/manual/ja/functions.arguments.php#functions.named-arguments) を使用して下さい。例えば `find('all', ['conditions' => $array])` の代わりに `find('all', conditions: $array)` です。カスタムの finder オプションについても同様に `find('list', ['valueField' => 'name'])` の代わりに `find('list', valueField: 'name')` を使用して下さい。複数の名前付き引数の場合は例えば `find(type: 'list', valueField: 'name', conditions: $array)` となります。 + +## 新機能 + +### 進化した型チェック + +CakePHP 5 は、PHP 8.1 以上で有効な型システムを活用します。 +CakePHPは `assert()` を使うことによっても、詳細なエラーメッセージや、型の安全性を提供します。 +本番運用モードにおいては、 `assert()` でのコード生成を停止させてパフォーマンスを向上させることができます。 +この方法については [Symlink Assets](../deployment#symlink-assets) を参照して下さい。(訳注 : このリンク先は、2024年3月時点ではまだ翻訳されていません。 [英語版](https://book.cakephp.org/5/en/deployment.html#symlink-assets) で `zend.assertions` を設定している箇所を参照して下さい。) + +### Collection + +- コールバック関数を用いて重複した値を除去するメソッド `unique()` が追加されました。 +- `reject()` は、trueっぽい値のみを除外するデフォルトのコールバック関数が利用可能になりました。これは `filter()` のデフォルトの挙動の真逆となります。 + +### Core + +- `PluginInterface` には `services()` メソッドが追加されました。 +- [プラグインの読み込み](../plugins#loading-a-plugin) に `PluginCollection::addFromConfig()` が追加されました。 + +### Database + +- `ConnectionManager` は read / write の接続ロールをサポートしました。データベース接続設定において `read` や `write` のキーで指定した設定項目によって共通の設定項目を上書きすることで、接続ロールを構成することができます。 +- 結果セットを整形するコールバックを実行できるメソッド `Query::all()` が追加されました。 +- SQLにコメントを追加するメソッド `Query::comment()` が追加されました。これによりクエリのデバッグが楽になります。 +- PHPの enum と、データベースの string や integer 型との間の橋渡しをする `EnumType` が追加されました。 +- `DriverInterface` に `getMaxAliasLength()` と `getConnectionRetries()` が追加されました。 +- 以前は integer 型の主キー全てに自動的に auto-increment 指定を入れていましたが、 "id" という名前の integer 型の主キーにのみこの動作をするようになりました。 'autoIncrement' を false に設定することで、この挙動を無効にできます。 + +### Http + +- [PSR-17](https://www.php-fig.org/psr/psr-17/) factory interface への対応が追加されました。これによって `cakephp/http` パッケージは、 php-http のように自動で interface resolution を有効にするライブラリに対してclient実装を提供できるようになりました。 +- 例外を発することなく便利に cookie を操作できる方法として `CookieCollection::__get()` と `CookieCollection::__isset()` が追加されました。 + +### ORM + +### 必須フィールド + +モデルのエンティティには opt-in 方式で利用可能な新しい機能、より厳格なプロパティ操作、が追加されました。 +この新しい機能は「必須フィールド」と呼ばれます。 +有効化されると、エンティティで定義されていないプロパティにアクセスした場合に例外が発生します。 +これは以下のようなコードに影響を与えます: + +``` php +$entity->get(); +$entity->has(); +$entity->getOriginal(); +isset($entity->attribute); +$entity->attribute; +``` + +フィールドは `array_key_exists` が true を返す場合に「定義されている」と判断されます。 +これは null 値も含まれます。 +この機能はうんざりするようなものであるかもしれないので、5.0 まで導入が延期されてきました。 +将来はこれをデフォルトでオンにしようと検討していますが、フィードバックがあればぜひお知らせ下さい。 + +### 型付きFinderパラメータ + +テーブルの finder のパラメータは必須のものにすることができるようになりました。 +例えばブログ記事の投稿をカテゴリまたはユーザで検索するメソッドは、以前はこのようなコードになりました: + +``` php +public function findByCategoryOrUser(SelectQuery $query, array $options) +{ + if (isset($options['categoryId'])) { + $query->where(['category_id' => $options['categoryId']]); + } + if (isset($options['userId'])) { + $query->where(['user_id' => $options['userId']]); + } + + return $query; +} +``` + +これが今では次のように書くことができます: + +``` php +public function findByCategoryOrUser(SelectQuery $query, ?int $categoryId = null, ?int $userId = null) +{ + if ($categoryId) { + $query->where(['category_id' => $categoryId]); + } + if ($userId) { + $query->where(['user_id' => $userId]); + } + + return $query; +} +``` + +この finder を呼び出す際は `find('byCategoryOrUser', userId: $somevar)` と書くことができます。 +さらに、特別な名前付き引数を用いて、条件を追加することもできます。例えば `find('byCategoryOrUser', userId: $somevar, conditions: ['enabled' => true])` のようになります。 + +同様の変更が `RepositoryInterface::get()` にも追加されました: + +``` php +public function view(int $id) +{ + $author = $this->Authors->get($id, [ + 'contain' => ['Books'], + 'finder' => 'latest', + ]); +} +``` + +以前は上記のようなコードでしたが、今後は以下のようにも書けます: + +``` php +public function view(int $id) +{ + $author = $this->Authors->get($id, contain: ['Books'], finder: 'latest'); +} +``` + +### TestSuite + +- Integrationテストにおいて、次に発行するリクエストのヘッダに、JSON でやり取りする趣旨のヘッダを付与するメソッド `IntegrationTestTrait::requestAsJson()` が追加されました。 + +### Plugin Installer + +- プラグインのインストーラが更新されて、プラグインのクラスの autoload を自動的に制御するようになりました。 `composer.json` から、名前空間とパスの対応関係マップを削除して、 `composer dumpautoload` を実行することでもプラグインを動作させられます。 diff --git a/docs/ja/appendices/5-0-upgrade-guide.md b/docs/ja/appendices/5-0-upgrade-guide.md new file mode 100644 index 0000000000..c87be45b47 --- /dev/null +++ b/docs/ja/appendices/5-0-upgrade-guide.md @@ -0,0 +1,60 @@ +# 5.0 アップグレードガイド + +まず始めに、お手元のアプリケーションが CakePHP 4.x の最新バージョンで動作していることを確認して下さい。 + +## 非推奨警告の対応 + +CakePHP 4.x の最新バージョンで動作していることが確認できたら **config/app.php** の設定を変更して、非推奨警告を有効にします: + +``` text +'Error' => [ + 'errorLevel' => E_ALL, +] +``` + +これによって全ての警告が見えるようになります。アップグレードの作業を始める前に確実にこれを済ませておいて下さい。 + +いくつか、影響の大きな非推奨項目があります。 + +- `Table::query()` は 4.5.0 で非推奨となりました。代わりに `selectQuery()`, `updateQuery()`, `insertQuery()`, `deleteQuery()` を使用して下さい。 + +## PHP 8.1 にアップグレード + +もしもPHPのバージョンが **8.1 または それ以上** ではない場合、CakePHPのアップデートをする前にPHPのアップグレードをして下さい。 + +> [!NOTE] +> CakePHP 5.0 の実行には **最低でも PHP 8.1** が必要です。 + +## アップグレード・ツール の利用 + +> [!NOTE] +> このアップグレード・ツールは、最新の CakePHP 4.x でのみ実行可能です。CakePHP を 5.0 にした後では実行することはできません。 + +CakePHP 5 では、union型 や `mixed` 型を有効活用するので、メソッドのシグネチャやファイル名などで、後方互換性を持たない変更が多く含まれます。 +つまらない仕事をさっさと片付けるために、アップグレード用の CLI ツールを利用して下さい。 + +``` bash +# Install the upgrade tool +git clone https://github.com/cakephp/upgrade +cd upgrade +git checkout 5.x +composer install --no-dev +``` + +このアップグレードツールがインストールされると、お手元のアプリケーションやプラグインにおいて実行可能となります: + +``` text +bin/cake upgrade rector --rules cakephp50 +bin/cake upgrade rector --rules chronos3 +``` + +## CakePHPの依存関係の更新 + +ツールでのアップグレードが完了した後には、 `composer.json` に示されている、CakePHPやそのプラグイン、PHPUnit、そしてその他の多くの依存関係をアップグレードしましょう。 +この作業の内容は、お手元のアプリケーションの状況によって変わりますので、 `composer.json` ファイルを、お手元のアプリケーションのものと、 CakePHP 5.x のアプリのテンプレート [cakephp/app](https://github.com/cakephp/app/blob/5.x/composer.json) とで見比べると良いでしょう。 + +`composer.json` のバージョンが調整されたら、 `composer update -W` を実行して、結果を確認しましょう。 + +## アプリのファイルを、アプリのテンプレートに沿って更新 + +次に、アプリケーションの他のファイルについても、アプリのテンプレート [cakephp/app](https://github.com/cakephp/app/blob/5.x/) を見て、必要なアップデートを施します。 diff --git a/docs/ja/appendices/5-1-migration-guide.md b/docs/ja/appendices/5-1-migration-guide.md new file mode 100644 index 0000000000..750d608d34 --- /dev/null +++ b/docs/ja/appendices/5-1-migration-guide.md @@ -0,0 +1,135 @@ +# 5.1 移行ガイド + +5.1.0 リリースは 5.0 と後方互換性があります。 +新機能が追加され、新たな非推奨機能が導入されます。 +5.x で非推奨とされた機能は 6.0.0 で削除されます。 + +## 動作の変更 + +- Connectionは、設定に `read` または `write` キーが存在する場合、その値に関係なく一意の読み取りおよび書き込みドライバーを作成するようになりました。 +- FormHelper は、 `required` 属性が設定されている入力要素に対して `aria-required` 属性を生成しなくなりました。 + これらの要素では `aria-required` 属性は冗長であり、HTML のバリデーション警告を引き起こします。 + スタイリングやスクリプトで `aria-required` 属性を使用している場合は、アプリケーションを更新する必要があります。 +- 重複した名前でアソシエーションを追加しようとすると例外が発生するようになりました。 + 必要に応じて `$table->associations()->has()` を使って条件付きでアソシエーションを定義できます。 +- テキストユーティリティおよび TextHelper の省略や最大長に関するメソッドは、 + `...` の代わりに UTF-8 文字の `ellipsis` を使用するようになりました。 +- `TableSchema::setColumnType()` は、指定したカラムが存在しない場合に例外をスローするようになりました。 +- `PluginCollection::addPlugin()` は、同じ名前のプラグインがすでに追加されている場合に例外をスローするようになりました。 +- `TestCase::loadPlugins()` は、以前にロードされたプラグインをすべてクリアするようになりました。 + そのため、以降のテストで必要なすべてのプラグインを指定する必要があります。 +- `groups` を使用する `Cache` 設定用のハッシュアルゴリズムが変更されました。 + すべてのキーに対して新しいグループプレフィックスのハッシュが生成されるため、キャッシュミスが発生します。 + 完全にコールドキャッシュとなるのを避けるため、段階的なデプロイを検討してください。 +- `FormHelper::getFormProtector()` は、従来の型に加えて `null` を返すようになりました。 + これにより動的なビューコードがエラーになりにくくなりますが、ほとんどのアプリケーションには影響しません。 +- `Table::findList()` の `valueSeparator` のデフォルト値が `;` から半角スペースに変更されました。 +- `ErrorLogger` は `Psr\Log\LogTrait` を使用するようになりました。 +- `Database\QueryCompiler::$_orderedUnion` は削除されました。 + +## 非推奨 + +### I18n + +- `_cake_core_` キャッシュ設定キーは `_cake_translations_` に変更されました。 + +### Mailer + +- `Mailer::setMessage()` は非推奨となりました。このメソッドは直感的でない動作をし、利用頻度も非常に低いためです。 + +## 新機能 + +### Cache + +- `RedisEngine` で Redis への TLS 接続を有効にする `tls` オプションがサポートされるようになりました。。 + `ssl_ca`、`ssl_cert`、`ssl_key` オプションを使用して Redis 用の TLS コンテキストを定義できます。 + +### Command + +- `bin/cake plugin list` が追加され、利用可能なすべてのプラグイン、そのロード設定、およびバージョンを一覧表示できるようになりました。 +- オプションの `Command` 引数に `default` 値を指定できるようになりました。 +- `BannerHelper` が追加されました。このコマンドヘルパーは、テキストをカラフルな背景と余白付きのバナーとして整形できます。 +- `ConsoleOutput` に `info.bg`、`warning.bg`、`error.bg`、`success.bg` のデフォルトスタイルが追加されました。 + +### Console + +- `Arguments::getBooleanOption()` および `Arguments::getMultipleOption()` が追加されました。 +- `Arguments::getArgument()` は、未知の引数名が指定された場合に例外をスローするようになりました。 + これにより、オプション名と引数名の混同を防ぐことができます。 + +### Controller + +- コンポーネントでも、コントローラやコマンドと同様に、DIコンテナを使用して依存関係を解決し、コンストラクタの引数として受け取れるようになりました。 + +### Core + +- `PluginConfig` が追加されました。このクラスを使用すると、利用可能なすべてのプラグイン、そのロード設定、およびバージョンを取得できます。 +- `toString`、 `toInt`、 `toBool` 関数が追加されました。これらの関数は、リクエストデータや他の入力値を型安全にキャストし、変換に失敗した場合は `null` を返します。 +- `pathCombine()` が追加され、重複や末尾のスラッシュを気にせずパスを組み立てられるようになりました。 +- `BaseApplication` および `BasePlugin` クラスに新しい `events` フックが追加されました。 + このフックは、アプリケーションのグルーバルイベントリスナーを登録する推奨方法です。詳しくは [Registering Listeners](../core-libraries/events#registering-event-listeners) を参照してください。 + +### Database + +- `point`、 `linestring`、 `polygon`、 `geometry` 型のサポートが追加されました。 + これらの型は地理空間やデカルト座標を扱う際に便利です。SQLite のサポートは内部的にテキストカラムを使用しており、 + データを地理空間値として操作するための関数はありません。 +- `SelectQuery::__debugInfo()` に、クエリがどのコネクションロール用のものかが含まれるようになりました。 +- `SelectQuery::intersect()` および `SelectQuery::intersectAll()` が追加されました。 + これらのメソッドにより、 `INTERSECT` および `INTERSECT ALL` 結合を使用したクエリを記述できるようになりました。 +- `intersect`、 `intersect-all`、および `set-operations-order-by` 機能がサポートされるようになりました。 +- 4.x に存在したバッファリングなしでレコードを取得する機能が復活しました。 + `SelectQuery::enableBufferedResults()`、\` SelectQuery::disableBufferedResults()、 + SelectQuery::isBufferedResultsEnabled()\` メソッドが再追加されました。 + +### Datasource + +- `RulesChecker::remove()`、 `removeCreate()`、 `removeUpdate()`、および + `removeDelete()` メソッドが追加されました。これらのメソッドにより、ルールを名前で削除できるようになりました。 + +### Http + +- `SecurityHeadersMiddleware::setPermissionsPolicy()` が追加されました。このメソッドにより、`permissions-policy` ヘッダー値を定義できるようになりました。 +- `Client` はリクエスト送信時に `HttpClient.beforeSend` および `HttpClient.afterSend` イベントを発火するようになりました。 + これらのイベントを利用して、ログ記録、キャッシュ、テレメトリ収集などを行うことができます。 +- `Http\Server::terminate()` が追加されました。 + このメソッドは `Server.terminate` イベントを発火し、fastcgi 環境ではレスポンス送信後にロジックを実行できます。 + その他の環境では `Server.terminate` イベントはレスポンス送信 *前* に実行されます。 + +### I18n + +- `Number::formatter()` および `currency()` は、丸め方法を上書きする `roundingMode` オプションを受け付けるようになりました。 +- `toDate` および `toDateTime` 関数が追加されました。これらの関数は、リクエストデータや他の入力値を型安全にキャストし、変換に失敗した場合は `null` を返します。 + +### ORM + +- アソシエーションのファインダークエリで `preserveKeys` オプションを設定できるようになりました。 + これにより、 `formatResults()` と組み合わせて、アソシエーションのファインダー結果を連想配列として返すことができます。 +- 名前に `json` を含む SQLite カラムを `JsonType` にマッピングできるようになりました。 + この機能は現時点ではオプトインであり、アプリで `ORM.mapJsonTypeForSqlite` 設定値を `true` にすることで有効になります。 + +### TestSuite + +- CakePHP およびアプリのテンプレートは PHPUnit `^10.5.5 || ^11.1.3"` を使用するように更新されました。 - `ConnectionHelper` のメソッドがすべて static になりました。このクラスは状態を持たず、メソッドが static に更新されました。 - `LogTestTrait` が追加されました。この新しいトレイトにより、テスト内でログを簡単にキャプチャし、ログメッセージの有無をアサートできるようになりました。 - `IntegrationTestTrait::replaceRequest()` が追加されました。 + +### Utility + +- `Hash::insert()` および `Hash::remove()` は、 `array` データに加えて `ArrayAccess` オブジェクトも受け付けるようになりました。 + +### Validation + +- `Validation::enum()` および `Validator::enum()` が追加されました。これらのバリデーションメソッドにより、Backed Enum 値の検証が簡単になりました。 +- `Validation::enumOnly()` および `Validation::enumExcept()` が追加されました。これらのメソッドにより、特定のケースの検証や、Backed Enum 値のバリデーションをさらに簡単に行うことができます。 + +### View + +- View cells は、アクションの前後で `Cell.beforeAction` および `Cell.afterAction` イベントを発火するようになりました。 +- `NumberHelper::format()` は、丸め方法を上書きする `roundingMode` オプションを受け付けるようになりました。 + +### Helpers + +- `TextHelper::autoLinkUrls()` に、リンクラベルの表示を改善するためのオプションが追加されました: + - `stripProtocol`: リンク先の先頭から `http://` や `https://` を取り除きます。デフォルトは無効です。 + - `maxLength`: リンクラベルの最大長を指定します。デフォルトは無効です。 + - `ellipsis`: リンクラベルの末尾に付加する文字列です。デフォルトはUTF8バージョンです。 +- `HtmlHelper::meta()` で `meta('csrfToken')` を使うことで、現在の CSRF トークンを含む meta タグを生成できるようになりました。 diff --git a/docs/ja/appendices/5-2-migration-guide.md b/docs/ja/appendices/5-2-migration-guide.md new file mode 100644 index 0000000000..ec0cdc719d --- /dev/null +++ b/docs/ja/appendices/5-2-migration-guide.md @@ -0,0 +1,92 @@ +# 5.2 移行ガイド + +5.2.0 リリースは 5.0 と後方互換性があります。 +新機能が追加され、新たな非推奨機能が導入されます。 +5.x で非推奨とされた機能は 6.0.0 で削除されます。 + +## 動作の変更 + +- `ValidationSet::add()` は、すでに定義されている名前でルールが追加された場合にエラーを発生させるようになりました。 + この変更は、ルールが誤って上書きされるのを防ぐことを目的としています。 +- `Http\Session` は、無効なセッション・プリセットが使用された場合に例外を発生させるようになりました。 +- `FormProtectionComponent` は、 `Cake\Controller\Exception\FormProtectionException` を発生させるようになりました。 + このクラスは `BadRequestException` のサブクラスであり、ログからフィルタリングできる利点があります。 +- `NumericPaginator::paginate()` は、 `SelectQuery` インスタンスが渡された場合でも `finder` オプションを使用するようになりました。 + +## 非推奨 + +### Console + +- `Arguments::getMultipleOption()` は非推奨になりました。代わりに `getArrayOption()` を使用してください。 + +### Datasource + +- `EntityInterface` インスタンスを文字列にキャストする機能は非推奨となりました。 + 代わりにエンティティを `json_encode()` してください。 +- `EntityInterface::set()` を使って複数のエンティティフィールドを一括代入することは非推奨となりました。 + 代わりに `EntityInterface::patch()` を使用してください。例えば、 + `$entity->set(['field1' => 'value1', 'field2' => 'value2'])` のような使い方は + `$entity->patch(['field1' => 'value1', 'field2' => 'value2'])` に変更してください。 + +### Event + +- イベントリスナーやコールバックから値を返すことは非推奨となりました。代わりに `$event->setResult()` を使用してください。 + また、イベントの伝播を停止したい場合は `$event->stopPropogation()` を使用してください。 + +### View + +- `FormHelper` の `errorClass` オプションは非推奨となり、テンプレート文字列の使用が推奨されます。 + アップグレードするには、 `errorClass` の定義をテンプレートセットに移動してください。 + 詳細は [Customizing Templates](../views/helpers/form#customizing-templates) を参照してください。 + +## 新機能 + +### Console + +- `cake counter_cache` コマンドが追加されました。このコマンドは `CounterCacheBehavior` を使用しているモデルのカウンターを再生成するために使用できます。 +- `ConsoleIntegrationTestTrait::debugOutput()` により、コンソールコマンドの統合テストのデバッグが容易になりました。 +- `ConsoleInputArgument` は `separator` オプションをサポートするようになりました。 + このオプションにより、位置引数を `,` などの文字列で区切ることができ、 + CakePHP は引数を配列として分割して処理します。 +- `Arguments::getArrayArgumentAt()` および `Arguments::getArrayArgument()` が追加されました。 + これらのメソッドにより、区切り文字 `separator` で区切られた位置引数を配列として取得できます。 +- `ConsoleInputOption` は `separator` オプションをサポートするようになりました。 + このオプションにより、オプション値を `,` などの文字列で区切ることができ、 + CakePHP は引数を配列として分割して処理します。 +- `Arguments::getArrayArgumentAt()`、 `Arguments::getArrayArgument()`、および + `Arguments::getArrayOption()` が追加されました。これらのメソッドにより、 + 区切り文字 `separator` で区切られた位置引数やオプションを配列として取得できます。 + +### Database + +- `nativeuuid` 型が追加されました。この型により、MariaDB を使用した Mysql 接続で `uuid` カラムを利用できるようになりました。他のすべてのドライバーでは、 `nativeuuid` は `uuid` のエイリアスです。 +- `Cake\Database\Type\JsonType::setDecodingOptions()` が追加されました。このメソッドにより、 `json_decode()` の `$flags` 引数の値を設定できます。 +- `CounterCacheBehavior::updateCounterCache()` が追加されました。このメソッドにより、設定された関連付けのすべてのレコードのカウンターキャッシュ値を更新できます。 + 同様の処理をコンソールから実行する `CounterCacheCommand` も追加されました。 +- `Cake\Database\Driver::quote()` が追加されました。このメソッドは、プリペアドステートメントが使用できない場合に、SQL クエリで使用する値をクォートする方法を提供します。 + +### Datasource + +- アプリケーションルールで `Closure` を使用してバリデーションメッセージを定義できるようになりました。 + これにより、エンティティの状態やバリデーションルールのオプションに基づいて動的なバリデーションメッセージを作成できます。 + +### Error + +- カスタム例外に対して、 `ErrorController` で特定のエラーハンドリングロジックを定義できるようになりました。 + +### ORM + +- `CounterCacheBehavior::updateCounterCache()` が追加されました。このメソッドにより、設定された関連付けのすべてのレコードのカウンターキャッシュ値を更新できます。 +- `BelongsToMany::setJunctionProperty()` および `getJunctionProperty()` が追加されました。 + これらのメソッドにより、中間テーブルのレコードをハイドレートする際に使用される `_joinData` プロパティをカスタマイズできます。 +- `Table::findOrCreate()` は、第2引数に配列を直接渡してデータを指定できるようになりました。 + +### TestSuite + +- `TestFixture::$strictFields` プロパティが追加されました。このプロパティを有効にすると、フィクスチャーのレコードリストにスキーマに存在しないフィールドが含まれている場合にエラーが発生します。 + +### View + +- `FormHelper::deleteLink()` が追加されました。これは、テンプレート内で `DELETE` メソッドを使用した削除リンクを簡単に作成できるラッパーです。 +- `HtmlHelper::importmap()` が追加されました。このメソッドにより、JavaScript ファイルのインポートマップを定義できます。 +- `FormHelper` は、フォームコントロールの div にクラスを適用するために `containerClass` テンプレートを使用するようになりました。デフォルト値は `input` です。 diff --git a/docs/ja/appendices/cakephp-development-process.md b/docs/ja/appendices/cakephp-development-process.md new file mode 100644 index 0000000000..e0bae9e55e --- /dev/null +++ b/docs/ja/appendices/cakephp-development-process.md @@ -0,0 +1,43 @@ +# CakePHP の開発プロセス + +ここで CakePHP フレームワークの開発をする時に使ってるプロセスを説明しようと思います。 +私たちはチケットや IRC チャットを通したコミュニティーの対話に非常に信頼しています。 +IRC は [開発チーム](https://github.com/cakephp?tab=members) のメンバーを見つけて +アイデア、最新のコードについて議論する、全般的なコメントをするのに最適な場所です。 +もし何か、より正式な要望の提出、リリースに問題があるなどの場合は、チケットシステムは +あなたの考えを共有する最適な場所となります。 + +私たちは現在 CakePHP の4つのバージョンを整備しています。 + +- **タグ付けリリース** (*tagged release*) : タグ付けリリースは、機能より安定が重要な + 製品のために用意されたリリースです。これらのリリースに対して起こった課題は、関連する + ブランチで修正され、次のリリースの一部になります。 +- **メインラインブランチ** (*mainline branch*) : これらのブランチは、全てのバグ修正が + マージされるところです。安定版リリースは、これらのブランチからタグ付けされます。 + `master` は、現行リリースシリーズのためのメインラインブランチです。 `2.x` は、 + 2.x リリースシリーズの保守ブランチです。もし、安定版リリースを使用していて、 + タグ付けリリースに入っていない修正が必要なら、これをチェックしてください。 +- **開発ブランチ** (*development branches*) : 開発ブランチは最先端の修正と機能を + 含みます。これらはメジャーバージョン番号にちなんで命名されます。例えば、 *3.next* + になります。一旦安定版リリースの瞬間が近づいてきたとき、開発ブランチは + メインラインブランチにマージされます。 +- **機能ブランチ** (*feature branches*) : 機能ブランチは完了してないまたは安定しない + 可能性のある機能で、最先端の機能セットに興味のあるへビーユーザーのみに推奨されており、 + コミュニティーへ貢献のお返しをする意思もあります。 + 機能ブランチは次の規約を元に命名されています: *バージョン-機能* 。 + 例としては、 *3.3-router* 、これは 3.3 の Router のための新しい機能となります。 + +このことが、きっとどのバージョンがあなたにとって正しいかどうかの理解を助けることでしょう。 +一度バージョンを選択したら、バグ報告やコードの全般的なコメントをせずにはいられないかもしれません。 + +- もし安定バージョンまたは保守ブランチを使っているなら、チケットを送るか + IRC で私たちと議論してください。 +- もし開発ブランチか機能ブランチを使っているなら、まず最初に行くのは IRC です。 + もし言いたいことがあり、1~2日 IRC で私たちと会うことが出来なかったら、 + チケットを送ってください。 + +もし問題を見つけたら、テストを書くことが最高の答えです。テストを書く中で私たちが提案する +最大のアドバイスは、コアに含まれるテストを見ましょうということです。 + +いつも通り、どんな質問でもコメントでもありましたら、 irc.freenode.net の \#cakephp で +私たちを訪ねてください。 diff --git a/docs/ja/appendices/glossary.md b/docs/ja/appendices/glossary.md new file mode 100644 index 0000000000..6082fe8e14 --- /dev/null +++ b/docs/ja/appendices/glossary.md @@ -0,0 +1,104 @@ +# 用語集 + +
    + +CDN +Content Delivery Network の略。世界中のデータセンターにあなたのコンテンツを +配信するために利用するサードパーティベンダーです。地理的に分散したユーザーの近くの +静的なアセットを送信するのに便利です。 + +カラム +データベーステーブルの中のテーブルカラムを参照するときに ORM の中で使用されます。 + +CSRF +クロスサイトリクエストフォージェリ(*Cross Site Request Forgery*)。 +再生攻撃、二重投稿、他ドメインからの偽造リクエストを防止します。 + +DSN +データソース名。URI のような書式の接続文字列フォーマット。CakePHP は、キャッシュ、 +データベース、ログ、Eメール接続のための DSN に対応します。 + +ドット記法 +ドット記法は、 `.` を用いてネストされたレベルを区切ることによって配列のパスを定義します。 +例: + + Cache.default.engine + +は以下の値を指し示します。 : + +``` php +[ + 'Cache' => [ + 'default' => [ + 'engine' => 'File' + ] + ] +] +``` + +DRY +同じことを繰り返さない (Don’t repeat yourself)。これはあらゆる情報の繰り返しを +少なくするためのソフトウェア開発の原則です。CakePHP では同じコードは1カ所に書いて +再利用するという形で DRY 原則に従っています。 + +フィールド +エンティティーのプロパティー、もしくはデータベースのカラムを記述するために +使用される一般的な用語。FormHelper と組み合わせて使用されることがよくあります。 + +HTML属性 +HTML の属性を構成するキー =\> 値の配列。例: + +``` text +// これを与えると +['class' => 'my-class', 'target' => '_blank'] + +// これが生成される +class="my-class" _target="blank" +``` + +オプションが最小化できるか、名前そのものが値として許可される場合は、 +`true` が利用できます。 : + +``` text +// これを与えると +['checked' => true] + +// これが生成される +checked="checked" +``` + +PaaS +Platform as a Service の略。クラウドベースのホスティング、データベース、 +キャッシュリソースを提供します。有名なプロバイダーは、Heroku、EngineYard、 +PagodaBox などです。 + +プロパティー +ORM エンティティーにマップされたカラムを参照するときに使用されます。 + +プラグイン記法 +プラグイン記法はドットで区切られたクラス名で、クラスがプラグインの一部であることを +指定しています。 : + +``` text +// プラグインは "DebugKit", クラス名は "Toolbar". +'DebugKit.Toolbar' + +// プラグインは "AcmeCorp/Tools", クラス名は "Toolbar". +'AcmeCorp/Tools.Toolbar' +``` + +routes.php +`config` ディレクトリー中のファイルで、ルーティングの設定が入っています。 +このファイルは全てのリクエストが処理される前に読み込まれます。リクエストが正しい +コントローラー+アクションにルーティングされるように、アプリケーションが必要とする +全てのルートに接続する必要があります。 + +ルーティング配列 +`Router::url()` に渡される属性の配列。 +典型的には以下のようになります。 : + +``` php +['controller' => 'Posts', 'action' => 'view', 5] +``` + +
    diff --git a/docs/ja/appendices/migration-guides.md b/docs/ja/appendices/migration-guides.md new file mode 100644 index 0000000000..8fd2b21b3b --- /dev/null +++ b/docs/ja/appendices/migration-guides.md @@ -0,0 +1,9 @@ +# 移行ガイド + +移行ガイドは、各バージョンで導入された新機能に関する情報と、5.x のマイナーリリース間の移行手順を解説します。 + +- [5 0 Upgrade Guide](5-0-upgrade-guide) +- [5 0 Migration Guide](5-0-migration-guide) +- [5 1 Migration Guide](5-1-migration-guide) +- [5 2 Migration Guide](5-2-migration-guide) +- [Phpunit10](phpunit10) diff --git a/docs/ja/appendices/phpunit10.md b/docs/ja/appendices/phpunit10.md new file mode 100644 index 0000000000..df453e419a --- /dev/null +++ b/docs/ja/appendices/phpunit10.md @@ -0,0 +1,65 @@ +# PHPUnit 10 へのアップグレード + +CakePHP 5 では、PHPUnit の最低バージョンが `^8.5 || ^9.3` から `^10.1` に変更されました。 この文書は、PHPUnit や CakePHP 由来の、いくつかの破壊的変更を紹介するものです。 + +## phpunit.xml の調整 + +PHPUnitの設定ファイルを、以下のコマンドで更新することが推奨されます: + + vendor/bin/phpunit --migrate-configuration + +> [!NOTE] +> 上記のコマンドを実行する前に、 `vendor/bin/phpunit --version` を実行して PHPUnit 10 が実行されていることを確認して下さい。 + +このコマンド(訳注 : `vendor/bin/phpunit --migrate-configuration` のこと)を実行することによって、お手元のプロジェクトの `phpunit.xml` ファイルには推奨される変更が適用された状態になります。 + +### 新しいイベントシステム + +PHPUnit 10 は、古い hook の仕組みを削除した上で、新しい [イベントシステム](https://docs.phpunit.de/en/10.5/extending-phpunit.html#extending-the-test-runner) が導入されました。 +ここでは、以下に示すような `phpunit.xml` は…: + +``` php + + + +``` + +次のように調整されます: + +``` php + + + +``` + +## `->withConsecutive()` の削除 + +削除されたメソッド `->withConsecutive()` は、応急措置的に置き換え可能です。例えば以下のコードは: + +``` php +->withConsecutive(['firstCallArg'], ['secondCallArg']) +``` + +次のように置き換えられます: + +``` php +->with( + ...self::withConsecutive(['firstCallArg'], ['secondCallArg']) +) +``` + +`Cake\TestSuite\TestCase` クラスには、 `Cake\TestSuite\PHPUnitConsecutiveTrait` 経由で、静的メソッド `self::withConsecutive()` が追加されました。なので、Testcase のクラスに手動で trait を仕込む必要はありません。 + +## data provider は static に + +お手元のプロジェクトのテストケースにおいて、PHPUnitの data provider 機能を活用している場合、それを static にする必要があります。例えば以下のコードは: + +``` php +public function myProvider(): array +``` + +次のように置き換えて下さい: + +``` text +public static function myProvider(): array +``` diff --git a/docs/ja/bake.md b/docs/ja/bake.md new file mode 100644 index 0000000000..3f307d4fe4 --- /dev/null +++ b/docs/ja/bake.md @@ -0,0 +1,3 @@ +# Bake コンソール + +このページは [移動しました](https://book.cakephp.org/bake/2.x/ja/index.html) 。 diff --git a/docs/ja/bake/development.md b/docs/ja/bake/development.md new file mode 100644 index 0000000000..3b00f04397 --- /dev/null +++ b/docs/ja/bake/development.md @@ -0,0 +1,3 @@ +# Bake の拡張 + +このページは [移動しました](https://book.cakephp.org/bake/2.x/ja/development.html) 。 diff --git a/docs/ja/bake/usage.md b/docs/ja/bake/usage.md new file mode 100644 index 0000000000..bb8aa2de72 --- /dev/null +++ b/docs/ja/bake/usage.md @@ -0,0 +1,3 @@ +# Bake でコード生成 + +このページは [移動しました](https://book.cakephp.org/bake/2.x/ja/usage.html) 。 diff --git a/docs/ja/chronos.md b/docs/ja/chronos.md new file mode 100644 index 0000000000..5623e84eb8 --- /dev/null +++ b/docs/ja/chronos.md @@ -0,0 +1,3 @@ +# Chronos + +このページは [移動しました](https://book.cakephp.org/chronos/2.x/ja/) 。 diff --git a/docs/ja/console-commands.md b/docs/ja/console-commands.md new file mode 100644 index 0000000000..5ce35ebc2b --- /dev/null +++ b/docs/ja/console-commands.md @@ -0,0 +1,162 @@ +# コンソールコマンド + +CakePHP はウェブフレームワークとしてだけではなく、コンソールアプリケーションを開発するための +コンソールフレームワークとしての機能を合わせ持っています。コンソールアプリケーションは +メンテナンスといった様々なバックグラウンド タスクを行ったり、リクエスト-レスポンスのサイクルの +外側で何かを実行するための仕組みです。CakePHP のコンソールアプリケーションでは、コマンドライン +からあなたが作成したアプリケーションクラスを再利用できます。 + +CakePHP には元々たくさんのコンソールアプリケーションが備わっています。 +これらの中には(i18n のように)他の CakePHP の機能と組合せて使うものもあれば、 +仕事をより早く片付けるための、より一般的なものもあります。 + +## CakePHP のコンソール + +このセクションでは、コマンドラインにおける CakePHP をご紹介します。 +CakePHPのコンソールツールは、ディスパッチタイプのセットアップを使って シェルやタスクをロードし、 +それらへパラメーターを渡します。以下の例ではbashを使用していますが、 +CakePHPコンソールは多くの \*nix シェルおよびウィンドウと互換性があります。 + +CakePHPアプリケーションには、シェルとタスクを含む **src/Command** 、 +**src/Shell** 、および **src/Shell/Task** ディレクトリが含まれています。 +また、binディレクトリには実行ファイルが付属しています。 + +``` bash +$ cd /path/to/app +$ bin/cake +``` + +> [!NOTE] +> Windowsのコマンドプロンプト(cmd.exe)の場合、 `bin\cake` としないと動作しません(スラッシュではなくバックスラッシュ)。 + +引数なしでコンソールを実行すると、使用可能なコマンドが一覧表示されます。 +そのコマンド名を使用してコマンドを実行できます。 + +``` bash +# server シェルを実行 +bin/cake server + +# マイグレーションシェルを実行 +bin/cake migrations -h + +# bake コマンドを実行 (プラグインプレフィックスを先頭に付ける例) +bin/cake bake.bake -h +``` + +コマンドの名前がアプリケーションやフレームワークのコマンドと重複しない場合、 +プラグインのコマンドはプラグインプレフィックスなしで呼び出すことができます。 +2つのプラグインが同じ名前のコマンドを提供する場合、最初にロードされた +プラグインが短いエイリアス名を取得します。 +この `plugin.command` 形式を使用して、コマンドを呼び分けることができます。 + +## コンソールアプリケーション + +デフォルトでは、CakePHP はアプリケーションやプラグインの全てのコマンドを +自動的に検出します。独立したコンソールアプリケーションを構築する場合は、 +公開されるコマンドの数を減らすことをお勧めします。 +`Application` の `console()` フックを使って、公開されるコマンドの制限やコマンド名の整理が可能です。: + +``` php +// src/Application.php +namespace App; + +use App\Command\UserCommand; +use App\Command\VersionCommand; +use Cake\Console\CommandCollection; +use Cake\Http\BaseApplication; + +class Application extends BaseApplication +{ + public function console(CommandCollection $commands): CommandCollection + { + // Add by classname + $commands->add('user', UserCommand::class); + + // Add instance + $commands->add('version', new VersionCommand()); + + return $commands; + } +} +``` + +上記の例では、利用できるコマンドは `help` ・ `version` ・ `user` のみです。 +プラグインにコマンドを追加する方法については、 [Plugin Commands](plugins#plugin-commands) セクションを +ご覧ください。 + +> [!NOTE] +> 同じ Command クラスを使用する複数のコマンドを追加する場合、 `help` コマンドは +> 最短のオプションを表示します。 + +## コマンド名を変更 + +コマンドの名前を変更したり、ネストされたコマンドやサブコマンドを作成する場合があります。 +コマンドのデフォルトの自動検出ではこれは行われませんが、コマンドを登録して任意の名前をつけることができます。 + +プラグインの中で各コマンドを定義することにより、コマンド名をカスタマイズできます。: + +``` php +public function console(CommandCollection $commands): CommandCollection +{ + // ネストされた名前のコマンドを追加 + $commands->add('user dump', UserDumpCommand::class) + $commands->add('user:show', UserShowCommand::class) + + // コマンドの名前を全体的に変更 + $commands->add('lazer', UserDeleteCommand::class) + + return $commands; +} +``` + +アプリケーションの `console()` フックを上書きする場合、 +`$commands->autoDiscover()` を呼び出して、CakePHP、アプリケーション、 +およびプラグインからコマンドを追加することを忘れないでください。 + +添付されているコマンドの名前を変更したい・削除する必要がある場合は、 +アプリケーションイベントマネージャーで `Console.buildCommands` イベントを +使用して、使用可能なコマンドを変更できます。 + +## コマンド + +最初のコマンドを作成する方法については、 [コマンドオブジェクト](console-commands/commands) の章を +参照してください。次に、コマンドについて詳しく説明します。 + +- [コマンドオブジェクト](console-commands/commands) +- [コマンドの入力と出力](console-commands/input-output) +- [オプションパーサー](console-commands/option-parsers) +- [cron ジョブに登録してシェルを実行する](console-commands/cron-jobs) + +## CakePHPが提供するコマンド + +- [キャッシュツール](console-commands/cache) +- [Completion ツール](console-commands/completion) +- [I18N ツール](console-commands/i18n) +- [Plugin シェル](console-commands/plugin) +- [スキーマキャッシュツール](console-commands/schema-cache) +- [Routes ツール](console-commands/routes) +- [Server ツール](console-commands/server) +- [インタラクティブ・コンソール (REPL)](console-commands/repl) +- [シェル](console-commands/shells) + +## コンソール環境におけるルーティング + +コマンドラインインターフェイス(CLI)、特にシェルとタスクでは、 +`env('HTTP_HOST')` およびその他のWebブラウザー固有の +環境変数が設定されていません。 + +`Router::url()` を使ってレポートを生成したり、メールを送信したりする場合、 +デフォルトホスト `http://localhost/` が含まれるため、結果として無効なURLになってしまいます。 +こういったケースでは、ドメインを手動で設定する必要があります。たとえば、ブートストラップ +または Config の `App.fullBaseUrl` を使って設定できます。 + +メールを送信する場合は、メールを送信する際のホストを Email クラスで指定する必要があります: + +``` php +use Cake\Mailer\Email; + +$email = new Email(); +$email->setDomain('www.example.org'); +``` + +これにより生成されたメッセージIDはは有効で、メール送信元のドメインに適合していることが保証されます。 diff --git a/docs/ja/console-commands/cache.md b/docs/ja/console-commands/cache.md new file mode 100644 index 0000000000..0896c94916 --- /dev/null +++ b/docs/ja/console-commands/cache.md @@ -0,0 +1,12 @@ +# キャッシュツール + +CLI 環境でキャッシュデータをよりよく管理するために、コンソールコマンドで、 +あなたのアプリケーションが持つキャッシュデータの消去ができます。 : + +``` text +// 特定の設定のキャッシュをクリア +bin/cake cache clear + +// すべての設定のキャッシュをクリア +bin/cake cache clear_all +``` diff --git a/docs/ja/console-commands/commands.md b/docs/ja/console-commands/commands.md new file mode 100644 index 0000000000..082cc75de2 --- /dev/null +++ b/docs/ja/console-commands/commands.md @@ -0,0 +1,544 @@ +# コマンドオブジェクト + +`class` Cake\\Console\\**Command** + +CakePHP には、開発のスピードアップと日常的なタスクの自動化を目的とした多数の組み込みコマンドが +用意されています。これらの同じライブラリを使用して、アプリケーションとプラグイン用のコマンドを +作成することができます。 + +## コマンドの作成 + +最初のコマンドを作ってみましょう。この例では、単純な Hello world コマンドを作成します。 +アプリケーションの **src/Command** ディレクトリの中で、 **HelloCommand.php** を作成してください。 +その中に次のコードを書いてください。 : + +``` php +out('Hello world.'); + } +} +``` + +上記のように、Command クラスでは、さまざまな処理まとめて +行うための `execute()` メソッドを実装する必要があります。 +コマンドが呼び出されたときに、このメソッドが呼び出されます。 +それでは、このコマンド実行してみましょう。 + +``` bash +bin/cake hello +``` + +次の出力が表示されます。 : + + Hello world. + +無事に表示されましたか? +次に、パラメータを与えられるようにしてみます。 +`buildOptionParser()` を使用します。 : + +``` php +addArgument('name', [ + 'help' => 'What is your name' + ]); + + return $parser; + } + + public function execute(Arguments $args, ConsoleIo $io) + { + $name = $args->getArgument('name'); + $io->out("Hello {$name}."); + } +} +``` + +このファイルを保存した後、次のようにコマンドを実行できます。 + +``` bash +bin/cake hello jillian + +# 出力結果 +Hello jillian +``` + +## デフォルトのコマンド名の変更 + +コマンド名は原則的にCakePHPの規約に則ったものになります。 +このコマンド名を上書きする場合は、 +コマンドの `defaultName()` メソッドを用います。: + +``` text +public static function defaultName(): string +{ + return 'oh_hi'; +} +``` + +上記のように書くことで、 `HelloCommand` コマンドは +`cake hello` ではなく `cake oh_hi` として +実行できるようになります。 + +## 引数やオプションの定義 + +すでに說明したように、 `buildOptionParser()` フックメソッドを使って引数を定義することができます。 +また、オプションも定義できます。 たとえば、 `HelloCommand` に `yell` オプションを +追加することができます。 : + +``` php +// ... +protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser +{ + $parser + ->addArgument('name', [ + 'help' => 'What is your name' + ]) + ->addOption('yell', [ + 'help' => 'Shout the name', + 'boolean' => true + ]); + + return $parser; +} + +public function execute(Arguments $args, ConsoleIo $io) +{ + $name = $args->getArgument('name'); + if ($args->getOption('yell')) { + $name = mb_strtoupper($name); + } + $io->out("Hello {$name}."); +} +``` + +詳しくは、 [オプションパーサー](../console-commands/option-parsers) をご覧ください。 + +## ファイルに出力 + +コマンドは、実行されると `ConsoleIo` インスタンスが提供されます。 +このオブジェクトは `stdout` 、 `stderr` とのハンドシェイクによりファイルを生成することができます。 +詳しくは、 [コマンドの入力と出力](../console-commands/input-output) セクションをご覧ください。 + +## コマンド内でのモデルの使用 + +コンソールコマンドでアプリケーションのビジネスロジックにアクセスしたいケースが、しばしばあると思います。 +コントローラと同じように、コマンドでは `LocatorAwareTrait` を +通じてモデルをロードできます。 +そのために、 `$this->fetchTable()` を使用します。: + +``` php +addArgument('name', [ + 'help' => 'What is your name' + ]); + + return $parser; + } + + public function execute(Arguments $args, ConsoleIo $io): ?int + { + $name = $args->getArgument('name'); + $user = $this->fetchTable()->findByUsername($name)->first(); + + $io->out(print_r($user, true)); + + return null; + } +} +``` + +上記のコマンドは、ユーザー名でユーザを取得し、データベースに格納されている情報を表示します。 + +## 終了コードと実行停止 + +コマンドが回復不能なエラーに遭遇したら、 `abort()` メソッドを使って実行を終了することができます。 : + +``` php +// ... +public function execute(Arguments $args, ConsoleIo $io) +{ + $name = $args->getArgument('name'); + if (strlen($name) < 5) { + // 実行を停止し、標準エラーに出力し、終了コードを 1 に設定 + $io->error('Name must be at least 4 characters long.'); + $this->abort(); + } +} +``` + +`$io->abort()` の引数を使用して、任意のメッセージと終了コードを渡すこともできます: + +``` php +public function execute(Arguments $args, ConsoleIo $io) +{ + $name = $args->getArgument('name'); + if (strlen($name) < 5) { + // 実行を停止しstderrに出力し、終了コードを99に設定します + $io->abort('名前は4文字以上にする必要があります。', 99); + } +} +``` + +> [!TIP] +> 終了コードの 64 から 78 は避けてください。それらは `sysexits.h` で記述された +> 特定の意味を持っています。また、127 以上も避けてください。 +> それらは、 SIGKILL や SIGSEGV のようなシグナルによるプロセスの終了を示すために使用されるからです。 +> +> OS既存の終了コードの詳細については、Unixシステム の sysexit マニュアルページ +> (`man sysexits`)、または Windows の `System Error Codes` ヘルプページを +> 参照してください。 + +## 他のコマンドの呼び出し + +コマンド内から他のコマンドを呼び出す必要がある場合があります。 +そのために `executeCommand()` を用いることができます。: + +``` php +// コマンドのオプションと引数は配列で渡します。 +$this->executeCommand(OtherCommand::class, ['--verbose', 'deploy']); + +// コンストラクターに引数がある場合はインスタンスを生成して渡します。 +$command = new OtherCommand($otherArgs); +$this->executeCommand($command, ['--verbose', 'deploy']); +``` + +## コマンド説明文の設定 + +以下のようにコマンドの説明文を設定することができます。: + +``` php +class UserCommand extends Command +{ + public static function getDescription(): string + { + return 'カスタムの説明文'; + } +} +``` + +これにより、Cake CLIに説明文が表示されます。: + +``` bash +bin/cake + +App: + - user + └─── カスタムの説明文 +``` + +コマンドのヘルプセクションと同様です。: + +``` bash +cake user --help +カスタムの説明文 + +Usage: +cake user [-h] [-q] [-v] +``` + +## コマンドのテスト + +コンソールアプリケーションをより簡単にテストするため、CakePHP には、 +コンソールアプリケーションをテストし、結果をアサートするための +`ConsoleIntegrationTestTrait` トレイトが備わっています。 +このトレイトは、コマンドを実行するために使用する +`exec()` メソッドが定義されており、このメソッドに、 +CLI で使用するのと同じ文字列を渡すことができます。 + +**src/Command/UpdateTableCommand.php** に置かれた、とてもシンプルなシェルで始めましょう。 : + +``` php +namespace App\Command; + +use Cake\Command\Command; +use Cake\Console\Arguments; +use Cake\Console\ConsoleIo; +use Cake\Console\ConsoleOptionParser; + +class UpdateTableCommand extends Command +{ + protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser + { + $parser->setDescription('My cool console app'); + + return $parser; + } +} +``` + +このシェルの統合テストを書くために、 **tests/TestCase/Command/UpdateTableTest.php** +に `Cake\TestSuite\ConsoleIntegrationTestTrait` を使用したテストケースを作成します。 +このシェルの説明が `stdout` に出力されることをテストしましょう。 : + +``` php +namespace App\Test\TestCase\Command; + +use Cake\TestSuite\ConsoleIntegrationTestTrait; +use Cake\TestSuite\TestCase; + +class UpdateTableCommandTest extends TestCase +{ + use ConsoleIntegrationTestTrait; + + public function testDescriptionOutput() + { + $this->exec('update_table --help'); + $this->assertOutputContains('My cool console app'); + } +} +``` + +テストが成立しました。これは簡単な例ですが、コンソールアプリケーションの +統合テストケースを作成することは基本的に簡単です。 +このコマンドにさらに多くのロジックを追加してみましょう。 : + +``` php +namespace App\Command; + +use Cake\Command\Command; +use Cake\Console\Arguments; +use Cake\Console\ConsoleIo; +use Cake\Console\ConsoleOptionParser; +use Cake\I18n\FrozenTime; + +class UpdateTableCommand extends Command +{ + protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser + { + $parser + ->setDescription('My cool console app') + ->addArgument('table', [ + 'help' => 'Table to update', + 'required' => true + ]); + + return $parser; + } + + public function execute(Arguments $args, ConsoleIo $io) + { + $table = $args->getArgument('table'); + $this->fetchTable($table)->query() + ->update() + ->set([ + 'modified' => new FrozenTime() + ]) + ->execute(); + } +} +``` + +これはオプションと関連するロジックを必要とする、 +より完成度が高いシェルです。 +テストケースを次のコードスニペットに変更してみましょう。 : + +``` php +namespace Cake\Test\TestCase\Command; + +use Cake\Command\Command; +use Cake\I18n\FrozenTime; +use Cake\TestSuite\ConsoleIntegrationTestTrait; +use Cake\TestSuite\TestCase; + +class UpdateTableCommandTest extends TestCase +{ + use ConsoleIntegrationTestTrait; + + protected $fixtures = [ + // assumes you have a UsersFixture + 'app.Users' + ]; + + public function testDescriptionOutput() + { + $this->exec('update_table --help'); + $this->assertOutputContains('My cool console app'); + } + + public function testUpdateModified() + { + $now = new FrozenTime('2021-12-12 00:00:00'); + FrozenTime::setTestNow($now); + + $this->loadFixtures('Users'); + + $this->exec('update_table Users'); + $this->assertExitCode(Command::CODE_SUCCESS); + + $user = $this->getTableLocator()->get('Users')->get(1); + $this->assertSame($user->modified->timestamp, $now->timestamp); + + FrozenTime::setTestNow(null); + } +} +``` + +`testUpdateModified()` メソッドを見ると分かると思いますが、 +コマンドが最初の引数として渡すテーブルを更新することをテストします。 +まず最初に、コマンドが適切なステータスコード `0` で終了したことを +`assertExitCode()` によってアサートします。 + +次に、このコマンドが意図どおりに動作したことを確認します。 +つまり、 `modified` カラムが現在の時刻に更新されたことを +`assertSame()` で確認します。 + +ちなみに、 `exec()` はCLIに入力したのと同じ文字列を +使用するため、コマンド文字列としてオプションと引数を含める +ことができます。 + +### 対話的なシェルのテスト + +コンソールは対話的に用いることも多いインターフェイスです。 +`Cake\TestSuite\ConsoleIntegrationTestTrait` トレイトで +対話的なシェルをテストするには、期待する入力を `exec()` の2番目の +パラメーターとして渡すだけです。それらは、期待どおりの順序で配列として含める必要があります。 + +引き続き、対話的な確認フローを追加してみましょう。 +テスト元のコマンドクラスを次のように更新します。 : + +``` php +namespace App\Command; + +use Cake\Command\Command; +use Cake\Console\Arguments; +use Cake\Console\ConsoleIo; +use Cake\Console\ConsoleOptionParser; +use Cake\I18n\FrozenTime; + +class UpdateTableCommand extends Command +{ + protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser + { + $parser + ->setDescription('My cool console app') + ->addArgument('table', [ + 'help' => 'Table to update', + 'required' => true + ]); + + return $parser; + } + + public function execute(Arguments $args, ConsoleIo $io) + { + $table = $args->getArgument('table'); + if ($io->ask('Are you sure?', 'n', ['y', 'n']) === 'n') { + $io->error('You need to be sure.'); + $this->abort(); + } + $this->fetchTable($table)->query() + ->update() + ->set([ + 'modified' => new FrozenTime() + ]) + ->execute(); + } +} +``` + +対話的なサブコマンドができました。次に、適切な応答を受け取るか +どうかをテストするテストケースと、誤った応答を受け取るかどうかを +テストするケースを追加しましょう。 `testUpdateModified` +メソッドを削除し、 **tests/TestCase/Command/UpdateTableCommandTest.php** +に以下のメソッドを追加してください。 : + +``` php +public function testUpdateModifiedSure() +{ + $now = new FrozenTime('2017-01-01 00:00:00'); + FrozenTime::setTestNow($now); + + $this->loadFixtures('Users'); + + $this->exec('update_table Users', ['y']); + $this->assertExitCode(Command::CODE_SUCCESS); + + $user = $this->getTableLocator()->get('Users')->get(1); + $this->assertSame($user->modified->timestamp, $now->timestamp); + + FrozenTime::setTestNow(null); +} + +public function testUpdateModifiedUnsure() +{ + $user = $this->getTableLocator()->get('Users')->get(1); + $original = $user->modified->timestamp; + + $this->exec('my_console best_framework', ['n']); + $this->assertExitCode(Command::CODE_ERROR); + $this->assertErrorContains('You need to be sure.'); + + $user = $this->getTableLocator()->get('Users')->get(1); + $this->assertSame($original, $user->timestamp); +} +``` + +最初のテストケースでは、質問を確認し、レコードが更新されます。 +2番目のテストでは確認していませんし、レコードが更新されていないので、 +エラーメッセージが `stderr` に書き込まれていることを確認できます。 + +### アサーションメソッド + +`Cake\TestSuite\ConsoleIntegrationTestTrait` トレイトは、コンソールの出力に対して +容易にアサートできるようにするいくつかのアサーションメソッドを提供します。 : + +``` php +// シェルがsuccessステータスで終了したことをアサート +$this->assertExitSuccess(); + +// シェルがerrorステータスで終了したことをアサート +$this->assertExitError(); + +// シェルが期待したコードで終了したことをアサート +$this->assertExitCode($expected); + +// 標準出力が文字列を含むことをアサート +$this->assertOutputContains($expected); + +// 標準エラーが文字列を含むことをアサート +$this->assertErrorContains($expected); + +// 標準出力を正規表現にマッチするかをアサート +$this->assertOutputRegExp($expected); + +// 標準エラーが正規表現にマッチするかをアサート +$this->assertErrorRegExp($expected); +``` diff --git a/docs/ja/console-commands/completion.md b/docs/ja/console-commands/completion.md new file mode 100644 index 0000000000..998416d66d --- /dev/null +++ b/docs/ja/console-commands/completion.md @@ -0,0 +1,172 @@ +# Completion ツール + +コンソールで作業する開発者は多くの可能性を提供しますが、コマンドを完全に覚えて +記述することは退屈です。特に、異なるコマンドを緻密に繰り返しながら、 +新しいシェルを開発する時などです。Completion シェルは、bash ・ zsh ・ fish +などのシェルの補完スクリプトを書くための API を提供することによって、 +この問題を支援します。 + +## サブコマンド + +Completion シェルは、開発者が補完スクリプトの作成を支援するためのいくつかの +サブコマンドを含みます。それぞれ自動補完プロセスの中の異なるステップのための +ものです。 + +### commands + +最初のステップは、利用可能なシェルコマンドをプラグイン名が必要な場合は付加して +出力します。(出力結果は、このコマンド自身や他のサブコマンド全てがスペースで +区切られています。) 例えば: + + bin/cake Completion commands + +実行結果: + + acl api bake command_list completion console i18n schema server test testsuite upgrade + +あなたの補完スクリプトは、一連のリストから関連するコマンドを選ぶことができます。 +(このコマンドや以下のサブコマンドなど) + +### subCommands + +いったん気に入ったコマンドが選ばれると、次の段階として subCommands に入ります。 +そして、与えられたシェルコマンドのために利用可能なサブコマンドを出力します。 +例えば: + + bin/cake Completion subcommands bake + +実行結果: + + controller db_config fixture model plugin project test view + +### options + +三番目で最後の options は getOptionParser で設定されるような与えられた +(サブ) コマンドのためのオプションを出力します。 +(Shell から継承されたデフォルトのオプションを含みます。) +例えば: + + bin/cake Completion options bake + +実行結果: + + --help -h --verbose -v --quiet -q --everything --connection -c --force -f --plugin -p --prefix --theme -t + +また、シェルのサブコマンドである追加の引数を渡すことができます。それが、 +このサブコマンドの特定のオプションを出力します。 + +## CakePHP コンソール用の Bash 自動補完を有効にする方法 + +まず、\*\*bash-completion\*\* ライブラリーがインストールされていることを確認します。 +もし、ない場合は、次のコマンドでインストールします。 : + + apt-get install bash-completion + +**/etc/bash_completion.d/** に **cake** という名前のファイルを作成し、配置します。 +そのファイル中に [Bash Completion File Content](#bash-completion-file-content) を記述してください。 + +ファイルを保存して、コンソールを再起動してください。 + +> [!NOTE] +> MacOS X を使用している場合は、\*\*homebrew\*\* で `brew install bash-completion` を使って +> **bash-completion** ライブラリーをインストールすることができます。 +> **cake** ファイルの対象ディレクトリーは、 **/usr/local/etc/bash_completion.d/** になります。 + +### Bash 補完ファイルの内容 + +これは、CakePHP コンソールを使用しているときに自動補完を得るために正しい位置の +**cake** ファイルの内側に配置する必要があるコードです。 : + +``` php +# +# Bash completion file for CakePHP console +# + +_cake() +{ + local cur prev opts cake + COMPREPLY=() + cake="${COMP_WORDS[0]}" + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ "$cur" == -* ]] ; then + if [[ ${COMP_CWORD} = 1 ]] ; then + opts=$(${cake} Completion options) + elif [[ ${COMP_CWORD} = 2 ]] ; then + opts=$(${cake} Completion options "${COMP_WORDS[1]}") + else + opts=$(${cake} Completion options "${COMP_WORDS[1]}" "${COMP_WORDS[2]}") + fi + + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + + if [[ ${COMP_CWORD} = 1 ]] ; then + opts=$(${cake} Completion commands) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + + if [[ ${COMP_CWORD} = 2 ]] ; then + opts=$(${cake} Completion subcommands $prev) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + if [[ $COMPREPLY = "" ]] ; then + _filedir + return 0 + fi + return 0 + fi + + opts=$(${cake} Completion fuzzy "${COMP_WORDS[@]:1}") + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + if [[ $COMPREPLY = "" ]] ; then + _filedir + return 0 + fi + return 0; +} + +complete -F _cake cake bin/cake +``` + +## 自動補完の利用 + +有効にすると、\*\*TAB\*\* キーを使用することで、他のビルトインコマンドと同じ方法で +自動補完を使用することができます。自動補完の 3 つのタイプが用意されています。 +インストールしたばかりの CakePHP から次の出力があります。 + +### コマンド + +コマンドの自動補完のためのサンプル出力: + +``` bash +$ bin/cake +bake i18n orm_cache routes +console migrations plugin server +``` + +### サブコマンド + +サブコマンドの自動補完のためのサンプル出力: + +``` bash +$ bin/cake bake +behavior helper shell +cell mailer shell_helper +component migration template +controller migration_snapshot test +fixture model +form plugin +``` + +### オプション + +サブコマンドのオプションの自動補完のためのサンプル出力: + +``` bash +$ bin/cake bake - +-c --everything --force --help --plugin -q -t -v +--connection -f -h -p --prefix --quiet --theme --verbose +``` diff --git a/docs/ja/console-commands/cron-jobs.md b/docs/ja/console-commands/cron-jobs.md new file mode 100644 index 0000000000..99233182b0 --- /dev/null +++ b/docs/ja/console-commands/cron-jobs.md @@ -0,0 +1,36 @@ +# cron ジョブに登録してシェルを実行する + +通常シェルは、ニュースレターを送ったり、たまにデータベースをクリーンアップしたりすることを、 +cron ジョブとして実行します。 + +このように簡単な設定で行えます。 : + +``` text +*/5 * * * * cd /full/path/to/root && bin/cake myshell myparam +# * * * * * 実行するコマンド +# │ │ │ │ │ +# │ │ │ │ │ +# │ │ │ │ \───── 曜日 (0 - 6) (0 から 6 が日曜日から土曜日、 +# | | | | もしくは曜日名) +# │ │ │ \────────── 月 (1 - 12) +# │ │ \─────────────── 日 (1 - 31) +# │ \──────────────────── 時 (0 - 23) +# \───────────────────────── 分 (0 - 59) +``` + +詳しくはこちら: + +> [!TIP] +> cron ジョブで画面出力を非表示にするために `-q` (または --quiet) を使用してください。 + +## 共有ホスティング上の cron ジョブ + +いくつかの共有ホスティング上で `cd /full/path/to/root && bin/cake mycommand myparam` +は動作しません。代わりに `php /full/path/to/root/bin/cake.php mycommand myparam` +が使用できます。 + +> [!NOTE] +> php.ini の中で `register_argc_argv = 1` を含めることによって、 +> register_argc_argv を有効にしなければなりません。グローバルに register_argc_argv +> を変更できない場合、 `-d register_argc_argv=1` パラメーターをつけることで、 +> cron ジョブに独自の設定ファイル (php.ini) を指定することができます。例: `php -d register_argc_argv=1 /full/path/to/root/bin/cake.php myshell myparam` diff --git a/docs/ja/console-commands/i18n.md b/docs/ja/console-commands/i18n.md new file mode 100644 index 0000000000..883e850e31 --- /dev/null +++ b/docs/ja/console-commands/i18n.md @@ -0,0 +1,84 @@ +# I18N ツール + +CakePHP の国際化機能は、 [po files](https://ja.wikipedia.org/wiki/Gettext) +を翻訳のソースとして使います。POファイルは [Poedit](https://poedit.net/) +のようなツールで簡単に編集できます。 + +CakePHP の i18n コマンドは、手軽に potファイル(poテンプレートファイル)を生成します。 +翻訳者は、これらのテンプレートファイルを使ってアプリケーションの翻訳作業を行ないます。 +翻訳が更新された pot ファイルは既存の翻訳とマージされます。 + +## POT ファイルの生成 + +`extract` コマンドを使って、既存のアプリケーションのために +POT ファイルを生成することができます。 +このコマンドはアプリケーション全体から +`__()` 形式の関数をスキャンし、メッセージ文字列を +抽出します。アプリケーション中のユニークな文字列は +それぞれひとつの POT ファイルの中にマージされます。 : + +``` text +.. code-block:: console +``` + +> bin/cake i18n extract + +上記は、抽出シェルを実行します。 このコマンドの結果は、 +**resources/locales/default.pot** ファイルになります。 +pot ファイルは、 po ファイルを作成するためのテンプレート +として使用します。もし、手動で pot ファイルから po ファイルを +作成するなら、 `Plural-Forms` ヘッダー行を正しく設定してください。 + +### プラグイン用 POT ファイルの生成 + +特定のプラグインで使用される POT ファイルを生成することができます。 + +``` bash +bin/cake i18n extract --plugin +``` + +これにより、プラグインで使用される必要な POT ファイルが生成されます。 + +### 一括で複数のフォルダーを抽出 + +複数ディレクトリから文字列の抽出が必要なこともあるでしょう。 +例えば、アプリケーションの `config` ディレクトリー内の +いくつかの文字列を定義している場合、 `src` ディレクトリと +同様にこのディレクトリーからも文字列を抽出したくなる +はずです。 +それには `--paths` オプションを使用することができます。 +そのオプションに抽出する絶対パスをカンマ区切りリストで +渡します。 : + + bin/cake i18n extract --paths /var/www/app/config,/var/www/app/src + +### 特定フォルダーを除外 + +除外したいフォルダーをカンマ区切りで指定します。指定された値に含まれるパスは無視されます。 + +``` bash +bin/cake i18n extract --exclude vendor,tests +``` + +### 既存の POT ファイルの上書き警告をスキップする + +`--overwrite` を追加することで、 POT ファイルが存在しても警告されず、 +デフォルトで上書きされます。 + +``` bash +bin/cake i18n extract --overwrite +``` + +### CakePHP コアライブラリーからのメッセージ抽出 + +デフォルトで、抽出シェルスクリプトは CakePHP コアライブラリー中で使われているメッセージを +抽出するかどうか訊ねます。 `--extract-core` に yes か no を設定することで、 +デフォルトの動作を指定できます。 + +``` bash +bin/cake i18n extract --extract-core yes + +// または + +bin/cake i18n extract --extract-core no +``` diff --git a/docs/ja/console-commands/input-output.md b/docs/ja/console-commands/input-output.md new file mode 100644 index 0000000000..ec127500a6 --- /dev/null +++ b/docs/ja/console-commands/input-output.md @@ -0,0 +1,339 @@ +# コマンドの入力と出力 + +`class` Cake\\Console\\**ConsoleIo** + +CakePHP は `ConsoleIo` オブジェクトをコマンドに提供するので、 +ユーザー入力と出力情報を対話的にユーザーに読み取らせることができます。 + +## コマンドヘルパー + +コマンドヘルパーは、コマンド、シェルまたはタスクからアクセスして使用できます。 : + +``` php +// 表としてデータを出力 +$io->helper('Table')->output($data); + +// プラグインからヘルパーを取得 +$io->helper('Plugin.HelperName')->output($data); +``` + +また、ヘルパーのインスタンスを取得し、それらのパブリックメソッドを呼び出すこともできます。 : + +``` php +// Progress ヘルパーを取得して利用 +$progress = $io->helper('Progress'); +$progress->increment(10); +$progress->draw(); +``` + +## ヘルパーの作成 + +CakePHPにはいくつかのコマンドヘルパーが付属していますが、 +アプリケーションやプラグインでもっと多くのコマンドヘルパーを作成できます。 +例として、装飾的な見出しを生成するための簡単なヘルパーを作成してみましょう。まず +**src/Command/Helper/HeadingHelper.php** を作成し、以下を記述してください。 : + +``` php +_io->out($marker . ' ' . $args[0] . ' ' . $marker); + } +} +``` + +この新しいヘルパーは、シェルコマンドの中で呼び出すことで使用できます。 : + +``` php +// 両側に ### を付加 +$this->helper('Heading')->output(['It works!']); + +// 両側に ~~~~ を付加 +$this->helper('Heading')->output(['It works!', '~', 4]); +``` + +ヘルパーは一般的に、配列のパラメーターを受けとる `output()` メソッドを実装しています。 +しかし、コンソールヘルパーは任意の形式の引数を受け取る追加のメソッドを実装できる普通のクラスです。 + +> [!NOTE] +> 下位互換のため、ヘルパーは、 `src/Shell/Helper` にも配置できます。 + +## 組み込みヘルパー + +### Table ヘルパー + +TableHelper は、成形されたアスキーアートの表の作成を支援します。 +使い方はとてもシンプルです。 : + +``` php +$data = [ + ['Header 1', 'Header', 'Long Header'], + ['short', 'Longish thing', 'short'], + ['Longer thing', 'short', 'Longest Value'], +]; +$io->helper('Table')->output($data); + +// 出力結果 ++--------------+---------------+---------------+ +| Header 1 | Header | Long Header | ++--------------+---------------+---------------+ +| short | Longish thing | short | +| Longer thing | short | Longest Value | ++--------------+---------------+---------------+ +``` + +### Progress ヘルパー + +ProgressHelper は2つの異なる方法で利用されます。 +シンプルなモードは、処理が完了するまでに呼び出されるコールバックを渡すことができます。 : + +``` php +$io->helper('Progress')->output(['callback' => function ($progress) { + // ここで作業します + $progress->increment(20); + $progress->draw(); +}]); +``` + +追加のオプションを渡すことで、プログレスバーの制御ができます。 + +- `total` プログレスバーの全アイテム数。デフォルトは 100 です。 +- `width` プログレスバーの幅。デフォルトは 80 です。 +- `callback` プログレスバーを更新するループ中で呼ばれるコールバック。 + +全てのオプションを使用した例です。 : + +``` php +$io->helper('Progress')->output([ + 'total' => 10, + 'width' => 20, + 'callback' => function ($progress) { + $progress->increment(2); + $progress->draw(); + } +]); +``` + +Progress ヘルパーは、必要であればプログレスバーの増加や再描画を手動で行うことができます。 : + +``` php +$progress = $io->helper('Progress'); +$progress->init([ + 'total' => 10, + 'width' => 20, +]); + +$progress->increment(4); +$progress->draw(); +``` + +## ユーザー入力の取得 + +`method` Cake\\Console\\ConsoleIo::**ask**($question, $choices = null, $default = null) + +対話的なコンソールアプリケーションを構築する際には、ユーザー入力を取得する必要があります。 +CakePHP は、このための簡単な方法を提供します。 : + +``` php +// ユーザーから任意のテキストを取得 +$color = $io->ask('What color do you like?'); + +// ユーザーの選択を取得 +$selection = $io->askChoice('Red or Green?', ['R', 'G'], 'R'); +``` + +選択のバリデーションは大文字と小文字を区別しません。 + +## ファイルの作成 + +`method` Cake\\Console\\ConsoleIo::**createFile**($path, $contents) + +ファイルを作成することは、多くの場合、開発とデプロイの自動化に役立つ多くのコンソールコマンドの +重要な部分です。 `createFile()` メソッドは、対話的な確認でファイルを作成するための +シンプルなインターフェイスを提供します。 : + +``` php +// 上書きの確認を含むファイルを作成します +$io->createFile('bower.json', $stuff); + +// 尋ねることなく強制的に上書きします +$io->createFile('bower.json', $stuff, true); +``` + +## 出力の作成 + +`stdout` や `stderr` への書き込みは、CakePHP が簡単にできる別のルーチン操作です。 : + +``` php +// 標準出力に出力 +$io->out('Normal message'); + +// 標準エラーに出力 +$io->err('Error message'); +``` + +通常の出力メソッドに加え、CakePHP は適切な ANSI +カラーで出力をスタイルするラッパーメソッドを提供します。 : + +``` php +// 標準出力に緑色テキスト +$io->success('Success message'); + +// 標準出力に水色テキスト +$io->info('Informational text'); + +// 標準出力に青色テキスト +$io->comment('Additional context'); + +// 標準エラーに赤色テキスト +$io->error('Error text'); + +// 照準エラーに黄色テキスト +$io->warning('Warning text'); +``` + +また、出力レベルに関する2つの便利なメソッドを提供します。 : + +``` php +// 詳細出力が有効の時のみ (-v) +$io->verbose('Verbose message'); + +// すべてのレベルで表示 +$io->quiet('Quiet message'); +``` + +シェルはまた、画面のクリア、空白行の作成、または横棒線を描くためのメソッドを含みます。 : + +``` php +// 2行の改行を出力 +$io->out($io->nl(2)); + +// 横棒線を描画 +$io->hr(); +``` + +最後に、画面上の現在のテキスト行を更新することができます。 : + +``` php +$io->out('Counting down'); +$io->out('10', 0); +for ($i = 9; $i > 0; $i--) { + sleep(1); + $io->overwrite($i, 0, 2); +} +``` + +> [!NOTE] +> 新しい行が出力されたら、テキストを上書きすることができないことに注意してください。 + +## 出力のレベル + +コンソールアプリケーションには、詳細なレベルの出力が必要なことがよくあります。 +たとえば、cron ジョブとして実行する場合、ほとんどの出力は不要です。 +出力レベルを使用して、出力に適切なフラグを付けることができます。 +シェルの利用者は、コマンドを呼び出すときに正しいフラグを設定することで、 +関心のあるレベルを決定することができます。次の3つのレベルがあります。 + +- `QUIET` - 必須のメッセージであり、静かな(必要最小限の)出力モードでも表示。 +- `NORMAL` - 通常利用におけるデフォルトのレベル。 +- `VERBOSE` - 毎日利用には冗長すぎるメッセージを表示、しかしデバッグ時には有用。 + +以下のように出力を指定できます。 : + +``` php +// すべてのレベルで表示されます。 +$io->out('Quiet message', 1, ConsoleIo::QUIET); +$io->quiet('Quiet message'); + +// QUIET 出力時には表示されません。 +$io->out('normal message', 1, ConsoleIo::NORMAL); +$io->out('loud message', 1, ConsoleIo::VERBOSE); +$io->verbose('Verbose output'); + +// VERBOSE 出力時のみ表示されます。 +$io->out('extra message', 1, ConsoleIo::VERBOSE); +$io->verbose('Verbose output'); +``` + +シェルの実行時に `--quiet` や `--verbose` を使うことで出力を制御できます。 +これらのオプションはデフォルトで組み込まれていて、いつでも CakePHP コマンド内部の +出力レベルを制御できるように考慮されています。 + +また、 `--quiet` と `--verbose` オプションは、ログデータの標準出力/標準エラーへの +出力方法を制御します。通常の情報とそれ以上のレベルのログメッセージは標準出力/標準エラーに出力されます。 +`--verbose` を使用する場合は、デバッグログは標準出力に出力されます。 +`--quiet` を使用する場合は、警告とそれ以上のレベルのログメッセージのみ標準エラーに出力されます。 + +## 出力のスタイル + +ちょうど HTML のようなタグを埋め込むことで、出力のスタイルを変更することができます。 +ConsoleOutput はこれらのタグを正しい ansi コードシーケンスに変換したり、ansi コードを +サポートしないコンソールではタグを除去します。 +スタイルはいくつかビルトインされたものがありますが、自分で作成することも 可能です。 +ビルトインされたものは以下の通りです。 + +- `success` 成功メッセージ。緑色のテキスト。 +- `error` エラーメッセージ。赤色のテキスト。 +- `warning` 警告メッセージ。黄色のテキスト。 +- `info` 情報メッセージ。水色のテキスト。 +- `comment` 追加情報。青色のテキスト。 +- `question` 質問事項。シェルが自動的に追加する。 + +`$io->styles()` を使ってさらに多くのスタイルを追加できます。 +新しいスタイルを追加するには以下のようにします。 : + +``` php +$io->styles('flashy', ['text' => 'magenta', 'blink' => true]); +``` + +これで `` というタグが有効になり、ansi カラーが有効な端末であれば、 +`$this->out('うわ! 何か変になった');` の場合の表示は +色がマゼンタでブリンクになります。 +スタイルを定義する際は `text` と `background` 属性として以下の色が指定できます。 + +- black +- blue +- cyan +- green +- magenta +- red +- white +- yellow + +さらに以下のオプションをブール型のスイッチとして指定できます。 値が真の場合に有効になります。 + +- blink +- bold +- reverse +- underline + +スタイルを追加すると ConsoleOutput のすべてのインスタンスでも有効になります。 +ですので stdout と stderr 両方のオブジェクトでこれらを再定義する必要はありません。 + +## カラー表示の無効化 + +カラー表示はなかなか綺麗ですが、オフにしたい場合や強制的にオンにしたい場合もあるでしょう。 : + +``` php +$io->outputAs(ConsoleOutput::RAW); +``` + +これは RAW(生の)出力モードにします。 RAW 出力モードではすべてのスタイルが無効になります。 +モードには3種類あります。 + +- `ConsoleOutput::COLOR` - カラーエスケープコードを出力します。 +- `ConsoleOutput::PLAIN` - プレーンテキスト出力。既知のスタイルタグが出力から取り除かれます。 +- `ConsoleOutput::RAW` - RAW 出力、スタイルや書式設定は行われない。 + これは XML を出力する場合や、スタイルのデバッグを行う際に役立ちます。 + +デフォルトでは \*nix システムにおける ConsoleOutput のデフォルトはカラー出力モードです。 +Windows では `ANSICON` 環境変数がセットされている場合を除き、プレーンテキストモードが +デフォルトです。 diff --git a/docs/ja/console-commands/option-parsers.md b/docs/ja/console-commands/option-parsers.md new file mode 100644 index 0000000000..a0ee3f78ee --- /dev/null +++ b/docs/ja/console-commands/option-parsers.md @@ -0,0 +1,411 @@ +# オプションパーサー + +`class` Cake\\Console\\**ConsoleOptionParser** + +コンソールアプリケーションは通常、端末からコマンドに情報を得るための主要な手段として +オプションと引数を受け取ります。 + +## OptionParser の定義 + +コマンドとシェルは `buildOptionParser($parser)` フックメソッドを提供します。 +このメソッドを使用して、コマンドのオプションと引数を定義できます。 : + +``` php +protected function buildOptionParser($parser) +{ + // オプションと引数を定義 + + // 完成したパーサーを返します + return $parser; +} +``` + +シェルクラスは `getOptionParser()` フックメソッドを使ってオプションパーサーを定義します。 : + +``` php +public function getOptionParser() +{ + // フレームワークから空のパーサーを取得 + $parser = parent::getOptionParser(); + + // オプションと引数を定義 + + // 完成したパーサーを返します + return $parser; +} +``` + +## 引数の使用 + +`method` Cake\\Console\\ConsoleOptionParser::**addArgument**($name, $params = []) + +コマンドラインツールにおいて、位置引数 (指定順序が意味を持つ引数) はよく使われます。 +`ConsoleOptionParser` では位置引数を要求するだけでなく定義することもできます。 +指定する際は `$parser->addArgument();` で一度にひとつずつ設定するか、 +`$parser->addArguments();` で複数個を同時に指定するかを選べます。 : + +``` php +$parser->addArgument('model', ['help' => 'bake するモデル']); +``` + +引数を作成する際は、以下のオプションが指定できます。 + +- `help` この引数に対して表示するヘルプ。 +- `required` この引数が必須かどうか。 +- `index` 引数のインデックス。設定されない場合は引数リストの末尾に位置づけられます。 + 同じインデックスを2回指定すると、最初に指定したオプションは上書きされます。 +- `choices` この引数について有効な選択肢。指定しない場合はすべての値が有効となります。 + parse() が無効な値を検出すると、例外が発生します。 + +必須であると指定された引数が省略された場合、コマンドのパースにおいて例外が発生します。 +これにより、引数のチェックをシェルの中で行う必要がなくなります。 + +### 複数の引数の追加 + +`method` Cake\\Console\\ConsoleOptionParser::**addArguments**(array $args) + +複数の引数を1個の配列で持つ場合、 `$parser->addArguments()` により +一度に複数の引数を追加できます。 : + +``` php +$parser->addArguments([ + 'node' => ['help' => 'The node to create', 'required' => true], + 'parent' => ['help' => 'The parent node', 'required' => true] +]); +``` + +ConsoleOptionParser 上のすべてのビルダーメソッドと同様に、 +addArguments も強力なメソッドチェーンの一部として使えます。 + +### 引数の検証 + +位置引数を作成する場合、 `required` フラグを使用して、シェルが呼び出されたときに +引数が存在しなければならないことを示すことができます。さらに `choices` を使うことで、 +その引数が取りうる有効な値の選択肢を制限できます。 : + +``` php +$parser->addArgument('type', [ + 'help' => 'これとやり取りするノードの型。', + 'required' => true, + 'choices' => ['aro', 'aco'] +]); +``` + +この例では、必須でかつ入力時に値の正当性チェックが行われるような引数を作成します。 +引数が指定されないか、または無効な値が指定された場合は例外が発生してシェルが停止します。 + +## オプションの利用 + +`method` Cake\\Console\\ConsoleOptionParser::**addOption**($name, $options = []) + +オプションまたはフラグは、コマンドラインツールで使用され、コマンドの順序付けられていない +キーと値の引数を提供します。オプションは、長い名前と短い別名の両方を定義できます。 +値を受け取ったり (例えば `--connection=default`)、 +ブール値のオプション (`-verbose` など) を使うことができます。 +オプションは、 `addOption()` メソッドで定義されます。 : + +``` php +$parser->addOption('connection', [ + 'short' => 'c', + 'help' => 'connection', + 'default' => 'default', +]); +``` + +この例の場合、シェルを起動する際に `cake myshell --connection=other`, +`cake myshell --connection other`, `cake myshell -c other` +のいずれかで引数を指定できます。 + +またブール型のスイッチも作れますが、これらのスイッチは値を消費せず、 +またその存在はパースされた引数の中だけとなります。 : + +``` php +$parser->addOption('no-commit', ['boolean' => true]); +``` + +このオプション指定の場合、 `cake myshell --no-commit something` のようにコールされると +no-commit 引数が `true` になり、'something' は位置引数と見なされます。 + +オプションを作成する場合、オプションの振る舞いを定義するのに以下が指定できます。 + +- `short` - このオプションを表す1文字の別名。未定義の場合はなしになります。 +- `help` - このオプションのヘルプ文字列。オプションのヘルプを生成する際に参照されます。 +- `default` - このオプションのデフォルト値。未定義の場合、デフォルト値は `true` となります。 +- `boolean` - 値を持たない単なるブール型のスイッチ。デフォルト値は `false` です。 +- `choices` - このオプションで取りうる有効な選択肢。指定しない場合はすべての値が有効となります。 + parse() が無効な値を検出すると、例外が発生します。 + +### 複数オプションの追加 + +`method` Cake\\Console\\ConsoleOptionParser::**addOptions**(array $options) + +複数の引数を1個の配列で持つ場合、 `$parser->addOptions()` により +一度に複数のオプションを追加できます。 : + +``` php +$parser->addOptions([ + 'node' => ['short' => 'n', 'help' => 'The node to create'], + 'parent' => ['short' => 'p', 'help' => 'The parent node'] +]); +``` + +ConsoleOptionParser 上のビルダーメソッドと同様に、addOptions も強力なメソッドチェーンの +一部として使えます。 + +オプション値は、 `$this->params` 配列に格納されます。また、存在しないオプションにアクセスした時の +エラーを回避するために便利なメソッド `$this->param()` を使用することができます。 + +### オプションの検証 + +オプションでは位置引数と同様に、値の選択肢を指定できます。 +オプションに choices が指定されている場合、それらがそのオプションで取りうる有効な値です。 +これ以外の値が指定されると `InvalidArgumentException` が発生します。 : + +``` php +$parser->addOption('accept', [ + 'help' => 'What version to accept.', + 'choices' => ['working', 'theirs', 'mine'] +]); +``` + +### ブール型オプションの使用 + +フラグのオプションを作りたい場合、オプションをブール型として指定できます。 +デフォルト値を持つオプションのように、ブール型のオプションもパース済み引数の中に常に +自分自身を含んでいます。フラグが存在する場合それらは `true` にセットされ、 +存在しない場合は `false` になります。 : + +``` php +$parser->addOption('verbose', [ + 'help' => 'Enable verbose output.', + 'boolean' => true +]); +``` + +次のオプションは、解析されたパラメータに常に値を持ちます。 +その値が含まれていない場合、デフォルト値は `false` になり、定義されていれば `true` になります。 + +### 配列から ConsoleOptionParser の構築 + +`method` Cake\\Console\\ConsoleOptionParser::**buildFromArray**($spec) + +前述のように、サブコマンドのオプションパーサーを作成する際は、そのメソッドに対する +パーサーの仕様を配列として定義できます。 +これによりすべてが配列として扱えるので、サブコマンドパーサーの構築が容易になります。 : + +``` php +$parser->addSubcommand('check', [ + 'help' => __('Check the permissions between an ACO and ARO.'), + 'parser' => [ + 'description' => [ + __("Use this command to grant ACL permissions. Once executed, the "), + __("ARO specified (and its children, if any) will have ALLOW access "), + __("to the specified ACO action (and the ACO's children, if any).") + ], + 'arguments' => [ + 'aro' => ['help' => __('ARO to check.'), 'required' => true], + 'aco' => ['help' => __('ACO to check.'), 'required' => true], + 'action' => ['help' => __('Action to check')] + ] + ] +]); +``` + +パーサーの仕様の中では `arguments`, `options`, `description` そして `epilog` のための +キーを定義できます。配列形式ビルダーの内部には `subcommands` は定義できません。 +引数とオプションの値は、 `Cake\Console\ConsoleOptionParser::addArguments()` や +`Cake\Console\ConsoleOptionParser::addOptions()` が利用する書式に従ってください。 +buildFromArray を単独で使ってオプションパーサーを構築することも可能です。 : + +``` php +public function getOptionParser() +{ + return ConsoleOptionParser::buildFromArray([ + 'description' => [ + __("Use this command to grant ACL permissions. Once executed, the "), + __("ARO specified (and its children, if any) will have ALLOW access "), + __("to the specified ACO action (and the ACO's children, if any).") + ], + 'arguments' => [ + 'aro' => ['help' => __('ARO to check.'), 'required' => true], + 'aco' => ['help' => __('ACO to check.'), 'required' => true], + 'action' => ['help' => __('Action to check')] + ] + ]); +} +``` + +### オプションパーサーのマージ + +`method` Cake\\Console\\ConsoleOptionParser::**merge**($spec) + +group コマンドを構築する場合、おそらく、いくつかのパーサーを組み合わせたいでしょう。 : + +``` php +$parser->merge($anotherParser); +``` + +各パーサーの引数の順序が同じでなければならないこと、およびオプションは、動作するために互換性が +なければならないことに注意してください。ですので、別のキーを使用しないでください。 + +## シェルからヘルプを取得 + +オプションパーサーでオプションと引数を定義することで、CakePHP は基本的なヘルプ情報を自動的に生成し、 +それぞれのコマンドに `--help` と `-h` を追加することができます。 +これらのオプションのいずれかを使用すると、生成されたヘルプの内容を見ることができます。 + +``` bash +bin/cake bake --help +bin/cake bake -h +``` + +このいずれでも bake のヘルプを生成します。ネストされたコマンドのヘルプを表示することもできます。 + +``` bash +bin/cake bake model --help +bin/cake bake model -h +``` + +これは bake の model コマンドに関するヘルプを表示します。 + +### ヘルプを XML で取得 + +自動ツールや開発ツールをビルドするのに CakePHP のシェルとの対話処理を必要とする場合、 +ヘルプを機械がパースできる形式で取得できると便利です。 +ConsoleOptionParser に以下の引数を追加することで、ヘルプを xml で出力できます。 + +``` bash +cake bake --help xml +cake bake -h xml +``` + +この例は生成されたヘルプ、オプション、引数そして選択されたシェルのサブコマンドに関するドキュメントを +XML で返します。XML ドキュメントの例としては以下のようになります。 + +``` xml + + + bake fixture + Generate fixtures for use with the test suite. You can use + `bake fixture all` to bake all fixtures. + + Omitting all arguments and options will enter into an interactive + mode. + + + + + + + + + + + + + + + + +``` + +## ヘルプの出力をカスタマイズ + +説明文とエピローグを追加することで、生成されたヘルプの内容をさらに充実させることができます。 + +### 説明文の設定 + +`method` Cake\\Console\\ConsoleOptionParser::**setDescription**($text) + +オプションパーサーの説明文を取得または設定します。説明文は引数やオプションの上に表示されます。 +配列または文字列を渡すことで説明文の値を設定できます。引数がない場合は現在の値を返します。 : + +``` php +// 一度に複数行を設定 +$parser->setDescription(['1行目', '2行目']); +// 3.4 より前 +$parser->description(['1行目', '2行目']); + +// 現在の値を取得する +$parser->getDescription(); +``` + +### エピローグの設定 + +`method` Cake\\Console\\ConsoleOptionParser::**setEpilog**($text) + +オプションパーサーのエピローグを取得または設定します。 +エピローグは、引数とオプションの情報の後に表示されます。 +配列または文字列を渡すことで、エピローグの値を設定することができます。 +引数がない場合は現在の値を返します。 : + +``` php +// 一度に複数行を設定 +$parser->setEpilog(['1行目', '2行目']); +// 3.4 より前 +$parser->epilog(['1行目', '2行目']); + +// 現在の値を取得する +$parser->getEpilog(); +``` + +### サブコマンドの追加 + +`method` Cake\\Console\\ConsoleOptionParser::**addSubcommand**($name, $options = []) + +コンソールアプリケーションはサブコマンドから構成されることも多いのですが、サブコマンド側で +特別なオプション解析や独自ヘルプを持ちたいこともあります。この完全な例が `bake` です。 +Bake は多くの別々のタスクから構成されますが、各タスクはそれぞれ独自のヘルプとオプションを持っています。 +`ConsoleOptionParser` を使ってサブコマンドを定義し、それらに固有のオプションパーサーを提供できるので、 +シェルはそれぞれのタスクについてコマンドをどう解析すればよいのかを知ることができます。 : + +``` php +$parser->addSubcommand('model', [ + 'help' => 'Bake a model', + 'parser' => $this->Model->getOptionParser() +]); +``` + +上の例では、シェルのタスクに対してヘルプやそれに特化したオプションパーサーの提供方法を示しています。 +タスクの `getOptionParser()` を呼ぶことで、オプションパーサーの複製をしたり、シェル内の関係を +調整する必要がなくなります。この方法でサブコマンドを追加することには2つの利点があります。 +まず生成されたヘルプの中で簡単にサブコマンドを文書化できること、そしてサブコマンドのヘルプに簡単に +アクセスできることです。前述のやり方で生成したサブコマンドを使って `cake myshell --help` とやると、 +サブコマンドの一覧が出ます。また `cake myshell model --help` とやると、model タスクだけの +ヘルプが表示されます。 + +> [!NOTE] +> シェルはサブコマンドを定義すると、すべてのサブコマンドは、明示的に定義する必要があります。 + +サブコマンドを定義する際は、以下のオプションが使えます。 + +- `help` - サブコマンドのヘルプテキスト。 +- `parser` - サブコマンドの ConsoleOptionParser。 + これによりメソッド固有のオプションパーサーを生成します。 + サブコマンドに関するヘルプが生成される際、もしパーサーが存在すればそれが使われます。 + `Cake\Console\ConsoleOptionParser::buildFromArray()` と + 互換性のある配列としてパーサーを指定することができます。 + +サブコマンドの追加は、強力なメソッドチェーンの一部として使えます。 diff --git a/docs/ja/console-commands/plugin.md b/docs/ja/console-commands/plugin.md new file mode 100644 index 0000000000..24add6347d --- /dev/null +++ b/docs/ja/console-commands/plugin.md @@ -0,0 +1,57 @@ +# Plugin シェル + +Plugin シェルを使用すると、コマンドプロンプトを経由してプラグインをロードおよび +アンロードすることができます。ヘルプを表示するには、以下を実行します。 : + + bin/cake plugin --help + +## プラグインのロード + +`Load` タスクで、あなたの **config/bootstrap.php** の中にプラグインをロード +することができます。以下を実行することによって行います。 : + + bin/cake plugin load MyPlugin + +これはあなたの **src/Application.php** に以下を追加します。 : + +``` php +// bootstrap メソッドの中に追加 +$this->addPlugin('MyPlugin'); + +// 3.6 より前は、config/bootstrap.php に以下を追加 +Plugin::load('MyPlugin'); +``` + +bake のような CLI ツールのみ提供するプラグインをロードする場合、以下のように +`bootstrap_cli.php` を更新することができます。 : + + bin/cake plugin load --cli MyPlugin + bin/cake plugin unload --cli MyPlugin + +## プラグインのアンロード + +プラグインの名前を指定することで、アンロードすることができます。 : + + bin/cake plugin unload MyPlugin + +これは **src/Application.php** から `$this->addPlugin('MyPlugin',...)` +の行を削除します。 + +## プラグインのアセット + +CakePHP は、デフォルトで `AssetMiddleware` ミドルウェアを使用して、 +プラグインのアセットを提供しています。これはとても便利ですが、 +PHP を呼び出すことなく、直接ウェブサーバーがサービスを提供することができるように、 +アプリの webroot 下のプラグインのアセットをシンボリックリンク/コピーすることを +お勧めします。以下を実行することによって行います。 : + + bin/cake plugin assets symlink + +上記のコマンドを実行すると、アプリの webroot 下にすべてのプラグインのアセットを +シンボリックリンクします。シンボリックリンクをサポートしていない Windows では、 +アセットをシンボリックリンクする代わりにそれぞれのフォルダーにコピーされます。 + +プラグインの名前を指定することにより、特定のプラグインのアセットを +シンボリックリンクすることができます。 : + + bin/cake plugin assets symlink MyPlugin diff --git a/docs/ja/console-commands/repl.md b/docs/ja/console-commands/repl.md new file mode 100644 index 0000000000..944f53020d --- /dev/null +++ b/docs/ja/console-commands/repl.md @@ -0,0 +1,50 @@ +# インタラクティブ・コンソール (REPL) + +[REPL(Read Eval Print Loop) プラグイン](https://github.com/cakephp/repl) を使う +ことで CakePHP やアプリケーションがインタラクティブ・コンソール内で探索しやすくなります。 + +> [!NOTE] +> このプラグインは 4.3 以前では CakePHP の app スケルトンに同梱されていました。 + +以下のようにするとインタラクティブ・コンソールを使い始めることができます。 + +``` bash +$ bin/cake console +``` + +これは、アプリケーションを自動実行し、インタラクティブコンソールを開始します。 +この時点で、アプリケーションコードを対話的に実行したり、 +アプリケーションのモデルを利用してクエリーを実行することができます。 + +``` bash +bin/cake console + +>>> $articles = Cake\Datasource\FactoryLocator::get('Table')->get('Articles'); +// object(Cake\ORM\Table)( +// +// ) +>>> $articles->find()->all(); +``` + +アプリケーションが自動実行されたら、REPL を利用してルーティングを試すこともできます。 : + +``` php +>>> Cake\Routing\Router::parse('/articles/view/1'); +// [ +// 'controller' => 'Articles', +// 'action' => 'view', +// 'pass' => [ +// 0 => '1' +// ], +// 'plugin' => NULL +// ] +``` + +URL 生成を試すこともできます。 : + +``` php +>>> Cake\Routing\Router::url(['controller' => 'Articles', 'action' => 'edit', 99]); +// '/articles/edit/99' +``` + +REPL を終了するには、 `CTRL-C` を使用するか、あるいは `exit` と入力してください。 diff --git a/docs/ja/console-commands/routes.md b/docs/ja/console-commands/routes.md new file mode 100644 index 0000000000..dffb89136e --- /dev/null +++ b/docs/ja/console-commands/routes.md @@ -0,0 +1,27 @@ +# Routes ツール + +RoutesShell は、ルートのテストおよびデバッグのためにシンプルに利用する CLI インターフェイスを +提供します。どのようにルートが解析されるか、どのような URL のルーティングパラメーターが +生成されるかをテストするために使用することができます。 + +## すべてのルートの一覧を取得 + + bin/cake routes + +## URL 解析のテスト + +URL が `check` メソッドを使用して解析される方法をすぐに見ることができます。 : + + bin/cake routes check /bookmarks/edit/1 + +ルートが任意のクエリー文字列パラメーターが含まれている場合は、引用符で URL を囲むことを +忘れないでください。 : + + bin/cake routes check "/bookmarks/?page=1&sort=title&direction=desc" + +## URL 生成のテスト + +`generate` メソッドを使用して、 `ルーティング配列` が、どのような URL を +生成するかを確認できます。 : + + bin/cake routes generate controller:Bookmarks action:edit 1 diff --git a/docs/ja/console-commands/schema-cache.md b/docs/ja/console-commands/schema-cache.md new file mode 100644 index 0000000000..cbff9faf6e --- /dev/null +++ b/docs/ja/console-commands/schema-cache.md @@ -0,0 +1,26 @@ +# スキーマキャッシュツール + +SchemaCacheShell は単純なアプリケーションのメタデータキャッシュを管理する CLI ツールを提供します。 +開発環境ではこれは正しくメタデータキャッシュを既存のデータを消去することなく再構築する助けになります。 +以下のコマンドで可能です。 : + + bin/cake schema_cache build --connection default + +これで `default` 設定で接続されている全てのテーブルのメタデータを再構築します。 +一つのテーブルだけ再構築したければ名前を指定して出来ます。 : + + bin/cake schema_cache build --connection default articles + +加えて、キャッシュデータを作るために、SchemaCacheShell をキャッシュされた +メタデータを削除するために使えます。: + +``` text +# 全メタデータの消去 +bin/cake schema_cache clear + +# 一つのテーブルだけ消去 +bin/cake schema_cache clear articles +``` + +> [!NOTE] +> 3.6 より前は、 `schema_cache` の代わりに `orm_cache` を使ってください。 diff --git a/docs/ja/console-commands/server.md b/docs/ja/console-commands/server.md new file mode 100644 index 0000000000..d9cc4639f7 --- /dev/null +++ b/docs/ja/console-commands/server.md @@ -0,0 +1,25 @@ +# Server ツール + +`ServerSコマンドhell` は、ビルトイン PHP ウェブサーバーを使用して簡単なウェブサーバーを +立ち上げることができます。このサーバーは製品利用するためのものではありません。 +すぐにアイデアを試してみたくて Apache や nginx を設定する時間を費やしたくない開発中に、 +手軽に行うことができます。以下のように、サーバーのシェルを起動することができます。 : + +``` bash +$ bin/cake server +``` + +サーバーの起動とポート 8765 へのアタッチを確認してください。 +ウェブブラウザーから `http://localhost:8765` へ、 CLI サーバーにアクセスしてください。 +あなたの端末内で `CTRL-C` を押してサーバーを閉じることができます。 + +> [!NOTE] +> 他のホストからサーバーに繋がらない場合、 `bin/cake server -H 0.0.0.0` を試してください。 + +## ポートおよびドキュメントルートの変更 + +オプションを使用してポートおよびドキュメントルートをカスタマイズすることができます。 : + +``` bash +$ bin/cake server --port 8080 --document_root path/to/app +``` diff --git a/docs/ja/console-commands/shells.md b/docs/ja/console-commands/shells.md new file mode 100644 index 0000000000..2f4e6a3b33 --- /dev/null +++ b/docs/ja/console-commands/shells.md @@ -0,0 +1,408 @@ +# シェル + +`class` Cake\\Console\\**Shell** + +::: info Deprecated in version 3.6.0 +Shell は 3.6.0 で非推奨ですが、 5.x までは削除されません。 代わりに [コマンドオブジェクト](../console-commands/commands) を使用してください。 +::: + +## シェルの作成 + +早速コンソールで動くシェルを作ってみましょう。この例ではシンプルな Hello world シェルを作ります。 +アプリケーションの **src/Shell** ディレクトリーに **HelloShell.php** を作成してください。 +その中に以下のコードを書きます。 : + +``` php +namespace App\Shell; + +use Cake\Console\Shell; + +class HelloShell extends Shell +{ + public function main() + { + $this->out('Hello world.'); + } +} +``` + +シェルクラスの規約として、クラス名は Shell サフィックス(接尾辞)を 付け、 +ファイル名と一致する必要があります。上記のシェルでは、 `main()` メソッドを作成しました。 +シェルが 追加コマンド(引数)なしで起動された場合、このメソッドが呼ばれます。 +この後、 多少コマンドを追加していきますが、現時点では単にシェルを起動してみましょう。 +アプリケーションディレクトリーから、以下を実行してください。 : + + bin/cake hello + +次の出力が表示されるはずです。 : + + Hello world. + +すでに述べたように、シェルで `main()` メソッドは、シェルに与えられた他のコマンドや +引数がない場合、いつでも呼ばれる特殊なメソッドです。main メソッドの使い方がある程度わかったら、 +次は以下のように別のコマンドを追加してみましょう。 : + +``` php +namespace App\Shell; + +use Cake\Console\Shell; + +class HelloShell extends Shell +{ + public function main() + { + $this->out('Hello world.'); + } + + public function heyThere($name = 'Anonymous') + { + $this->out('Hey there ' . $name); + } +} +``` + +このファイルを保存した後、次のコマンドを実行すると、あなたの名前が表示されるはずです。 : + + bin/cake hello hey_there your-name + +public メソッドのうち頭に \_ が付かないものは、コマンドラインから呼び出せます。 +ご覧のように、コマンドラインから呼び出されるメソッドは、アンダースコアー形式のシェル引数から +クラス内の正しいキャメルケース形式のメソッド名に変換されています。 + +`heyThere()` メソッドでは、位置引数が `heyThere()` 関数に提供されていることがわかります。 +位置引数は `args` プロパティーでも利用できます。 `$this->params` で利用できる +シェルアプリケーション上のスイッチやオプションにアクセスできますが、それを少し使いやすくしています。 + +`main()` メソッドを使用するときは、位置引数を使用することはできません。 +最初の位置引数やオプションが、コマンド名として解釈されるためです。 +引数を使用したい場合、 `main` 以外のメソッド名を使用する必要があります。 + +## シェルのタスク + +より高度なコンソールアプリケーションを開発する場合には、 +多くのシェル間で共有される再利用可能なクラスとして機能を構成したいでしょう。 +タスクによりコマンドをクラスに展開できます。例えば `bake` は、そのほとんどがタスクにより +作られています。 `$tasks` プロパティーを使ってシェルのタスクを定義できます。 : + +``` php +class UserShell extends Shell +{ + public $tasks = ['Template']; +} +``` + +プラグインからタスクを使うには、標準の `プラグイン記法` を使用します。 +タスクは `Shell/Task/` に、クラスにちなんで名付けられたファイルに格納されます。 +たとえば新たに 'FileGenerator' タスクを作成したい場合は、 +**src/Shell/Task/FileGeneratorTask.php** を作成することになります。 + +各タスクは、少なくとも `main()` メソッドを実装する必要があります。 +タスクが呼び出されたとき ShellDispatcher は、このメソッドを呼び出します。 +タスククラスは次のようになります。 : + +``` php +namespace App\Shell\Task; + +use Cake\Console\Shell; + +class FileGeneratorTask extends Shell +{ + public function main() + { + + } +} +``` + +シェルはプロパティーとしてもタスクにアクセスできますので、 [コンポーネント](../controllers/components) +と同様に再利用可能な部品としてタスクを利用できます。 : + +``` php +// Found in src/Shell/SeaShell.php +class SeaShell extends Shell +{ + // Found in src/Shell/Task/SoundTask.php + public $tasks = ['Sound']; + + public function main() + { + $this->Sound->main(); + } +} +``` + +また、コマンドラインからタスクに直接アクセスすることができます。 : + +``` bash +$ cake sea sound +``` + +> [!NOTE] +> コマンドラインからタスクを直接アクセスするには、タスクは **必ず** シェルクラスの +> \$tasks プロパティーに含まれている必要があります。 + +また、タスク名は、シェルの OptionParser にサブコマンドとして追加する必要があります。 : + +``` php +public function getOptionParser() +{ + $parser = parent::getOptionParser(); + $parser->addSubcommand('sound', [ + // コマンド一覧のヘルプテキストを提供 + 'help' => 'Execute The Sound Task.', + // オプションパーサーを互いにリンク + 'parser' => $this->Sound->getOptionParser(), + ]); + + return $parser; +} +``` + +### TaskRegistry による動的なタスクのロード + +タスクのレジストリーオブジェクトを使用して、その場でタスクをロードすることができます。 +以下のようにすると \$tasks で宣言されなかったタスクをロードすることができます。 : + +``` php +$project = $this->Tasks->load('Project'); +``` + +ProjectTask インスタンスをロードして返します。 +プラグインからタスクをロードすることもできます。 : + +``` php +$progressBar = $this->Tasks->load('ProgressBar.ProgressBar'); +``` + +## シェルの中でのモデルの使用 + +アプリケーションのビジネスロジックに、シェルユーティリティーの中からアクセスする必要があることも +よくあるでしょう。 CakePHP はそれが超簡単にできます。コントローラーの中で `loadModel()` を +使用するのと同じように、シェルの中でモデルを読み込むことができます。 +ロードされたモデルは、あなたのシェルに付属するプロパティーとして設定されます。 : + +``` php +namespace App\Shell; + +use Cake\Console\Shell; + +class UserShell extends Shell +{ + + public function initialize() + { + parent::initialize(); + $this->loadModel('Users'); + } + + public function show() + { + if (empty($this->args[0])) { + // CakePHP 3.2 より前なら error() を利用 + return $this->abort('Please enter a username.'); + } + $user = $this->Users->findByUsername($this->args[0])->first(); + $this->out(print_r($user, true)); + } +} +``` + +上記のシェルは、username によってユーザーを取得し、データベースに格納された情報が表示されます。 + +## シェルヘルパー + +複雑な出力生成ロジックの場合、再利用可能な方法で、このロジックをカプセル化するために +[Command Helpers](../console-commands/input-output#command-helpers) を利用することができます。 + +## シェルから他のシェルの呼び出し + +`method` Cake\\Console\\Shell::**dispatchShell**($args) + +あるシェルから他のシェルを呼び出したいケースは多々あると思います。 +他のシェルを呼び出すには `Shell::dispatchShell()` を使います。 +サブシェル側では引数を受け取るための `argv` が使えます。 +引数やオプションは可変引数もしくは文字列として指定できます。 : + +``` php +// 文字列として +$this->dispatchShell('schema create Blog --plugin Blog'); + +// 配列として +$this->dispatchShell('schema', 'create', 'Blog', '--plugin', 'Blog'); +``` + +上記は、プラグインのシェルの中からプラグイン用のスキーマを作るために schema シェルを呼んでいます。 + +### ディスパッチされたシェルへのパラメーター追加 + +ディスパッチされたシェルへの(シェルの引数にない)追加パラメーターを渡すことが有用な時がしばしばあります。 +これを行うために、 `dispatchShell()` に配列を渡すことができます。 +配列は、 `command` キーと共に `extra` キーを持つことが期待されています。 : + +``` php +// コマンド文字列を使用 +$this->dispatchShell([ + 'command' => 'schema create Blog --plugin Blog', + 'extra' => [ + 'foo' => 'bar' + ] +]); + +// コマンド配列を使用 +$this->dispatchShell([ + 'command' => ['schema', 'create', 'Blog', '--plugin', 'Blog'], + 'extra' => [ + 'foo' => 'bar' + ] +]); +``` + +`extra` で渡されたパラメーターは、 `Shell::$params` プロパティーにマージされ、 +`Shell::param()` メソッドでアクセス可能になります。 +シェルで `dispatchShell()` を使用してディスパッチされた時、デフォルトで `requested` +追加パラメーターが自動的に追加されます。この `requested` パラメーターは、 +ディスパッチされたシェルに表示されている CakePHP のコンソールウェルカムメッセージを防ぎます。 + +## CLI オプションのパース + +シェルはオプション、引数を定義し、ヘルプの生成を自動化するために +[オプションパーサー](../console-commands/option-parsers) を使います。 + +## 入出力との対話 + +シェルでは、 `getIo()` メソッドを使って `ConsoleIo` インスタンスにアクセスすることができます。 +詳細は、 [コマンドの入力と出力](../console-commands/input-output) をご覧ください。 + +`ConsoleIo` オブジェクトに加えて、シェルクラスは一連のショートカットメソッドを提供します。 +これらのメソッドは、 `ConsoleIo` にあるメソッドのショートカットやエイリアスです。 : + +``` php +// ユーザーから任意のテキストを取得 +$color = $this->in('What color do you like?'); + +// ユーザーの選択を取得 +$selection = $this->in('Red or Green?', ['R', 'G'], 'R'); + +// ファイルの作成 +$this->createFile('bower.json', $stuff); + +// 標準出力に出力 +$this->out('Normal message'); + +// 標準エラーに出力 +$this->err('Error message'); + +// 標準エラーに出力し、停止例外を発生 +$this->abort('Fatal error'); + +// CakePHP 3.2 より前。標準エラーに出力し exit() +$this->error('Fatal error'); +``` + +また、出力レベルに関する2つの便利なメソッドを提供します。 : + +``` php +// 詳細出力が有効の時のみ (-v) +$this->verbose('Verbose message'); + +// すべてのレベルで表示 +$this->quiet('Quiet message'); +``` + +シェルはまた、画面のクリア、空白行の作成、または横棒線を描くためのメソッドを含みます。 : + +``` php +// 2行の改行を出力 +$this->out($this->nl(2)); + +// ユーザーの画面をクリア +$this->clear(); + +// 横棒線を描画 +$this->hr(); +``` + +## シェルの実行を停止 + +あなたのシェルコマンドを停止したい条件に達した時、プロセスを停止するための `StopException` +を発生させるために `abort()` を使用することができます。 : + +``` php +$user = $this->Users->get($this->args[0]); +if (!$user) { + // エラーメッセージとエラーコードとともに停止 + $this->abort('ユーザーが見つかりません', 128); +} +``` + +## ステータスとエラーコード + +コマンドラインツールは、成功を示すために 0 を返し、エラー状態を示すために 0 以外を +返すべきです。 PHP メソッドは、通常 `true` か `false` を返すため、 +Cake Shell の `dispatch` 関数は、 `null` と `true` の戻り値を 0 へ、 +それ以外の値は 1 へと変換することによって、これらのセマンティクスとの橋渡しに役立ちます。 + +Cake Shell の `dispatch` 関数は、 `StopException` をキャッチし、 +その例外コードの値をシェルの終了コードとして使用します。上記のように、 +`abort()` を使ってメッセージを出力して指定したコードで終了したり、 +例に示すように、直接 `StopException` を起こすことができます。 : + +``` php +namespace App\Shell\Task; + +use Cake\Console\Shell; + +class ErroneousShell extends Shell +{ + public function main() + { + return true; + } + + public function itFails() + { + return false; + } + + public function itFailsSpecifically() + { + throw new StopException("", 2); + } +} +``` + +上記の例では、コマンドライン上で実行された際、次の終了コードを返します。 : + +``` bash +$ bin/cake erroneousshell ; echo $? +0 +$ bin/cake erroneousshell itFails ; echo $? +1 +$ bin/cake erroneousshell itFailsSpecifically ; echo $? +2 +``` + +> [!TIP] +> 終了コードの 64 から 78 は避けてください。それらは `sysexits.h` で記述された +> 特定の意味を持っています。 +> 終了コードの 127 以上を避けてください。それらは、 SIGKILL や SIGSEGV のような +> シグナルによるプロセスの終了を示すために使用されます。 + +> [!NOTE] +> 従来の終了コードについての詳細は、ほとんどの Unix システムの sysexit マニュアルページ +> (`man sysexits`) 、または Windows の `System Error Codes` ヘルプページを +> 参照してください。 + +## フックメソッド + +`method` Cake\\Console\\Shell::**initialize**() + +`method` Cake\\Console\\Shell::**startup**() + +> [!TIP] +> ウェルカム情報を削除する場合やそれまでのコマンドの流れを変更する場合は、 +> `startup()` メソッドをオーバーライドします。 +> +> 終了コードの 64 から 78 は避けてください。それらは `sysexits.h` で記述された +> 特定の意味を持っています。終了コードの 127 以上を避けてください。 +> それらは、 SIGKILL や SIGSEGV のようなシグナルによるプロセスの終了を示すために使用されます。 diff --git a/docs/ja/contents.md b/docs/ja/contents.md new file mode 100644 index 0000000000..e73d70ad0e --- /dev/null +++ b/docs/ja/contents.md @@ -0,0 +1,80 @@ +# コンテンツ + +### はじめに + +- [CakePHP 概要](intro) +- [クイックスタートガイド](quickstart) +- [移行ガイド](appendices/migration-guides) +- [チュートリアルと例](tutorials-and-examples) +- [貢献](contributing) +- [リリースポリシー](release-policy) + +### CakePHP 入門 + +- [インストール](installation) +- [構成設定](development/configuration) +- [アプリケーション](development/application) +- [依存性の注入(DI)](development/dependency-injection) +- [ルーティング](development/routing) +- [リクエストとレスポンスオブジェクト](controllers/request-response) +- [ミドルウェア](controllers/middleware) +- [コントローラー](controllers) +- [ビュー](views) +- [データベースアクセス & ORM](orm) + +### 一般的なトピック + +- [キャッシュ](core-libraries/caching) +- [コンソールコマンド](console-commands) +- [デバッグ](development/debugging) +- [デプロイ](deployment) +- [Mailer](core-libraries/email) +- [エラーと例外の処理](development/errors) +- [イベントシステム](core-libraries/events) +- [国際化と地域化](core-libraries/internationalization-and-localization) +- [ロギング](core-libraries/logging) +- [モデルのないフォーム](core-libraries/form) +- [ページネーション](controllers/pagination) +- [プラグイン](plugins) +- [REST](development/rest) +- [セキュリティ](security) +- [セッション](development/sessions) +- [テスト](development/testing) +- [バリデーション](core-libraries/validation) + +### ユーティリティ + +- [Appクラス](core-libraries/app) +- [コレクション](core-libraries/collections) +- [Folder & File](core-libraries/file-folder) +- [Hash](core-libraries/hash) +- [Http Client](core-libraries/httpclient) +- [Inflector](core-libraries/inflector) +- [Number](core-libraries/number) +- [Plugin Class](core-libraries/plugin) +- [レジストリーオブジェクト](core-libraries/registry-objects) +- [Text](core-libraries/text) +- [日付と時刻](core-libraries/time) +- [Xml](core-libraries/xml) + +### プラグインとパッケージ + +- [スタンドアロンパッケージ](standalone-packages) +- [Authentication](https://book.cakephp.org/authentication/2/ja/) +- [Authorization](https://book.cakephp.org/authorization/2/) +- [Bake](https://book.cakephp.org/bake/2/ja/) +- [Debug Kit](https://book.cakephp.org/debugkit/4/ja/) +- [Migrations](https://book.cakephp.org/migrations/3/ja/) +- [Elasticsearch](https://book.cakephp.org/elasticsearch/3/ja/) +- [Phinx](https://book.cakephp.org/phinx/0/ja/) +- [Chronos](https://book.cakephp.org/chronos/2/ja/) +- [Queue](https://book.cakephp.org/queue/1/en/) + +### その他 + +- [定数および関数](core-libraries/global-constants-and-functions) +- [付録](appendices) + +
    + +
    diff --git a/docs/ja/contributing.md b/docs/ja/contributing.md new file mode 100644 index 0000000000..dc71144dca --- /dev/null +++ b/docs/ja/contributing.md @@ -0,0 +1,10 @@ +# 貢献 + +CakePHP に貢献する方法は数多くあります。 +以下のセクションは、 CakePHP に貢献するための様々な方法について記述しています。 + +- [ドキュメント](contributing/documentation) +- [チケット](contributing/tickets) +- [コード](contributing/code) +- [コーディング規約](contributing/cakephp-coding-conventions) +- [後方互換性ガイド](contributing/backwards-compatibility) diff --git a/docs/ja/contributing/backwards-compatibility.md b/docs/ja/contributing/backwards-compatibility.md new file mode 100644 index 0000000000..9cda22095d --- /dev/null +++ b/docs/ja/contributing/backwards-compatibility.md @@ -0,0 +1,297 @@ +# 後方互換性ガイド + +アプリケーションを簡単に、円滑にアップグレードができるようになっていることは +重要なことです。 +メジャーリリースのマイルストーンでのみ互換性を破棄するのはそのためです。 +全ての CakePHP プロジェクトで使用する全般的なガイドラインである、 +[セマンティックバージョニング](https://semver.org/) について理解したほうが +良いでしょう。 +セマンティックバージョニングとは簡単に言うと、(2.0、3.0、4.0 のような) +メジャーリリースのみ後方互換性を破棄することができ、(2.1、3.1、3.2 のような) +マイナーリリースが新しい機能の導入をするが互換性を破棄することは許されず、 +(2.1.2、3.0.1 のような) バグフィックスリリースは新機能の追加はせず、 +バグの修正かパフォーマンスの向上のみにすることを意味します。 + +> [!NOTE] +> 非推奨は、フレームワークの次のメジャーバージョンで削除されます。 +> 非推奨のコメントや移行ガイドに記載されているように、コードに +> マイナーリリースの変更を早期に適用することをお勧めします。 + +それぞれのリリースに予定する変更を明確にするために、CakePHP を使う開発者と +CakePHP の開発者のために、マイナーリリースでされ得る変更予定の助けとなる更に +詳しい情報があります。 +メジャーリリースは必要に応じて多くの破壊的変更を含むことができます。 + +## 移行ガイド + +メジャー・マイナーの各リリースについて、CakePHP チームは移行ガイドを提供します。 +このガイドはそれぞれのリリースの新機能と非互換な変更を説明します。 +これは Cookbook の [付録](../appendices) セクションで見ることができます。 + +## CakePHP の使用 + +CakePHP を用いてアプリケーションを構築している場合、次のガイドラインで示す堅牢性 +(**stability**) が期待できます。 + +### インターフェイス + +メジャーリリース以外では、CakePHP が提供するどんな既存のメソッドの +インターフェイスも変更 **されません** 。 +新しいメソッドは、既存のインターフェイスには追加 **されません** 。 + +### クラス + +CakePHP が提供するクラスを構成する、アプリケーションから使われる public なメソッド +とプロパティーがメジャーリリース以外で後方互換性が保証されます。 + +> [!NOTE] +> CakePHP のいくつかのクラスは `@internal` API ドキュメントタグがつけられています。 +> これらのクラスは堅牢 **ではなく** 、後方互換性は約束されていません。 + +マイナーリリースでは、クラスに新しいメソッドが追加されることがあり、 +既存のメソッドは新しい引数が追加されることもあります。 +新しい引数は必ずデフォルト値を持ちますが、異なる引数の形式でメソッドを +オーバーライドしていると致命的な (**fatal**) エラーになることがあります。 +新しい引数が追加されたメソッドは、そのリリースの移行ガイドに掲載されます。 + +下記のテーブルはいくつかのユースケースと CakePHP に予定する互換性についての +概要となります。 + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    すること互換性
    クラスに対するタイプヒント有り
    新しいインスタンスの作成有り
    クラスの継承有り
    public プロパティーへのアクセス有り
    public メソッドの呼び出し有り
    クラスを継承して...
    public プロパティーをオーバーライド有り
    protected プロパティーにアクセス無し1
    protected プロパティーをオーバーライド無し2
    protected メソッドをオーバーライド無し3
    protected メソッドの呼び出し無し4
    public プロパティーの追加無し
    public メソッドの追加無し
    オーバーライドされたメソッド への引数の追加無し5
    既存メソッドの引数へのデフォルト値 の追加有り
    +
    +
    +
      +
    1. マイナーリリースでコードが破壊される 恐れが あります。 詳細は移行ガイドをチェックしてください。↩︎

    2. +
    3. マイナーリリースでコードが破壊される 恐れが あります。 詳細は移行ガイドをチェックしてください。↩︎

    4. +
    5. マイナーリリースでコードが破壊される 恐れが あります。 詳細は移行ガイドをチェックしてください。↩︎

    6. +
    7. マイナーリリースでコードが破壊される 恐れが あります。 詳細は移行ガイドをチェックしてください。↩︎

    8. +
    9. マイナーリリースでコードが破壊される 恐れが あります。 詳細は移行ガイドをチェックしてください。↩︎

    10. +
    +
    + +## CakePHP での作業 + +CakePHP をより良くする手助けをしようという場合、機能の追加・変更時に以下の +ガイドラインに沿うように頭にとどめておいてください。 + +マイナーリリースでは次のことができます。 + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    マイナーリリースでできること
    クラス
    クラスの削除不可
    インターフェイスの削除不可
    トレイトの削除不可
    final にする不可
    abstract にする不可
    名前の変更1
    プロパティー
    public プロパティーの追加
    public プロパティーの削除不可
    protected プロパティーの追加
    protected プロパティーの削除2
    メソッド
    public メソッドの追加
    public メソッドの削除不可
    protected メソッドの追加
    親クラスへの移動
    protected メソッドの削除3
    可視性の減少不可
    メソッド名の変更4
    デフォルト値つき引数の新規追加
    既存メソッドへの必須引数の 新規追加不可
    既存引数からのデフォルト値の 削除不可
    void 型メソッドの変更
    +
    +
    +
      +
    1. 古いクラス名・メソッド名を利用可能なように残しておくことで名前の変更ができます。 通常、名前の変更は重要な利点を持っていない限り避けられます。↩︎

    2. +
    3. 出来る限り避けましょう。削除したことは移行ガイドに掲載する必要があります。↩︎

    4. +
    5. 出来る限り避けましょう。削除したことは移行ガイドに掲載する必要があります。↩︎

    6. +
    7. 古いクラス名・メソッド名を利用可能なように残しておくことで名前の変更ができます。 通常、名前の変更は重要な利点を持っていない限り避けられます。↩︎

    8. +
    +
    + +## 非推奨 + +各マイナーリリースでは、機能が非推奨になる可能性があります。 +機能が非推奨になると、API ドキュメントや実行時の警告が追加されます。 +実行時エラーは、コードが壊れる前に更新する必要があるコードを見つけるのに役立ちます。 +実行時の警告を無効にするには、 `Error.errorLevel` 設定値を使用します。 : + +``` text +// config/app.php の中で +// ... +'Error' => [ + 'errorLevel' => E_ALL ^ E_USER_DEPRECATED, +] +// ... +``` + +これで、実行時の非推奨警告を無効にします。 + + + +## Experimental Features + +Experimental features are **not included** in the above backwards compatibility +promises. Experimental features can have breaking changes made in minor releases +as long as they remain experimental. Experiemental features can be identified by +the warning in the book and the usage of `@experimental` in the API +documentation. + +Experimental features are intended to help gather feedback on how a feature +works before it becomes stable. Once the interfaces and behavior has been vetted +with the community the experimental flags will be removed. diff --git a/docs/ja/contributing/cakephp-coding-conventions.md b/docs/ja/contributing/cakephp-coding-conventions.md new file mode 100644 index 0000000000..57a7623b55 --- /dev/null +++ b/docs/ja/contributing/cakephp-coding-conventions.md @@ -0,0 +1,656 @@ +# コーディング規約 + +CakePHP の開発者はコーディング規約として下記のルールに加え、 [PSR-12 coding style guide](https://www.php-fig.org/psr/psr-12/) にも従って頂くことになります。 + +その他の CakePHP プラグイン等 (訳注:原文 *CakeIngredients* 、ケーキの材料のこと) +の開発でも同じ規約に従うことが推奨されます。 + +[CakePHP Code Sniffer](https://github.com/cakephp/cakephp-codesniffer) を使って、 +コードが規約に沿っているかどうかをチェックすることができます。 + +## 新しい機能の追加 + +新しい機能は、そのテストが無い限り、追加してはいけません。 +このテストはリポジトリーにコミットされる前にパスする必要があります。 + +## IDE の設定 + +IDE が空白の "trim right" の設定がされているかどうかを確かめてください。 +その設定で、行の空白は残りません。 + +最新の IDE は、 `.editorconfig` ファイルをサポートします。CakePHP アプリの +スケルトンは、デフォルトで提供しています。すでにデフォルトでベストプラクティスが入っています。 + +IDE との互換性を最大限にしたい場合、 [IdeHelper](https://github.com/dereuromark/cakephp-ide-helper) プラグインの使用をお勧めします。 +アノテーションを最新の状態に保ち、IDE がすべてのクラスの仕組みを完全に理解し、 +より良いタイプヒントと自動補完を提供するのを支援します。 + +## インデント + +インデントには4つの空白を用います。 + +従って、インデントはこのようになります。 : + +``` text +// 基本レベル + // レベル1 + // レベル2 + // レベル1 +// 基本レベル +``` + +または: + +``` php +$booleanVariable = true; +$stringVariable = '大鹿'; +if ($booleanVariable) { + echo '真偽値は true です'; + if ($stringVariable === '大鹿') { + echo '大鹿に遭遇しました'; + } +} +``` + +あなたが、複数行にわたる関数呼び出しを使用している場合、以下のガイドラインに従ってください。 + +- 複数行にわたる関数呼び出しのカッコを開く時には、行末になければなりません。 +- 複数行にわたる関数呼び出しの中の1行ごとに1引数のみ許可します。 +- 複数行にわたる関数呼び出しの閉じカッコは、1行にしなければなりません。 + +例として、以下の形式が使用されていた場合、 : + +``` php +$matches = array_intersect_key($this->_listeners, + array_flip(preg_grep($matchPattern, + array_keys($this->_listeners), 0))); +``` + +代わりに以下を使用してください。 : + +``` php +$matches = array_intersect_key( + $this->_listeners, + array_flip( + preg_grep($matchPattern, array_keys($this->_listeners), 0) + ) +); +``` + +## 行の長さ + +コードを読みやすくするために、だいたい 100 文字程度の長さで改行することを推奨します。 +80 文字や 120 文字に制限することで、複雑なロジックや式を関数によって分割するだけでなく、 +関数やオブジェクトに、短く、より表現的な名前をつけることが必要になります。 +行を 120 文字より長くしてはいけません。 + +要約すると: + +- 100 文字がソフトな制限 +- 120 文字がハードな制限 + +## 制御構造 + +制御構造は例えば "`if`"、"`for`"、"`foreach`"、"`while`"、"`switch`"などです。 +下記に、 "`if`" の例を示します。 : + +``` text +if ((expr_1) || (expr_2)) { + // action_1; +} elseif (!(expr_3) && (expr_4)) { + // action_2; +} else { + // default_action; +} +``` + +- 制御構造では1個の空白が最初の丸括弧の前に、1個の空白が最後の丸括弧と開き中括弧の間にある必要があります。 +- 制御構造では、必要でなくとも常に中括弧を使います。 + これはコードの可読性を高め、論理エラーが発生しにくくなります。 +- 開き中括弧は制御構造と同じ行に置かれる必要があります。閉じ中括弧は新しい行に置かれ、 + 制御構造と同じレベルのインデントがされている必要があります。中括弧内に含まれているステートメントは + 新しい行で始まり、その中に含まれるコードは、新しいレベルのインデントが付けられる必要があります。 +- インラインの代入は、制御構造の中で使用するべきではありません。 + +``` php +// 間違い=中括弧が無い、ステートメントの場所が悪い +if (expr) statement; + +// 間違い=中括弧が無い +if (expr) + statement; + +// よろしい +if (expr) { + statement; +} + +// 間違い=インラインの代入 +if ($variable = Class::function()) { + statement; +} + +// よろしい +$variable = Class::function(); +if ($variable) { + statement; +} +``` + +### 三項演算子 + +三項演算子は、三項演算子全体が1行に収まる場合に許容されます。 +長い三項演算子は `if else` ステートメントに分割するべきです。 +どのような場合でも、三項演算子はネストしてはいけません。 +見やすさのために、丸括弧を三項の条件チェックの周りに使ってもかまいません。 : + +``` php +//良い。シンプルで読みやすい +$variable = isset($options['variable']) ? $options['variable'] : true; + +//ネストされた三項はダメ +$variable = isset($options['variable']) ? isset($options['othervar']) ? true : false : false; +``` + +### テンプレートファイル + +テンプレートファイル (拡張子が .php のファイル) 内では、開発者は、キーワードの制御構造を使用する +必要があります。キーワードの制御構造を使うと、複雑なテンプレートファイルが読みやすくなります。 +制御構造は、大きい PHP ブロック内、または別々の PHP タグに含めることができます。 : + +``` php +You are the admin user.

    '; +endif; +?> +

    The following is also acceptable:

    + +

    You are the admin user.

    + +``` + +## 比較 + +値の比較は、常に可能な限り厳密に行うようにしてください。もし厳格でないテストが意図的なものであれば、 +混乱を避けるためにコメントを残しておいたほうがいいかもしれません。 + +変数が null かどうかのテストの場合は、厳密なチェックを使用することを推奨します。 : + +``` php +if ($value === null) { + // ... +} +``` + +チェック対象の値は右側に配置してください(※ヨーダ記法を避けるということ)。 : + +``` php +// 非推奨 +if (null === $this->foo()) { + // ... +} + +// 推奨 +if ($this->foo() === null) { + // ... +} +``` + +## 関数の呼び出し + +関数は、関数の名前と開き括弧の間に空白を入れて呼び出してはいけません。 +関数呼び出しの引数各々に対して単一の空白がある必要があります。 : + +``` php +$var = foo($bar, $bar2, $bar3); +``` + +上記をご覧の通り、イコール記号 (=) の両サイドには単一の空白がある必要があります。 + +## メソッドの定義 + +メソッドの定義の例: + +``` php +public function someFunction($arg1, $arg2 = '') +{ + if (expr) { + statement; + } + + return $var; +} +``` + +デフォルトを用いた引数は、関数の定義の最後に置く必要があります。関数は何かを、少なくとも true か +false を、関数呼び出しが成功したかどうかを判定できるように、返すように作ってみてください。 : + +``` php +public function connection($dns, $persistent = false) +{ + if (is_array($dns)) { + $dnsInfo = $dns; + } else { + $dnsInfo = BD::parseDNS($dns); + } + + if (!($dnsInfo) || !($dnsInfo['phpType'])) { + return $this->addError(); + } + + return true; +} +``` + +イコール記号の両サイドには空白を置きます。 + +## アーリーリターン + +「アーリーリターン」テクニックを活用し、不必要なネストを避けるようにしてください。 : + +``` php +public function run(array $data) +{ + ... + if (!$success) { + return false; + } + + ... +} + +public function check(array $data) +{ + ... + if (!$success) { + throw new RuntimeException(/* ... */); + } + + ... +} +``` + +これは、コードの流れをシンプルかつ容易に保つのに役立ちます。 + +### タイプヒンティング + +オブジェクトや配列を期待する引数はタイプヒンティングを指定することができます。 +しかしながらタイプヒンティングはコストフリーではないので、public メソッドにだけ指定します。 : + +``` php +/** + * メソッドの説明。 + * + * @param \Cake\ORM\Table $table 使用するテーブルクラス + * @param array $array 配列。 + * @param callable $callback コールバック。 + * @param bool $boolean 真偽値。 + */ +public function foo(Table $table, array $array, callable $callback, $boolean) +{ +} +``` + +ここで `$table` は `\Cake\ORM\Table` のインスタンスで、また `$array` は `array` +でなければならず、 `$callback` は `callback` (有効なコールバック) 型でなければなりません。 + +ちなみに、もし `$array` が `\ArrayObject` のインスタンスでも受け付けるようにしたい場合は、 +`array` のタイプヒントを指定してプリミティブ型だけを受け入れるようにするべきではありません。 : + +``` php +/** + * メソッドの説明。 + * + * @param array|\ArrayObject $array 配列。 + */ +public function foo($array) +{ +} +``` + +### 無名関数 (クロージャー) + +無名関数の定義は [PSR-12](https://www.php-fig.org/psr/psr-12/) コーディングスタイルガイドに従ってください。 +そこでは function キーワードの後ろに空白1つ、 use キーワードの前後に空白1つずつが +必要であると宣言されています。 : + +``` php +$closure = function ($arg1, $arg2) use ($var1, $var2) { + // code +}; +``` + +## メソッドチェーン + +メソッドチェーンは複数の行にまたがる複数のメソッドとなり、空白4つでインデントする必要があります。 : + +``` php +$email->from('foo@example.com') + ->to('bar@example.com') + ->subject('A great message') + ->send(); +``` + +## コードのコメント + +全てのコメントは英語で書かれ、コードのコメントブロックを明確な方法で記述する必要があります。 + +コメントは以下の [phpDocumentor](https://phpdoc.org) タグを含めることができます。 + +- [@deprecated](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/deprecated.html) + `@version ` 形式を使用して、 `version` と `description` + は必須です。バージョンは非推奨のバージョンを示します。 +- [@example](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/example.html) +- [@ignore](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/ignore.html) +- [@internal](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/internal.html) +- [@link](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/link.html) +- [@see](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/see.html) +- [@since](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/since.html) +- [@version](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/version.html) + +PhpDoc タグは Java の JavaDoc タグによく似ています。 +タグはドキュメントブロックの行の最初のもののみ処理されます。 +例: + +``` text +/** + * タグの例。 + * + * @author このタグは解析されますが、この @version は無視されます + * @version 1.0 このタグも解析されます + */ +``` + +``` text +/** + * インライン phpDoc タグの例。 + * + * この関数は世界征服のために foo() を使って身を粉にして働きます。 + * + * @return void + */ +function bar() +{ +} + +/** + * Foo function. + * + * @return void + */ +function foo() +{ +} +``` + +ファイルの最初のブロック以外のコメントブロックは、常に新しい行を先に置く必要があります。 + +### 変数の型 + +ドキュメントブロックで使う変数の型: + +型名 +説明 + +mixed +型が定義されていない(もしくは複数定義されている)変数。 + +int +Integer 型の変数 (整数)。 + +float +Float 型 (小数点のある数値)。 + +bool +論理型 (true または false)。 + +string +String 型 (" " や ' ' で囲まれるすべての値)。 + +null +Null 型。通常は他の型と一緒に使われる。 + +array +配列型。 + +object +オブジェクト型。可能なら特定のクラス名を指定するべきです。 + +resource +リソース型 (例えば mysql_connect() の戻り値)。型を mixed に指定する場合、 +不明 (*unknown*) なのか、取りうる型が何なのかを指し示すべきということを覚えていてください。 + +callable +呼び出し可能な関数。 + +パイプ文字を使って型を組合せます。 : + + int|bool + +3つ以上の型の場合は `mixed` を使うほうが最良です。 + +チェーンのように自分自身のオブジェクトを返すような場合は代わりに `$this` を使ってください。 : + +``` php +/** + * Foo function. + * + * @return $this + */ +public function foo() +{ + return $this; +} +``` + +## ファイルの読み込み + +`include` 、 `require` 、 `include_once` そして `require_once` は括弧を付けないようにしてください。 : + +``` text +// 間違い = 括弧あり +require_once('ClassFileName.php'); +require_once ($class); + +// よろしい = 括弧なし +require_once 'ClassFileName.php'; +require_once $class; +``` + +クラスまたはライブラリーを伴うファイルを読み込む場合、 +[require_once](https://php.net/require_once) +関数のみを常に使用してください。 + +## PHP タグ + +常にショートタグ (``) の代わりに、ロングタグ (``) を使ってください。 +テンプレートファイル (**.php**) の中では適宜、ショート Echo を使ってください。 + +### Echoの短縮記法 + +テンプレートファイルの中でEchoの短縮記法を ` + +// OK = 空白があり、セミコロンもない + +``` + +PHP 5.4 以降、ショート Echo タグ (` +- WWW: +- FTP: + +"example.com" ドメインはこの (`2606` を見てください) 為に予約されており、 +ドキュメント内の説明や例として使うことが推奨されています。 + +### ファイル + +クラスを含まないファイルの名前は、小文字でアンダースコアー化される必要があります。例: + + long_file_name.php + +### キャスト + +次のキャストを使用します。 + +型 +説明 + +(bool) +boolean にキャスト。 + +(int) +integer にキャスト。 + +(float) +float にキャスト。 + +(string) +string にキャスト。 + +(array) +array にキャスト。 + +(object) +object にキャスト。 + +できるなら `intval($var)` よりも `(int)$var` を、 +`floatval($var)` よりも `(float)$var` を使ってください。 + +### 定数 + +定数は大文字で定義する必要があります。 : + +``` text +define('CONSTANT', 1); +``` + +もし定数の名前が複数の単語でできている場合は、アンダースコアー文字によって分割する必要があります。 +例: + +``` text +define('LONG_NAMED_CONSTANT', 2); +``` + +## empty()/isset() の使用に注意 + +`empty()` は、使いやすい関数ですが、エラーの隠蔽と `'0'` や `0` が与えられた際に +意図しない効果を引き起こします。変数やプロパティーがすでに定義されていた場合、 `empty()` +の利用は推奨されません。変数を操作する際、`empty()` ではなく boolean 型への +強制変換を用いましょう。プロパティーのempty判定に関しては、CakePHP独自の仕組みによる問題もあり、特に慎重に検討する必要があります(!issetなプロパティの場合はCakePHPの場合はマジックメソッドが走るためです)。 : + +``` php +function manipulate($var) +{ + // 推奨しません。 $var は引数を通じてすでにスコープ内で定義されています。 + if (empty($var)) { + // ... + } + + // boolean 型への強制変換を使用。 + if (!$var) { + // ... + } + if ($var) { + // ... + } +} +``` + +全てのエンジニアがissetとemptyの違いをに完璧に理解しているわけではありません。定義されたプロパティーを扱っている際、 `empty()`/`isset()` ではなく +`null` チェックを使ってください。これによりリーダブルなコードを維持することができます。 : + +``` php +class Thing +{ + private $property; // 定義済み。つまりnullです。 + + public function readProperty() + { + // issetはnullに対してfalseを返すことを体感的に理解 + // していないエンジニアが多いため、これは推奨しません。 + if (!isset($this->property)) { + // ... + } + // 見た目どおりで分かりやすいため、推奨します。 + if ($this->property === null) { + + } + } +} +``` + +配列を操作する際、 `empty` チェックを使うよりも、デフォルト値をマージする方が良いです。 +デフォルト値をマージすることによって、必要なキーが定義されることを保証できます。 : + +``` php +function doWork(array $array) +{ + // empty チェックを避けるためにデフォルト値をマージ + $array += [ + 'key' => null, + ]; + + // 推奨しません。キーはすでにセットされています。 + if (isset($array['key'])) { + // ... + } + + // 推奨します。 + if ($array['key'] !== null) { + // ... + } +} +``` diff --git a/docs/ja/contributing/code.md b/docs/ja/contributing/code.md new file mode 100644 index 0000000000..c321ce4885 --- /dev/null +++ b/docs/ja/contributing/code.md @@ -0,0 +1,137 @@ +# コード + +パッチと Pull Request はコードを CakePHP に送って貢献するための素晴らしい方法です。 +Pull Request は GitHub で作成することができるもので、パッチファイルをチケットのコメントで +伝えるよりも推奨される方法です。 + +## 最初のセットアップ + +CakePHP の修正に取りかかる前に自分の環境を整えることをお勧めします。 +以下のソフトウェアが必要になります。 + +- Git +- PHP |minphpversion| 以上 - PHPUnit 5.7.0 以上 + +ユーザー情報(自分の名前/ハンドル名とメールアドレス)をセットしてください。 : + + git config --global user.name 'Bob Barker' + git config --global user.email 'bob.barker@example.com' + +> [!NOTE] +> Git を使うのが初めてなら、無料で素敵なドキュメント +> [ProGit](https://git-scm.com/book/ja/) をぜひお読みください。 + +CakePHP のソースコードの clone を GitHub から取得してください。 + +- [GitHub](https://github.com) アカウントを持っていないなら、作成してください。 +- [CakePHP リポジトリー](https://github.com/cakephp/cakephp) の **Fork** + ボタンをクリックして Fork してください。 + +Fork できたら、Fork したものを自分のローカルマシンへと clone してください。 : + + git clone git@github.com:YOURNAME/cakephp.git + +オリジナルの CakePHP リポジトリーを remote リポジトリーとして追加してください。 +これは後で CakePHP リポジトリーから変更分を fetch するのに使うことになり、 +こうすることで CakePHP を最新の状態にしておくのです。 : + + cd cakephp + git remote add upstream git://github.com/cakephp/cakephp.git + +いまや CakePHP はセットアップされましたので +[データベース接続](../orm/database-basics#database-configuration) で `$test` を定義すれば、 +[すべてのテストを実行する](../development/testing#running-tests) ことができるでしょう。 + +## 修正に取りかかる + +バグや新機能、改善に取り組むたびに毎回、 トピック・ブランチを作成してください。 + +ブランチは 修正/改善対象のバージョンをベースにして作成してください。たとえば、 `3.x` +のバグを修正するなら、ブランチのベースとして `master` ブランチを使うことになります。 +もし、 2.x 系のバグ修正なら、 `2.x` ブランチを使用してください。これで、GitHub は、 +あなたに対象のブランチを編集させないので、後で変更をマージする際にとても簡単になります。 : + +``` text +# 3.x のバグを修正 +git fetch upstream +git checkout -b ticket-1234 upstream/master + +# 2.x のバグを修正 +git fetch upstream +git checkout -b ticket-1234 upstream/2.x +``` + +> [!TIP] +> ブランチの名前は説明的につけてください。チケット名や機能名を含めるのは良い慣習です。 +> 例) ticket-1234, feature-awesome + +上記は upstream (CakePHP) 2.x ブランチをベースにローカル・ブランチを作成します。 +修正に取り組み、必要なだけ commit してください。ただし、注意点があります: + +- [コーディング規約](../contributing/cakephp-coding-conventions) に従ってください。 +- バグが修正されたこと、もしくは新機能が機能することが判るテストケースを追加してください。 +- 理にかなったコミットを心がけ、コミット文は解りやすく簡潔に書いてください。 + +## Pull Request を送信する + +変更し終え、 CakePHP へとマージされる準備が整ったら、あなたのブランチを +更新したくなることでしょう。 : + +``` text +# master のトップに修正をリベース +git checkout master +git fetch upstream +git merge upstream/master +git checkout +git rebase master +``` + +これは作業開始以降に CakePHP で行われたすべての変更を fetch + merge します。 +その後に rebase 、つまり現在のコードの先端にあなたの変更を適用します。 +`rebase` 中、コンフリクトに出会うかもしれません。もし rebase が早期終了したら、 +`git status` で、どのファイルがコンフリクト/マージ失敗したのかを見ることができます。 +各コンフリクトを解決させてください。その後、 rebase を continue してください。 : + +``` text +git add # コンフリクトしたファイルごとにこれを行ってください。 +git rebase --continue +``` + +あなたのテストがすべて通過し続けているか確認してください。 +その後、あなたのブランチをあなたの Fork へと push します。 : + +``` text +git push origin +``` + +ブランチを push した後 rebase した場合、force push を使用する必要があります。 : + +``` text +git push --force origin +``` + +あなたのブランチが GitHub に上がったら、GitHub で Pull Request を送ってください。 + +### 変更対象のマージ先を選ぶ + +Pull Request を作る際には、ベースとなるブランチが正しく選ばれているか良く確認してください。 +ひとたび Pull Request を作った後ではもう変更することはできません。 + +- あなたの変更が **バグ修正** であり、新機能を追加しておらず、 + 現在のリリースに存在している既存の振る舞いを正すだけなら、 + マージ先として **master** を選んでください。 +- あなたの変更が **新機能** もしくはフレームワークへの追加なら、 + 次のバージョン番号のブランチを選んでください。 + たとえば、現在の安定版リリースが `3.2.10` なら、 + 新機能を受け入れるブランチは `3.next` になります。 +- あなたの変更が既存の機能性を壊すものであったり、API の仕様を変えるものであるなら、 + 次のメジャーリリースを選ばなければなりません。たとえば、現在のリリースが `3.2.2` なら、 + 次に既存の振る舞いを変更できるのは `4.x` となりますので、そのブランチを選んでください。 + +> [!NOTE] +> あなたが貢献したすべてのコードは MIT License に基づき CakePHP にライセンスされることを +> 覚えておいてください。 [Cake Software Foundation](https://cakefoundation.org/old) がすべての貢献されたコードの所有者になります。 +> 貢献する人は [CakePHP Community Guidelines](https://cakephp.org/get-involved) に従うようお願いします。 + +メンテナンス・ブランチへとマージされたすべてのバグ修正は、 +コアチームにより定期的に次期リリースにもマージされます。 diff --git a/docs/ja/contributing/documentation.md b/docs/ja/contributing/documentation.md new file mode 100644 index 0000000000..0d4db45e22 --- /dev/null +++ b/docs/ja/contributing/documentation.md @@ -0,0 +1,434 @@ +# ドキュメント + +ドキュメントへの貢献の方法はシンプルです。 +ファイルは にホストされています。 +自由にリポジトリーをフォークして、変更・改善・翻訳を追加し、 +プルリクエストを発行してください。 +またファイルをダウンロードせず、 +GitHub を使ってオンラインでドキュメントを編集することもできます。 +どのページにも存在している「Improve this Doc」ボタンを押すことで、 +GitHub 上にあるそのページのオンラインエディターに飛ぶことができます。 + +CakePHP ドキュメントは、プルリクエストがマージされるごとに +[継続的インテグレーション](https://ja.wikipedia.org/wiki/%E7%B6%99%E7%B6%9A%E7%9A%84%E3%82%A4%E3%83%B3%E3%83%86%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3) +が行われ、デプロイされます。 + +## 翻訳 + +ドキュメントチーム (*docs at cakephp dot org*) までEメールを送るか、 +IRC (#cakephp on freenode) で、参加したい旨を連絡してください。 + +### 新たに翻訳する言語 + +できるだけ完全な翻訳を提供したいのですが、 +翻訳ファイルが最新になっていない部分もありえます。 +常に英語バージョンを正のバージョンと考えてください。 + +もし、あなたの言語が現在の言語の中に無いなら、 +我々に Github 経由で連絡を頂ければ、我々はスケルトンフォルダーの作成を考えます。 +以下のセクションは頻繁に書き換わらないファイルなので +翻訳を検討するにはもっとも適しています: + +- index.rst +- intro.rst +- quickstart.rst +- installation.rst +- /intro folder +- /tutorials-and-examples folder + +### ドキュメント管理者へのリマインド + +すべての言語フォルダーの構造は英語フォルダーの構造のミラーであるべきです。 +もし英語版の構造が変わったら、我々はそれらの変更を他の言語にも適用します。 + +たとえば、新しい英語のファイルが **en/file.rst** に作成されたら、 +我々は次のようにします: + +- 他のすべての言語の中にそのファイルを追加します : **fr/file.rst**, **zh/file.rst**, ... + +- 中身を削除します。ただし、 `title` 、 `meta` 情報、 + `toc-tree` 要素は残しておきます。 + 次の記述は誰も翻訳していないファイルに記載されるものです。 : + + ``` text + File Title + ########## + + .. note:: + The documentation is not currently supported in XX language for this + page. + + Please feel free to send us a pull request on + `Github `_ or use the **Improve This Doc** + button to directly propose your changes. + + You can refer to the English version in the select top menu to have + information about this page's topic. + + // If toc-tree elements are in the English version + .. toctree:: + :maxdepth: 1 + + one-toc-file + other-toc-file + + .. meta:: + :title lang=xx: File Title + :keywords lang=xx: title, description,... + ``` + +### 翻訳者tips + +- 翻訳先の言語のページを閲覧・編集してください。そうしなければ、翻訳済みのものは見えません。 +- 選択した言語が book 上にすでに存在しているなら、遠慮無くその先に飛び込んでください。 +- 堅苦しくない文体を使ってください。 +- [エンジニアに通じる言葉](https://ja.wikipedia.org/wiki/%E4%BD%BF%E7%94%A8%E5%9F%9F) + を使ってください。たとえば「Assert」という単語は直訳すると「主張」「表明」という意味になりますが、エンジニアに対しては「アサート」とすべきです。 +- タイトルと内容を同時に翻訳してください。 +- 修正を投稿する前に、英語版との比較を行うようにしてください + (何か修正しても、 'upstream' の変更が含まれていないなら、あなたの投稿は受け入れられません)。 +- 用語を英語で書く必要があるなら、 `` タグで囲んでください。 + 例えば、「asdf asdf *Controller* asdf」 や 「asdf asdf Kontroller (*Controller*) asfd」 などです。 +- 一部だけ翻訳して投稿しないでください。 +- 保留されている項目があるセクションは編集しないでください。 +- アクセント文字のために + [HTML エンティティー](https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references) + を使用しないでください。 + この book は UTF-8 を使っています。 +- マークアップ (HTML) の変更や新しいコンテンツを絶対に変更しないでください。 +- 元のコンテンツに不備がある場合は、まずそれを編集するようにしてください。 + +## ドキュメントのフォーマットガイド + +新しい CakePHP のドキュメントは [ReST 形式](https://ja.wikipedia.org/wiki/ReStructuredText) で書かれています。 +ReST (Re Structured Text) は markdown や textile に似たプレーンテキストのマークアップ記法です。 +一貫性を保持するために、CakePHP ドキュメントに追加をする際には、 +自分のテキストのフォーマットと構造をこのガイドラインに合わせてください。 + +### 行の長さ + +テキストの行は半角 80 列を基準に改行してください。 +例外は長い URL とコードスニペットのみです。 + +### 見出しとセクション + +セクションの見出しはテキストの長さ以上の区切り文字で +タイトルにアンダーラインをつけることで作成されます。 + +- `#` はページタイトルを示すのに使われます。 +- `=` はページ内のセクションを意味するのに使われます。 +- `-` はサブセクションを意味するのに使われます。 +- `~` はサブ-サブセクションを意味するのに使われます。 +- `^` はサブ-サブ-サブセクションを意味するのに使われます。 + +見出しは5レベルより深くネストしてはなりません。 +また、空行で囲まれる必要があります。 + +### 段落(*Paragraphs*) + +段落というのは、全ての行が同じレベルのインデントがつけられた、単純なテキストの塊です。 +段落は1行の空行で区切られる必要があります。 + +### インラインマークアップ + +- 単一のアスタリスク: *text* 強調(斜体) + 我々はこれを一般的なハイライト/強調に使います。 + - `*text*` 。 +- 二つのアスタリスク: **text** 強い強調(太文字) + 我々はこれを作業ディレクトリー、箇条書きリストのタイトル、 + テーブル名(後に続く単語 "table" は含めません)に使います。 + - `**/config/Migrations**` 、 `**articles**` など。 +- 2つのバッククォート: `text` コード例。 + 我々はこれをメソッドのオプション名、テーブルの列名、 + オブジェクト名(後に続く単語 "object" は含めません)、 + メソッド/関数名( "()" を含めます )に使います。 + - ``cascadeCallbacks`` 、 ``true`` 、 ``id`` 、 + ``PagesController`` 、 ``config()`` など。 + +もしアスタリスクやバッククォートが文章の中に現れて、 +インラインマークアップの区切り文字に間違えられうるなら、 +バックスラッシュでエスケープする必要があります。 + +インラインマークアップは多少の制限があります: + +- ネスト **できません** 。 +- マークアップ対象の最初や最後が空白ではいけません: `* text*` は間違いです。 +- マークアップ対象は非単語文字(訳注:空白等)で囲まれることで、それ以外と区別されていなければなりません。 + 単語を分けたくない場合はバックスラッシュで空白をエスケープしてください: `一続きの長い\ *太字部分*\ を含む単語` 。 + +### リスト + +リストマークアップは markdown に非常によく似ています。 +順番なしのリストは単一のアスタリスクと空白から始まる行によって示されます。 +順番がついたリストは同様に数字、または `#` で自動的なナンバリングがなされます。 : + +``` text +* これは中黒(*bullet*)です +* これも同じです。しかしこの行は + 2行あります。 + +1. 一番目の行 +2. 二番目の行 + +#. 自動的なナンバリング +#. は時間の節約をもたらします。 +``` + +インデントされたリストも、セクションをインデントし、空行で区切ることによって作成できます。 : + + * 一番目の行 + * 二番目の行 + + * 深くなってる + * ワーオ! + + * 最初のレベルに戻った。 + +定義リストは以下のようにして作成できます。 : + + 項目 + 定義 + CakePHP + PHP の MVC フレームワーク + +項目は1行以上にすることができませんが、定義は複数行にすることができ、 +全ての行は一貫したインデントをつける必要があります。 + +### リンク + +いくつかの用途に合った種類のリンクがあります。 + +#### 外部リンク + +外部のドキュメントへのリンクは以下のようにできます。 : + +``` text +`php.net への外部リンク `_ +``` + +以上のものは次のようにリンクします: [php.net への外部リンク](https://php.net) + +#### 他のページへのリンク + +> ドキュメントの他のページへ `:doc:` ロール (*role*) を使ってリンクします。 +> 指定するドキュメントへ絶対パスまたは相対パス参照を用いてリンクできます。 +> `.rst` 拡張子は省く必要があります。 +> 例えば、 `` :doc:`form ``core-helpers/html に書かれていたとすると、 +> リンクは core-helpers/form を参照します。 +> もし参照が :doc:/core-helpers`` ` であったら、どこで使われるかを厭わずに、 常に ``/core-helpers\`\` を参照します。 + +#### 相互参照リンク + +> `:ref:` ロールを使って任意のドキュメントに任意のタイトルを相互参照することができます。 +> リンクのラベルはドキュメント全体に渡って一意のものに向けられる必要があります。 +> クラスのメソッドのラベルを作る時は、リンクのラベルのフォーマットとして +> `class-method` を使うのがベストです。 +> +> ラベルの最も一般的な使い方はタイトルの上に書くことです。例: +> +> .. _ラベル名: +> +> セクションの見出し +> ------------------ +> +> 続きの内容.. +> +> 他の場所で、 `` :ref:`ラベル名 `` を用いて上記のセクションを参照することができます。 +> リンクのテキストはリンクの先にあるタイトルになります。 +> また、 :ref:\`リンクテキスト \<ラベル名\>\`\` として自由にリンクのテキストを指定することができます。 + +#### Sphinx が出力する警告を防ぐ + +Sphinx は toc-tree 内に参照されないファイルがあると警告を出力します。 +これは、すべてのファイルが正しいリンクを持っていることを確認する良い方法ではありますが、 +ファイルへのリンクを挿入する必要がないときもありえます。 +たとえば、 epub-contentspdf-contents などがそうです。 +これらのケースでは、ファイルの先頭に `:orphan:` を加えることで、 +このファイルが toc-tree にいないという警告を抑えることができます。 + +### クラスとその内容を記述する + +CakePHP のドキュメントは [phpdomain](https://pypi.org/project/sphinxcontrib-phpdomain/) +を用いて PHP のオブジェクトと構成物を記述するための独自のディレクティブを提供します。 +適切な索引 (*index*) と相互参照機能を与えるためにこのディレクティブとロールの利用は欠かせません。 + +### クラスと構成物を記述する + +各々のディレクティブは索引と名前空間の索引のどちらか、または両方を生成します。 + +> このディレクティブは新規の PHP のグローバル変数を定義します。 + +> クラスに属さない新規のグローバル関数を定義します。 + +> このディレクティブは新規の定数を定義します。 +> これを class ディレクティブの中でネストして使うことにより、クラス定数を作成することもできます。 + +> このディレクティブは現在の名前空間内で新規の例外 (*Exception*) を定義します。 +> コンストラクターの引数を含める書き方もできます。 + +> クラスを記述します。 +> クラスに属するメソッド、属性、定数はこのディレクティブの本文の中にある必要があります。 : +> +> ``` text +> .. php:class:: MyClass +> +> クラスの説明 +> +> .. php:method:: method($argument) +> +> メソッドの説明 +> ``` +> +> 属性、メソッド、定数はネストする必要はありません。 +> これらは単にクラス定義の後につけることができます。 : +> +> ``` text +> .. php:class:: MyClass +> +> クラスについての文 +> +> .. php:method:: methodName() +> +> メソッドについての文 +> ``` +> +>
    +> +> `php:method`, `php:attr`, `php:const` +> +>
    + +> クラスのメソッドと、その引数、返り値、例外を記述します。 : +> +> ``` text +> .. php:method:: instanceMethod($one, $two) +> +> :param string $one: 第一引数。 +> :param string $two: 第二引数。 +> :returns: なんらかの配列。 +> :throws: InvalidArgumentException +> +> これはインスタンスメソッドです。 +> ``` + +> 静的なメソッド、その引数、返り値、例外を記述します。 +> オプションは `php:method` を見てください。 + +> クラスのプロパティー・属性を記述します。 + +#### Sphinx が出力する警告を防ぐ + +Sphinx は関数が複数のファイルから参照されていると警告を出力します。 +これは、関数を2度追加していないことを確認する良い方法ではありますが、 +実際には複数回にわたって関数を書きたいときもありえます。 +たとえば、 debug object/development/debugging と +/core-libraries/global-constants-and-functions から参照されます。 +このケースでは、debug 関数の下に `:noindex:` を加えることで、 +警告を抑えることができます。 +その関数が参照されるために `:no-index:` の **無い** 参照を1つだけを残して下さい。 : + +``` php +.. php:function:: debug(mixed $var, boolean $showHtml = null, $showFrom = true) + :noindex: +``` + +#### 相互参照 + +以下のロールは PHP のオブジェクトを参照し、適合するディレクティブがあればリンクが生成されます。 + +> PHP の関数を参照します。 + +> `$` 接頭辞を持つグローバル変数を参照します。 + +> グローバル定数、またはクラス定数のどちらかを参照します。 +> クラス定数はそのクラスが先に付けられる必要があります。 : +> +> ``` php +> DateTimeは :php:const:`DateTime::ATOM` 定数を持ちます。 +> ``` + +> 名前でクラスを参照します。 : +> +> :php:class:`ClassName` + +> クラスのメソッドを参照します。 +> このロールは両方の種類のメソッドをサポートします。 : +> +> ``` php +> :php:meth:`DateTime::setDate` +> :php:meth:`Classname::staticMethod` +> ``` + +> オブジェクトの属性を参照します。 : +> +> ``` text +> :php:attr:`ClassName::$propertyName` +> ``` + +> 例外を参照します。 + +### ソースコード + +段落の終わりの `::` を用いて、リテラルコードブロックを生成します。 +リテラルブロックはインデントされる必要があり、各段落のように単一の行で区切られる必要があります。 : + +``` text +これは段落です。 :: + + while ($i--) { + doStuff() + } + +これは普通のテキストの再開です。 +``` + +リテラルテキストは変更やフォーマットがされず、1レベル分のインデントが削除されたものが残ります。 + +### 注意と警告 + +重要なヒント、特別な注記、潜在的な危険を読者に知らせるためにしたいことがしばしばあります。 +sphinx の勧告 (*Admonitions*) は、まさにそのために使われます。 +勧告には3つの種類があります。 + +- `.. tip::` tip は面白い情報や重要な情報を文書化、または再反復するために使用されています。 + ディレクティブの内容は完結した文章で書かれ、また全ての適切な句読点を含める必要があります。 +- `.. note::` note は情報の特に重要なもののひとつを文書化するために使用されています。 + ディレクティブの内容は完結した文章で書かれ、また全ての適切な句読点を含める必要があります。 +- `.. warning::` warning は潜在的な障害、またはセキュリティに関する情報を文書化するために使用されています。 + ディレクティブの内容は完結した文章で書かれ、また全ての適切な句読点を含める必要があります。 +- `.. versionadded:: X.Y.Z` "バージョン追加" 勧告は特定のバージョンで追加された + 新機能特有の注記を表示するために使われます。 + `X.Y.Z` はその機能が追加されたバージョンです。 +- `.. deprecated:: X.Y.Z` "バージョン追加" 勧告とは反対に、 "撤廃" 勧告は、 + 廃止される機能を通知するために使われます。 + `X.Y.Z` はその機能が撤廃されるバージョンです。 + +全ての勧告は同じようになります。 : + +``` text +.. note:: + + インデントされ空の行に挟まれます。 + 段落と一緒です。 + +この文は note の一部ではありません。 +``` + +#### サンプル + +> [!TIP] +> これは忘れがちで役に立つ一言です。 + +> [!NOTE] +> ここに注意を払う必要があります。 + +> [!WARNING] +> 危ないかもしれません。 + +::: info Added in version 4.0.0 +すごい機能がバージョン 4.0.0 で追加されました。 +::: + +::: info Deprecated in version 4.0.1 +この古い機能はバージョン 4.0.1 で撤廃されます。 +::: diff --git a/docs/ja/contributing/tickets.md b/docs/ja/contributing/tickets.md new file mode 100644 index 0000000000..1c9e1059ae --- /dev/null +++ b/docs/ja/contributing/tickets.md @@ -0,0 +1,41 @@ +# チケット + +チケットの形式でコミュニティーからのフィードバックと助けを得ることは、 +CakePHP の開発プロセスの極めて重要な部分です。CakePHP のチケットの全ては +[GitHub](https://github.com/cakephp/cakephp/issues) にホストされています。 + +## バグの報告 + +よく書かれたバグの報告は非常に有用です。 +可能な限り最善のバグレポートを作成するのに役立ついくつかのステップがあります: + +- 既存のよく似ているチケットを + [検索](https://github.com/cakephp/cakephp/search?q=it+is+broken&ref=cmdform&type=Issues) + して、誰かが既にあなたの問題を報告してるかを、またはリポジトリー内でまだバグが修正されていないかを + 確認 **してください** 。 +- **バグを再現する方法** についての詳細な手順を必ず **書いてください** 。 + これは問題の実例を示すテストケースかコードスニペットの形式となるでしょう。 + 問題を再現する方法がないと、修正される可能性が低くなります。 +- 環境 (OS、PHP バージョン、CakePHP バージョン) についてできるだけ詳しく **書いてください** 。 +- サポートの質問にチケットシステムを使用 **しないでください** 。 + [Freenode](https://webchat.freenode.net) の \#cakephp IRC チャンネルでは、 + あなたの質問の答えを手助けしてくれる多くの開発者に会えます。 + [Stack Overflow](https://stackoverflow.com/questions/tagged/cakephp) + も覗いてみてください。 + +## セキュリティ問題の報告 + +もし CakePHP でセキュリティ問題を見つけたら、通常のバグ報告システムの代わりに、 +以下の手順を使用してください。バグトラッカー、メーリングリスト、IRC を使う代わりに、 +どうか **security \[at\] cakephp.org** に Eメールを送るようにしてください。 +このアドレスに送られた Eメールは CakePHP コアチームにプライベートなメーリングリストで送られます。 + +各々の報告では、私たちは最初に脆弱性を確認しようとします。 +一旦確認されたならば、CakePHP チームは次の処置を講ずるでしょう: + +- 私たちは問題を受け渡した報告者に受け取ったことを知らせた上で、修正に取り組みます。 + 私達がそれを発表するまで報告者が問題の機密性を保持するようお願いします。 +- 修正・パッチを準備します。 +- 危弱性と、エクスプロイトコードの説明をする記事を準備します。 +- 影響する全てのバージョンの新規バージョンをリリースします。 +- リリースの発表にその問題を大々的に載せます。 diff --git a/docs/ja/controllers.md b/docs/ja/controllers.md new file mode 100644 index 0000000000..8b5ce7bb12 --- /dev/null +++ b/docs/ja/controllers.md @@ -0,0 +1,592 @@ +# コントローラー + +`class` Cake\\Controller\\**Controller** + +コントローラー (Controller) は MVC の 'C' です。ルーティングが適用されて適切なコントローラーが見つかった後、 +コントローラーのアクションが呼ばれます。コントローラーはリクエストを解釈して、適切なモデルが +呼ばれるのを確認して、正しいレスポンスまたはビューを書き出します。コントローラーはモデルとビューの +中間層とみなすことができます。コントローラーは薄くシンプルに、モデルを大きくしましょう。 +そうすれば、あなたの書いたコードはより簡単に再利用できるようになり、そしてより簡単にテストできるでしょう。 + +一般的に、コントローラーはひとつのモデルのロジックを管理するために使われます。 +たとえば、オンラインベーカリーのサイトを構築しようとしている場合、レシピやその材料を管理する +RecipesController と IngredientsController を作るでしょう。 +コントローラーは複数のモデルを扱う場合でも問題なく動作しますが、CakePHP では +主に操作するモデルにちなんで、コントローラーの名前が付けられます。 + +アプリケーションのコントローラーは `AppController` クラスを継承し、そしてそれは +`Controller` クラスを継承しています。 `AppController` クラスは +**src/Controller/AppController.php** に定義し、アプリケーションのコントローラー全体で +共有されるメソッドを含めるようにしましょう。 + +コントローラーは、リクエストを操作するための *アクション* と呼ばれるいくつかのメソッドを提供します。 +デフォルトで、コントローラー上のすべての public メソッドはアクションとなり、URL からアクセスができます。 +アクションはリクエストを解釈してレスポンスを返す役割があります。 +普通、レスポンスは描画されたビューの形式となっていますが、同様のレスポンスを作成する方法は他にもあります。 + + + +## AppController + +冒頭で述べたように、 `AppController` クラスはアプリケーションのすべてのコントローラーの +親クラスとなります。 `AppController` はそれ自身、CakePHP のコアライブラリーに含まれる +`Cake\Controller\Controller` クラスを継承しています。 +`AppController` は **src/Controller/AppController.php** に次のように定義されます。 : + +``` php +namespace App\Controller; + +use Cake\Controller\Controller; + +class AppController extends Controller +{ +} +``` + +`AppController` で作られたクラス変数とメソッドはそれを継承するすべてのコントローラーで +有効となります。コンポーネント (あとで学びます) は多くのコントローラー +(必ずしもすべてのコントローラーとは限りません) で使われるコードをまとめるのに使われます。 + +アプリケーション中のすべてのコントローラーで使われるコンポーネントを読み込むために +`AppController` を使うことができます。CakePHP はこうした用途のために、 Controller +のコンストラクターの最後で呼び出される `initialize()` メソッドを提供します。 : + +``` php +namespace App\Controller; + +use Cake\Controller\Controller; + +class AppController extends Controller +{ + public function initialize(): void + { + // CSRF コンポーネントを常に有効にします。 + $this->loadComponent('Csrf'); + } +} +``` + +## リクエストの流れ + +CakePHP アプリケーションへのリクエストが生じると、 CakePHP の `Cake\Routing\Router` +と `Cake\Routing\Dispatcher` クラスは正しいコントローラーを見つけて、 +インスタンスを作成するために [Routes Configuration](development/routing#routes-configuration) を使用します。 +リクエストデータはリクエストオブジェトの中にカプセル化されています。 +CakePHP はすべての重要なリクエスト情報を `$this->request` プロパティーの中に格納します。 +CakePHP のリクエストオブジェクトについてのより詳しい情報は [Cake Request](controllers/request-response#cake-request) の章を参照してください。 + +## コントローラーのアクション + +コントローラーのアクションは、リクエストパラメーターを、要求を送ってきたブラウザーやユーザーに対する +レスポンスに変換する役割があります。CakePHP は規約に則ることで、このプロセスを自動化し、 +本来であればあなたが書かなければならなかったコードを省いてくれます。 + +CakePHP は規約に従って、アクション名のビューを描画します。オンラインベーカリーの例に戻りますが、 +RecipesController は `view()` ・ `share()` ・ `search()` アクションを持つかもしれません。 +このコントローラーは **src/Controller/RecipesController.php** にあり、 +次のようなコードになっています。 : + +``` php +// src/Controller/RecipesController.php + +class RecipesController extends AppController +{ + public function view($id) + { + // アクションの処理をここで行います。 + } + + public function share($customerId, $recipeId) + { + // アクションの処理をここで行います。 + } + + public function search($query) + { + // アクションの処理をここで行います。 + } +} +``` + +これらのアクションのテンプレートファイルは **templates/Recipes/view.php** 、 +**templates/Recipes/share.php** 、そして **templates/Recipes/search.php** になります。 +規約に従ったビューのファイル名は、アクション名を小文字にしてアンダースコアーでつないだものです。 + +通常、コントローラーのアクションは `View` クラスがビュー層の描画で使う +コンテキストを作るために `~Controller::set()` を使います。 +CakePHP の規約に従うと、手動でビューを描画したり生成したりする必要はありません。 +代わりに、コントローラーのアクションが完了すると、CakePHP はビューの描画と送信をします。 + +もし何らかの理由でデフォルトの動作をスキップさせたければ、完全にレスポンスを作成して、 +アクションから `Cake\Http\Response` オブジェクトを返すこともできます。 + +アプリケーションでコントローラーを効率的に使うために、CakePHP のコントローラーから提供される +いくつかのコアな属性やメソッドを説明しましょう。 + +## ビューとの相互作用 + +コントローラーはビューといくつかの方法でお互いに作用しあっています。最初に、コントローラーは +`Controller::set()` を使って、ビューにデータを渡すことができます。 +コントローラーからどのビュークラスを使うか、どのビューを描画すべきか、を決めることもできます。 + + + +### ビュー変数の設定 + +`method` Cake\\Controller\\Controller::**set**(string $var, mixed $value) + +`Controller::set()` メソッドはコントローラーからビューへデータを渡すための主な方法です。 +`Controller::set()` を使った後は、その変数はビュー内でアクセスできるようになります。 : + +``` php +// まずコントローラーからデータを渡します + +$this->set('color', 'pink'); + +// すると、ビューでそのデータを利用できます +?> + +You have selected icing for the cake. +``` + +`Controller::set()` メソッドは最初のパラメーターに連想配列も指定できます。 +この方法はデータのかたまりを簡単にビューに割り当てる方法としてよく使われます。 : + +``` php +$data = [ + 'color' => 'pink', + 'type' => 'sugar', + 'base_price' => 23.95 +]; + +// $color・$type および $base_price を作成し +// ビューで使用できるようになります + +$this->set($data); +``` + +### ビューオプションの設定 + +もしビュークラスや、レイアウト/テンプレートのパス、 +ビューの描画時に使われるヘルパーやテーマをカスタマイズしたければ、 +ビルダーを得るために `viewBuilder()` メソッドを使います。 +このビルダーは、ビューが作成される前にビューのプロパティーを設定するために使われます。 : + +``` php +$this->viewBuilder() + ->addHelper('MyCustom') + ->setTheme('Modern') + ->setClassName('Modern.Admin'); +``` + +上記は、どのようにしてカスタムヘルパーを読み込み、テーマを設定し、 +カスタムビュークラスを使用できるかを示しています。 + +### ビューの描画 + +`method` Cake\\Controller\\Controller::**render**(string $view, string $layout) + +`Controller::render()` メソッドは各アクションの最後に自動的に呼ばれます。 +このメソッドは (`Controller::set()` で渡したデータを使って) +すべてのビューロジックを実行し、ビューを `View::$layout` 内に配置し、 +エンドユーザーに表示します。 + +render に使用されるデフォルトのビューファイルは、規約によって決定されます。 +RecipesController の `search()` アクションがリクエストされたら、 +**templates/Recipes/search.php** のビューファイルが描画されます。 : + +``` php +namespace App\Controller; + +class RecipesController extends AppController +{ +// ... + public function search() + { + // templates/Recipes/search.php のビューを描画します + return $this->render(); + } +// ... +} +``` + +CakePHP は (`$this->autoRender` に `false` をセットしない限り) アクションの後に +自動的に描画メソッドを呼び出しますが、 `Controller::render()` メソッドの第一引数に +ビュー名を指定することで、別のビューファイルを指定することができます。 + +`$view` が '/' で始まっていれば、ビューまたはエレメントのファイルが **src/Template** +フォルダーからの相対パスであると見なします。これはエレメントを直接描画することができ、 +AJAX 呼び出しではとても有用です。 : + +``` php +// templates/element/ajaxreturn.php のエレメントを描画します +$this->render('/element/ajaxreturn'); +``` + +`Controller::render()` の第二引数 `$layout` +はビューが描画されるレイアウトを指定することができます。 + +#### 特定のテンプレートの描画 + +コントローラーで、規約に従ったものではなく、別のビューを描画したいことがあるかもしれません。 +これは `Controller::render()` を直接呼び出すことで可能です。一度 `Controller::render()` +を呼び出すと、CakePHP は再度ビューを描画することはありません。 : + +``` php +namespace App\Controller; + +class PostsController extends AppController +{ + public function my_action() + { + return $this->render('custom_file'); + } +} +``` + +これは **templates/Posts/my_action.php** の代わりに +**templates/Posts/custom_file.php** を描画します。 + +また、次のような書式で、プラグイン内のビューを描画することもできます。 +`$this->render('PluginName.PluginController/custom_file')` 。 +例: + +``` php +namespace App\Controller; + +class PostsController extends AppController +{ + public function myAction() + { + return $this->render('Users.UserDetails/custom_file'); + } +} +``` + +これは **plugins/Users/templates/UserDetails/custom_file.php** を描画します。 + +## コンテンツタイプのネゴシエーション + +`method` Cake\\Controller\\Controller::**viewClasses**() + +コントローラーは、サポートするビュークラスの一覧を定義することができます。 +コントローラーのアクションが完了すると、 CakePHP はビューリストを使用して +content-typeのネゴシエーション(コンテントタイプの取り決め)を実行します。 +これにより、アプリケーションは同じコントローラーアクションを再利用してHTML ビューをレンダリングしたり +JSONやXML のレスポンスをレンダリングしたりすることができるようになります。 +コントローラーにサポートするビュークラスのリストを定義するには、 `viewClasses()` メソッドを使用します。: + +``` php +namespace App\Controller; + +use Cake\View\JsonView; +use Cake\View\XmlView; + +class PostsController extends AppController +{ + public function viewClasses() + { + return [JsonView::class, XmlView::class]; + } +} +``` + +アプリケーションの `View` クラスは、リクエストの `Accept` ヘッダーやルーティング拡張機能に基づいて、 +他のビューを選択できない場合に自動的にフォールバックとして使用されます。 +もしアプリケーションが異なるレスポンスフォーマットに対して異なるロジックを実行する必要がある場合は、 +`$this->request->is()` を使用して、必要な条件ロジックを構築することができます。 + +> [!NOTE] +> ビュークラスがcontent-typeのネゴシエーションに参加するには、 +> 静的な `contentType()` フックメソッドを実装する必要があります。 + +## コンテンツタイプネゴシエーションのフォールバック + +リクエストのコンテントタイプの設定にマッチする View がない場合、 +CakePHP はベースとなる `View` クラスを使用します。 +もし、content-typeのネゴシエーションを要求したい場合は、 +406ステータスコードを設定する `NegotiationRequiredView` を使用します。: + +``` php +public function viewClasses() +{ + // Acceptヘッダーのネゴシエーションを要求するか、406のレスポンスを返します。 + return [JsonView::class, NegotiationRequiredView::class]; +} +``` + +`TYPE_MATCH_ALL` のコンテンツタイプ値を使用して、独自のフォールバックビューロジックを構築することができます: + +``` php +namespace App\View; + +use Cake\View\View; + +class CustomFallbackView extends View +{ + public static function contentType(): string + { + return static::TYPE_MATCH_ALL; + } + +} +``` + +match-allビューは、content-typeのネゴシエーションが試みられた *後で* +のみ適用されることを覚えておくことが重要です。 + +## 他のページへのリダイレクト + +`method` Cake\\Controller\\Controller::**redirect**(string|array $url, integer $status) + +もっともよく使う、フロー制御のメソッドは `Controller::redirect()` です。 +このメソッドは最初の引数に、CakePHP の相対 URL を受け取ります。 +ユーザーが正常に注文を出した時、レシート画面にリダイレクトさせたいかもしれません。 : + +``` php +public function place_order() +{ + // 注文終了のためのロジック + if ($success) { + return $this->redirect( + ['controller' => 'Orders', 'action' => 'thanks'] + ); + } + + return $this->redirect( + ['controller' => 'Orders', 'action' => 'confirm'] + ); +} +``` + +このメソッドは適切なヘッダーが設定されたレスポンスのインスタンスを返します。 +ビューの描画を抑制し、ディスパッチャーが実際にリダイレクトを行えるようにするために +このレスポンスのインスタンスを return すべきです。 + +\$url 引数に相対 URL または絶対 URL を指定することもできます。 : + +``` php +return $this->redirect('/orders/thanks'); + +return $this->redirect('http://www.example.com'); +``` + +アクションにデータを渡すこともできます。 : + +``` php +return $this->redirect(['action' => 'edit', $id]); +``` + +`Controller::redirect()` の第二引数では、リダイレクトに伴う +HTTP ステータスコードを定義することができます。リダイレクトの性質によっては、 +301 (moved parmanently) または 303 (see other) を使ったほうが良いでしょう。 + +リファラーのページにリダイレクトする必要があれば、次のようにできます。 : + +``` php +return $this->redirect($this->referer()); +``` + +クエリー文字列とハッシュを使う例は次のようになります。 : + +``` php +return $this->redirect([ + 'controller' => 'Orders', + 'action' => 'confirm', + $order->id, + '?' => [ + 'product' => 'pizza', + 'quantity' => 5 + ], + '#' => 'top' +]); +``` + +生成される URL はこのようになります。 : + + http://www.example.com/orders/confirm?product=pizza&quantity=5#top + +### 同じコントローラーの他のアクションへの転送 + +`method` Cake\\Controller\\Controller::**setAction**($action, $args...) + +もし、現在のアクションを *同じ* コントローラーの異なるアクションにフォワードする必要があれば、 +リクエストオブジェクトを更新し、描画されるビューテンプレートを変更し、そして +指定のアクションに実行をフォワードするために `Controller::setAction()` を使用します。 : + +``` php +// delete アクションから、更新後の一覧ページを描画することができます。 +$this->setAction('index'); +``` + +## 追加のモデル読み込み + +`method` Cake\\Controller\\Controller::**loadModel**(string $modelClass, string $type) + +`loadModel` 関数は、コントローラーのデフォルト以外のモデルを使う必要がある時に便利です。 : + +``` php +// コントローラーのメソッドの中で。 +$this->loadModel('Articles'); +$recentArticles = $this->Articles->find('all', [ + 'limit' => 5, + 'order' => 'Articles.created DESC' +]); +``` + +もしも組み込み ORM 以外のテーブルプロバイダーを使いたければ、 +ファクトリーメソッドに接続することで、そのテーブルシステムを +CakePHP のコントローラーに紐づけることができます。 : + +``` php +// コントローラーのメソッドの中で。 +$this->modelFactory( + 'ElasticIndex', + ['ElasticIndexes', 'factory'] +); +``` + +テーブルファクトリーを登録した後は、インスタンスを読み出すために +`loadModel` を使うことができます。 : + +``` php +// コントローラーのメソッドの中で。 +$this->loadModel('Locations', 'ElasticIndex'); +``` + +> [!NOTE] +> 組み込みの ORM の TableRegistry は既定では 'Table' プロバイダーとして +> 接続されています。 + +## モデルのページネーション + +`method` Cake\\Controller\\Controller::**paginate**() + +このメソッドはモデルから取得した結果をページ分けするために使われます。 +ページサイズやモデルの検索条件などを指定できます。 +paginate() のより詳しい使い方は [ページネーション](controllers/pagination) +の章を参照してください。 + +`$paginate` 属性は `paginate()` がどうふるまうかを簡単にカスタマイズする方法を提供します。 : + +``` php +class ArticlesController extends AppController +{ + public $paginate = [ + 'Articles' => [ + 'conditions' => ['published' => 1] + ] + ]; +} +``` + +## コンポーネント読み込みの設定 + +`method` Cake\\Controller\\Controller::**loadComponent**($name, $config = []) + +コントローラーの `initialize()` メソッドの中で、読み込みたいコンポーネントや、 +その設定データを定義することができます。 : + +``` php +public function initialize(): void +{ + parent::initialize(); + $this->loadComponent('Csrf'); + $this->loadComponent('Comments', Configure::read('Comments')); +} +``` + +コントローラーの `$components` プロパティーでは、コンポーネントの設定ができます。 +設定されたコンポーネントとそれに依存するコンポーネントは CakePHP によって構築されます。 +より詳しい情報は [Configuring Components](controllers/components#configuring-components) の章を参照してください。先に述べたように、 +`$components` プロパティーはコントローラーの各親クラスで定義されたプロパティーとマージされます。 + +## ヘルパー読み込みの設定 + +追加で利用する MVC クラスをどうやって +CakePHP のコントローラーに伝えるのかを見てみましょう。 : + +``` php +class RecipesController extends AppController +{ + public $helpers = ['Form']; +} +``` + +これらの変数はそれぞれ、継承された値とマージされます。したがって、たとえば +`FormHelper` を、あるいは `AppController` で宣言されている他のクラスを、 +再度宣言する必要はありません。 + +::: info Deprecated in version 3.0 +コントローラーからのヘルパーの読み込みは後方互換のために提供しています。 ヘルパーをどう読み込むかについては [Configuring Helpers](views/helpers#configuring-helpers) を参照してください。 +::: + +## リクエストライフサイクルコールバック + +CakePHP のコントローラーはリクエストのライフサイクル周りにロジックを挿入できる +いくつかのイベント/コールバックを呼び出します。 + +### イベント一覧 + +- `Controller.initialize` +- `Controller.startup` +- `Controller.beforeRedirect` +- `Controller.beforeRender` +- `Controller.shutdown` + +### コントローラーのコールバックメソッド + +コントローラーでメソッドが実装されていれば、 +既定では以下のコールバックメソッドが関連するイベントに接続されます。 + +`method` Cake\\Controller\\Controller::**beforeFilter**(EventInterface $event) + +`method` Cake\\Controller\\Controller::**beforeRender**(EventInterface $event) + +`method` Cake\\Controller\\Controller::**afterFilter**(EventInterface $event) + +コントローラーのライフサイクルのコールバックに加えて、 [コンポーネント](controllers/components) +も似たようなコールバックの一式を提供します。 + +最良の結果を得るために、子コントローラーのコールバック中で +`AppController` のコールバックを呼ぶのを忘れないでください。 : + +``` php +//use Cake\Event\EventInterface; +public function beforeFilter(EventInterface $event) +{ + parent::beforeFilter($event); +} +``` + + + +## Controller Middleware + +`method` Cake\\Controller\\Controller::**middleware**($middleware, array $options = []) + +[Middleware](controllers/middleware) can be defined globally, in +a routing scope or within a controller. To define middleware for a specific +controller use the `middleware()` method from your controller's +`initialize()` method: + +``` php +public function initialize(): void +{ + parent::initialize(); + + $this->middleware(function ($request, $handler) { + // Do middleware logic. + + // Make sure you return a response or call handle() + return $handler->handle($request); + }); +} +``` + +Middleware defined by a controller will be called **before** `beforeFilter()` and action methods are called. + +## コントローラーのより詳細 + +- [ページコントローラー](controllers/pages-controller) +- [コンポーネント](controllers/components) diff --git a/docs/ja/controllers/components.md b/docs/ja/controllers/components.md new file mode 100644 index 0000000000..5df06b503f --- /dev/null +++ b/docs/ja/controllers/components.md @@ -0,0 +1,327 @@ +# コンポーネント + +コンポーネントはコントローラー間で共有されるロジックのパッケージです。 +CakePHP には、様々な共通のタスクを支援するための素晴らしいコアコンポーネントが +用意されています。あなた独自のコンポーネントも作成できます。 もしコントローラー間で +コピー&ペーストしたい箇所があった場合、その機能を含むコンポーネントの作成を +検討しましょう。コンポーネントを作成することで、コントローラーのコードを綺麗に保ち、 +プロジェクト間のコードの再利用につながります。 + +CakePHP の中に含まれるコンポーネントの詳細については、各コンポーネントの章を +チェックしてください。 + +- [フラッシュ](../controllers/components/flash) +- [リクエストハンドリング](../controllers/components/request-handling) +- [フォームの保護](../controllers/components/form-protection) +- [HTTPキャッシュの確認](../controllers/components/check-http-cache) + + + +## コンポーネントの設定 + +コアコンポーネントの多くは設定を必要としています。コンポーネントが設定を +必要としている例は、 [フォームの保護](../controllers/components/form-protection) や +などにあります。これらのコンポーネントや +一般的なコンポーネントの設定は、通常、お使いのコントローラーの `initialize()` +メソッド内で `loadComponent()` を使用するか、 `$components` 配列を介して行われます。 : + +``` php +class PostsController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('RequestHandler', [ + 'viewClassMap' => ['json' => 'AppJsonView'], + ]); + $this->loadComponent('Security', ['blackholeCallback' => 'blackhole']); + } + +} +``` + +`config()` メソッドを使用して、実行時にコンポーネントを設定することができます。 +しばしば、コントローラーの `beforeFilter()` メソッドで行われます。 +上記は、次のように表現することもできます。 : + +``` php +public function beforeFilter(EventInterface $event) +{ + $this->RequestHandler->setConfig('viewClassMap', ['rss' => 'MyRssView']); +} +``` + +コンポーネントは、ヘルパーと同じように、設定データを取得および設定するために使用されている +`getConfig()` と `setConfig()` メソッドを実装しています。 : + +``` php +// 設定データの読み込み +$this->RequestHandler->getConfig('viewClassMap'); + +// 設定をセット +$this->Csrf->setConfig('cookieName', 'token'); +``` + +コンポーネントは、ヘルパーと同じように、`config()` でアクセス可能な +`$_config` プロパティーを作成するためにコンストラクターの設定で自分の +`$_defaultConfig` プロパティーを自動的にマージします。 + +### コンポーネントの別名 + +共通設定の一つに `className` オプションがあります。このオプションを使うと +コンポーネントに別名をつけられます。この機能は `$this->Auth` や +他のコンポーネントの参照を独自実装に置き換えたい時に便利です。 : + +``` php +// src/Controller/PostsController.php +class PostsController extends AppController +{ + public function initialize(): void + { + $this->loadComponent('Auth', [ + 'className' => 'MyAuth' + ]); + } +} + +// src/Controller/Component/MyFlashComponent.php +use Cake\Controller\Component\FlashComponent; + +class MyFlashComponent extends FlashComponent +{ + // Add your code to override the core FlashComponent +} +``` + +上記の例ではコントローラーにて `MyFlushComponent` に `$this->Flash` という +*別名* をつけています。 + +> [!NOTE] +> 別名を付けられたコンポーネントはコンポーネントが使われるあらゆる場所の +> インスタンスを置き換えます。これは、他のコンポーネントの内部を含みます。 + +### コンポーネントの動的ロード + +すべてのコントローラーアクションで全コンポーネントを使えるようにする必要は +ないかもしれません。このような状況では、お使いのコントローラーで +`loadComponent()` メソッドを使用して、実行時にコンポーネントを +ロードすることができます。 : + +``` php +// コントローラーのアクションの中で +$this->loadComponent('OneTimer'); +$time = $this->OneTimer->getTime(); +``` + +> [!NOTE] +> 動的にロードされたコンポーネントはコールバックされないことに注意してください。 +> もし、 `beforeFilter` または `startup` コールバックに依存している場合、 +> あなたのコンポーネントをロードするときに手動でそれらを呼び出す必要があります。 + +## コンポーネントの使用 + +一旦、コンポーネントをコントローラーに読込んでしまえば、使うのは非常に簡単です。 +使用中の各コンポーネントはコントローラーのプロパティーのように見えます。 +もし、 `Cake\Controller\Component\FlashComponent` を +コントローラーに読込んだ場合、以下のようにアクセスすることができます。 : + +``` php +class PostsController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('Flash'); + } + + public function delete() + { + if ($this->Post->delete($this->request->getData('Post.id')) { + $this->Flash->success('Post deleted.'); + + return $this->redirect(['action' => 'index']); + } + } +``` + +> [!NOTE] +> モデルとコンポーネントの両方がプロパティーとしてコントローラーに追加されているので、 +> それらは同じ「名前空間」を共有しています。 +> コンポーネントとモデルに同じ名前を付けないように注意してください。 + +## コンポーネントの作成 + +アプリケーションの様々な箇所で複雑な数学的処理を必要としている +オンラインアプリケーションを仮定して下さい。 +これから、コントローラーの様々な箇所で使うための共有ロジックを集約するための +コンポーネントを作成します。 + +はじめに、新しいコンポーネントファイルとクラスを作成します。 +**src/Controller/Component/MathComponent.php** にファイルを作成します 。 +コンポーネントのための基本的な構造は次のようになります。 : + +``` php +namespace App\Controller\Component; + +use Cake\Controller\Component; + +class MathComponent extends Component +{ + public function doComplexOperation($amount1, $amount2) + { + return $amount1 + $amount2; + } +} +``` + +> [!NOTE] +> すべてのコンポーネントは `Cake\Controller\Component` を +> 継承しなければなりません。継承されていない場合、例外が発生します。 + +### コントローラーの中にコンポーネントを読み込む + +一旦コンポーネントが完成してしまえば、コントローラーの `initialize()` メソッド中で +それをロードすることによって、アプリケーションのコントローラーで使用することができます。 +ロードされた後、コントローラーはそのコンポーネントに由来する名前の新しいプロパティーを与えられ、 +そのプロパティーを通してコンポーネントのインスタンスにアクセスできます。 : + +``` php +// コントローラーの中で +// 標準の $this->Csrf と同様に +// 新しいコンポーネントを $this->Math として利用可能にします。 +public function initialize(): void +{ + parent::initialize(); + $this->loadComponent('Math'); + $this->loadComponent('Csrf'); +} +``` + +コントローラーの中でコンポーネントを読み込む時、コンポーネントのコンストラクターに渡す +バラメータを宣言することもできます。 +このパラメーターはコンポーネントによって処理することができます。 : + +``` php +// コントローラーの中で +public function initialize(): void +{ + parent::initialize(); + $this->loadComponent('Math', [ + 'precision' => 2, + 'randomGenerator' => 'srand' + ]); + $this->loadComponent('Csrf'); +} +``` + +上記の例では precision と randomGenerator を含む配列が +`MathComponent::initialize()` の `$config` パラメーターに渡されます。 + +### コンポーネントの中で他のコンポーネントを使用する + +作成しているコンポーネントから他のコンポーネントを使いたい時がたまにあります。 +その場合、作成中のコンポーネントから他のコンポーネントを読み込むことができ、 +その方法はコントローラーから `$components` 変数を使って読み込む場合と同じです。 : + +``` php +// src/Controller/Component/CustomComponent.php +namespace App\Controller\Component; + +use Cake\Controller\Component; + +class CustomComponent extends Component +{ + // あなたのコンポーネントが使っている他のコンポーネント + public $components = ['Existing']; + + // あなたのコンポーネントに必要な、その他の追加のセットアップを実行 + public function initialize(array $config): void + { + $this->Existing->foo(); + } + + public function bar() + { + // ... + } +} + +// src/Controller/Component/ExistingComponent.php +namespace App\Controller\Component; + +use Cake\Controller\Component; + +class ExistingComponent extends Component +{ + public function foo() + { + // ... + } +} +``` + +> [!NOTE] +> コントローラーから読み込んだコンポーネントと違い、コンポーネントから +> コンポーネントを読み込んだ場合は、コールバックが呼ばれないことに注意して下さい。 + +### コンポーネントのコントローラーへのアクセス + +コンポーネント内から、\_registry を介して現在のコントローラーに +アクセスすることができます。 : + +``` php +$controller = $this->getController(); +``` + +## コンポーネントのコールバック + +また、コンポーネントは、リクエストサイクルを増強することができる、 +いくつかのリクエストライフサイクルコールバックを提供しています。 + +`method` Class::**beforeFilter**(EventInterface $event) + +`method` Class::**startup**(EventInterface $event) + +`method` Class::**beforeRender**(EventInterface $event) + +`method` Class::**shutdown**(EventInterface $event) + +`method` Class::**beforeRedirect**(EventInterface $event, $url, Response $response) + +## コンポーネントイベントでのリダイレクトの使用 + +コンポーネントのコールバックメソッド内からリダイレクトするには、次のようにします。 : + +``` php +public function beforeFilter(EventInterface $event) +{ + $event->stopPropagation(); + + return $this->getController()->redirect('/'); +} +``` + +イベントを停止することで、CakePHPに他のコンポーネントのコールバックを実行させたくないこと、 +そしてコントローラがこれ以上アクションを処理してはいけないことを知らせます。 +As of 4.1.0 you can raise a `RedirectException` to signal +a redirect: + +``` php +use Cake\Http\Exception\RedirectException; +use Cake\Routing\Router; + +public function beforeFilter(EventInterface $event) +{ + throw new RedirectException(Router::url('/')) +} +``` + +Raising an exception will halt all other event listeners and create a new +response that doesn't retain or inherit any of the current response's headers. +When raising a `RedirectException` you can include additional headers: + +``` php +throw new RedirectException(Router::url('/'), 302, [ + 'Header-Key' => 'value', +]); +``` diff --git a/docs/ja/controllers/components/check-http-cache.md b/docs/ja/controllers/components/check-http-cache.md new file mode 100644 index 0000000000..62495b231c --- /dev/null +++ b/docs/ja/controllers/components/check-http-cache.md @@ -0,0 +1,30 @@ +# HTTPキャッシュの確認 + +`class` **CheckHttpCacheComponent**(ComponentCollection $collection, array $config = []) + +HTTPキャッシュ検証モデルは、リバースプロキシとしても知られるキャッシュゲートウェイが、 +保存されたレスポンスのコピーをクライアントに提供できるかどうかを判断するための処理の1つです。 +このモデルでは、主に帯域幅を節約することができますが、 +正しく使用すればCPU処理も節約でき、レスポンスタイムを短縮することができます。: + +``` php +// in a Controller +public function initialize(): void +{ + parent::initialize(); + + $this->addComponent('CheckHttpCache'); +} +``` + +コントローラーで `CheckHttpCacheComponent` を有効にすると、自動的に `beforeRender` チェックが有効になります。 +このチェックでは、レスポンスオブジェクトに設定されたキャッシュヘッダと、リクエストで送信されたキャッシュヘッダを比較し、 +クライアントが最後にレスポンスを要求したときから変更されていないかどうかを判断します。 +以下のリクエストヘッダが使用されます。: + +- `If-None-Match` はレスポンスの `Etag` ヘッダーと比較されます。 +- `If-Modified-Since` はレスポンスの `Last-Modified` ヘッダーと比較されます。 + +レスポンスヘッダがリクエストヘッダの基準と一致する場合、 ビューのレンダリングはスキップされます。 +これにより、アプリケーションがビューを生成する手間が省け、帯域幅と時間を節約することができます。 +レスポンスヘッダが一致した場合、空のレスポンスが `304 Not Modified` というステータスコードとともに返されます。 diff --git a/docs/ja/controllers/components/flash.md b/docs/ja/controllers/components/flash.md new file mode 100644 index 0000000000..89d68ba775 --- /dev/null +++ b/docs/ja/controllers/components/flash.md @@ -0,0 +1,96 @@ +# フラッシュ + +`class` Cake\\Controller\\Component\\**FlashComponent**(ComponentCollection $collection, array $config = []) + +FlashComponent は、フォームの処理後やデータの確認のために表示する一回限りのメッセージ通知に +使用します。このような通知を CakePHP では「フラッシュメッセージ」と呼んでいます。 +FlashComponent はフラッシュメッセージを `$_SESSION` に書き込みます。そして +[FlashHelper](../../views/helpers/flash) を使ってビューで表示します。 + +## フラッシュメッセージの設定 + +FlashComponent は、フラッシュメッセージの設定に2つの方法を用意しています。 +ひとつは、 `__call()` マジックメソッドで、もうひとつは `set()` メソッドです。 +アプリケーションをより様々な表現を用いて利用するためには、 FlashComponent の `__call()` +マジックメソッドを利用することで **templates/element/flash** ディレクトリー以下に配置された +エレメントをマッピングしたメソッド名を使用することができます。規約により、キャメルケース形式の +メソッドは、小文字でアンダースコアー区切りのエレメント名に置き換えられます。 : + +``` php +// templates/element/flash/success.php を使用 +$this->Flash->success('This was successful'); + +// templates/element/flash/great_success.php を使用 +$this->Flash->greatSuccess('This was greatly successful'); +``` + +あるいは、エレメントをレンダリングしないで平文メッセージを設定するためには、 +`set()` メソッドを用いることができます。 : + +``` php +$this->Flash->set('This is a message'); +``` + +FlashComponent の `__call()` および `set()` メソッドは任意に第2引数をとります、 +その配列のオプションは以下の通りです。 + +- `key` デフォルトは ‘flash’。セッション内の ‘Flash’ キー配下の配列キー。 +- `element` デフォルトは null ですが、 `__call()` マジックメソッドの使用時には、 + 自動的に設定されます。表示に使用されるエレメント名。 +- `params` キーバリューの任意の配列です。エレメントの中で変数として利用する配列。 + +オプションの使用例: + +``` php +// コントローラーの中で +$this->Flash->success('The user has been saved', [ + 'key' => 'positive', + 'clear' => true, + 'params' => [ + 'name' => $user->name, + 'email' => $user->email + ] +]); + +// ビューの中で +Flash->render('positive') ?> + + +
    + : , . +
    +``` + +`__call()` マジックメソッドを使用している時、 `element` オプションは、常に置き換えられます。 +プラグインから指定したエレメントを取得するためには、 `plugin` パラメーターをセットしてください。 +例: + +``` php +// コントローラーの中で +$this->Flash->warning('My message', ['plugin' => 'PluginName']); +``` + +上記のコードは **plugins/PluginName/templates/element/flash** 配下の +**warning.php** エレメントを使用しています。 + +> [!NOTE] +> デフォルトでは、CakePHP は、クロスサイトスクリプティング防止のためにフラッシュメッセージ中の +> 内容はエスケープします。フラッシュメセージ中のユーザーデータは、HTML エンコードされ、 +> 安全に表示されます。もし、フラッシュメッセージの中に HTML を含めたい場合、 +> `escape` オプションをセットする必要があり、escape オプションがセットされた際に、 +> エスケープを無効にするようにフラッシュメッセージのテンプレートを調整してください。 + +## フラッシュメッセージ内の HTML + +`'escape'` オプションキーを使用することでフラッシュメッセージ中に +HTML を出力することができます。 : + +``` php +$this->Flash->info(sprintf('%s %s', h($highlight), h($message)), ['escape' => false]); +``` + +手動で入力をエスケープされることを確かめてください。上記の例では、 +`$highlight` と `$message` は、非 HTML な入力で、エスケープされます。 + +フラッシュメッセージの表示に関する詳しい情報は、 [FlashHelper](../../views/helpers/flash) +セクションをご覧ください。 diff --git a/docs/ja/controllers/components/form-protection.md b/docs/ja/controllers/components/form-protection.md new file mode 100644 index 0000000000..805fb910d4 --- /dev/null +++ b/docs/ja/controllers/components/form-protection.md @@ -0,0 +1,156 @@ +# フォームの保護 + +`class` **FormProtection**(ComponentCollection $collection, array $config = []) + +フォーム保護コンポーネントは、フォームデータの改ざんから保護する仕組みを提供します。 + +すべてのコンポーネントと同様に、いくつかの設定可能なパラメータを使用して設定します。 +これらのプロパティはすべて直接設定するか、コントローラーの `initialize()` メソッドや +`beforeFilter()` メソッドの中にある同じ名前のセッターメソッドを使って設定することができます。 + +もし、`startup()` コールバックでフォームデータを処理する他のコンポーネントを使用している場合は、 +`initialize()` メソッドの中で、必ず FormProtection Component を +それらのコンポーネントの前に置いてください。 + +> [!NOTE] +> FormProtectionコンポーネントを使用するときは、\*\*必ず\*\* FormHelperを使用して +> フォームを作成してください。また、フィールドの「名前」属性を上書き **してはいけません** 。 +> FormProtection コンポーネントは、FormHelper によって作成・管理される特定の指標を探します。 +> (特に `Cake\View\Helper\FormHelper::create()` と +> `Cake\View\Helper\FormHelper::end()` で作成されたもの) +> +> POST リクエストで送信される動的なフィールド変更 +> (JavaScriptによる無効化、削除、新規フィールドの作成など) +> を使用すると、フォームトークンのバリデーションに失敗する可能性が高くなります。 + +## フォームの改ざん防止 + +デフォルトでは `FormProtectionComponent` はユーザが特定の方法で +フォームを改ざんすることを防ぎます。例えば、次のようなことを防ぐことができます。 + +- フォームのアクション(URL)は変更できません。 +- 未知のフィールドをフォームに追加することはできません。 +- フォームからフィールドを削除することはできません。 +- 隠し入力の値を変更することはできません。 + +このようなタイプの改ざんを防ぐには `FormHelper` を使って +どのフィールドがフォームにあるかを追跡することで可能になります。 +隠されたフィールドの値も追跡されます。これらのデータはすべて結合されてハッシュ化され、 +隠されたトークンフィールドは自動的にフォームに挿入されます。 +フォームが送信されると、`FormProtectionComponent` は +POSTデータを使って同じ構造を作り、ハッシュを比較します。 + +> [!NOTE] +> FormProtectionComponent はセレクトボックスのオプションの追加や変更を防ぎ **\*ません**\* 。 +> また、ラジオボタンのオプションの追加や変更も防ぎません。 + +## 使用方法 + +セキュリティコンポーネントの設定は通常コントローラーの +`initialize()` または `beforeFilter()` コールバックで行われます。 + +利用可能なオプションは以下の通りです。 : + +validate +`false` に設定すると、POSTリクエストのバリデーションを完全にスキップし、 +本質的にフォームのバリデーションをオフにします。 + +unlockedFields +POSTのバリデーションから除外するフォームフィールドのリストを設定します。 +フィールドのロックを解除するには、コンポーネントで行うか +`FormHelper::unlockField()` を使用します。 +ロックが解除されたフィールドは POST の一部である必要はありませんし、 +ロックが解除されていない非表示のフィールドの値はチェックされません。 + +unlockedActions +POSTのバリデーションチェックから除外するアクションを設定します。 + +validationFailureCallback +バリデーションに失敗した場合に呼び出すコールバックを指定します。 +有効なClosureでなければなりません。 +デフォルトでは未設定で、検証に失敗した場合は例外がスローされます。 + +## フォームの改ざんチェックを無効にする + +``` php +namespace App\Controller; + +use App\Controller\AppController; +use Cake\Event\EventInterface; + +class WidgetsController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + + $this->loadComponent('FormProtection'); + } + + public function beforeFilter(EventInterface $event) + { + parent::beforeFilter($event); + + if ($this->request->getParam('prefix') === 'Admin') { + $this->FormProtection->setConfig('validate', false); + } + } +} +``` + +上記の例では、管理者用プレフィックス付きルートのフォーム改ざん防止機能を無効にしています。 + +## 特定のアクションのためにフォームの改ざんを無効にする + +アクションに対してフォームの改ざん防止を無効にしたい場合があるかもしれません。 +(例えばAJAXリクエストの場合) +これらのアクションを `beforeFilter()` 内の `$this->Security->unlockedActions` +にリストアップすることで「ロックを解除」することができます。 : + +``` php +namespace App\Controller; + +use App\Controller\AppController; +use Cake\Event\EventInterface; + +class WidgetController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('FormProtection'); + } + + public function beforeFilter(EventInterface $event) + { + parent::beforeFilter($event); + + $this->FormProtection->setConfig('unlockedActions', ['edit']); + } +} +``` + +この例では、編集アクションのすべてのセキュリティチェックを無効にします。 + +## コールバックによる検証失敗の処理 + +フォーム保護の検証に失敗した場合、デフォルトでは400エラーになります。 +この動作は `validationFailureCallback` 設定オプションを +コントローラーのコールバック関数に設定することで設定できます。 + +コールバックメソッドを設定することで、失敗処理の動作をカスタマイズすることができます。 : + +``` php +public function beforeFilter(EventInterface $event) +{ + parent::beforeFilter($event); + + $this->FormProtection->setConfig( + 'validationFailureCallback', + function (BadRequestException $exception) { + // You can either return a response instance or throw the exception + // received as argument. + } + ); +} +``` diff --git a/docs/ja/controllers/components/request-handling.md b/docs/ja/controllers/components/request-handling.md new file mode 100644 index 0000000000..8d5094dbf5 --- /dev/null +++ b/docs/ja/controllers/components/request-handling.md @@ -0,0 +1,161 @@ +# リクエストハンドリング + +`class` **RequestHandlerComponent**(ComponentCollection $collection, array $config = []) + +RequestHandler コンポーネントは、 アプリケーションに対する HTTP リクエストについての追加情報を +取得するために CakePHP で使用されています。それは、クライアントが好むコンテンツタイプが何かを知り、 +自動的にリクエストの入力を解析し、コンテンツタイプをビュークラスやテンプレートのパスとマップする方法を +定義することができます。 + +RequestHandler は初期状態で、多くの JavaScript ライブラリーが使用している `X-Requested-With` +ヘッダーに基づいた AJAX リクエストを自動的に判定します。 +`Cake\Routing\Router::extensions()` と組み合わせて使用することで、 +RequestHandler は、自動的に HTML 以外のメディアタイプに対応したレイアウトとテンプレートファイルを +切り替えます。さらに、リクエストの拡張子と同じ名前のヘルパーが存在する場合、コントローラーのヘルパーの +設定をする配列に加えます。最後に、 XML/JSON データをコントローラーへ POST した場合、自動的に解析され +`$this->request->getData()` 配列に割り当てられ、モデルデータとして保存可能です。 +RequestHandler を利用するためには `initialize()` メソッドに含めてください。 : + +``` php +class WidgetsController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('RequestHandler'); + } + + // 以下省略 +} +``` + +## リクエスト情報の取得 + +RequestHandler はクライアントやリクエストについての情報を提供するいくつかのメソッドがあります。 + +`method` RequestHandlerComponent::**accepts**($type = null) + +リクエスト「型」を検出する他のメソッドは、次のとおりです。 + +`method` RequestHandlerComponent::**isXml**() + +`method` RequestHandlerComponent::**isRss**() + +`method` RequestHandlerComponent::**isAtom**() + +`method` RequestHandlerComponent::**isMobile**() + +`method` RequestHandlerComponent::**isWap**() + +上記の全ての検出メソッドは、特定のコンテンツタイプを対象にしたフィルター機能と同様の方法で使用できます。 +例えば、 AJAX のリクエストに応答するときには、ブラウザーのキャッシュを無効にして、デバッグレベルを +変更したいでしょう。ただし、非 AJAX リクエストのときは反対にキャッシュを許可したいと思います。 +そのようなときは次のようにします。 : + +``` php +if ($this->request->is('ajax')) { + $this->response->disableCache(); +} +// コントローラーのアクションの続き +``` + +## リクエストデータの自動デコード + +この機能はバージョン4.0において `RequestHandlerComponent` から削除されました。 +代わりに [Body Parser Middleware](../../controllers/middleware#body-parser-middleware) を使用してください。 + +## コンテンツタイプの設定を確認 + +`method` RequestHandlerComponent::**prefers**($type = null) + +クライアントが好むコンテンツタイプを判定します。 +パラメーターを省略した場合は、最も可能性の高いコンテンツタイプが返されます。 +\$type を配列で渡した場合、クライアントが受け付けるものとマッチした最初の値が返されます。 +設定はまず、もし Router で解析されたファイルの拡張子により確定されます。 +次に、 `HTTP_ACCEPT` にあるコンテンツタイプのリストから選ばれます。 : + +``` php +$this->RequestHandler->prefers('json'); +``` + +## リクエストへの応答 + +`method` RequestHandlerComponent::**renderAs**($controller, $type) + +指定した型に、コントローラーの出力モードを変更します。適切なヘルパーが存在し、 +それがコントローラー中のヘルパー配列で指定されていなければ、これを追加します。 : + +``` php +// コントローラーに xml レスポンスの出力を強制。 +$this->RequestHandler->renderAs($this, 'xml'); +``` + +このメソッドは、現在のコンテンツタイプに一致するヘルパーを追加しようとします。 +例えば、 `rss` として出力する場合、 `RssHelper` が追加されます。 + +`method` RequestHandlerComponent::**respondAs**($type, $options) + +コンテンツタイプにマップした名前に基づき、応答するヘッダーをセットします。 +このメソッドは、一度に多くのレスポンスプロパティーを設定することができます。 : + +``` php +$this->RequestHandler->respondAs('xml', [ + // ダウンロードを強制 + 'attachment' => true, + 'charset' => 'UTF-8' +]); +``` + +`method` RequestHandlerComponent::**responseType**() + +現在の応答するコンテンツタイプのヘッダーをの型を返します。もしセットされていなければ null を返します。 + +## HTTP キャッシュバリデーションの活用 + +HTTP キャッシュバリデーションモデルは、クライアントへのレスポンスにコピーを使用するかどうかを +判断する(リバースプロキシーとして知られる)キャッシュゲートウェイを使用する処理です。 +このモデルでは、主に帯域幅を節約しますが、正しく使用することで応答時間の短縮や、いくつかの +CPU の処理を節約することができます。 + +コントローラーで RequestHandlerComponent を有効化すると、ビューが描画される前に、自動的に +チェックを行います。このチェックでは、前回クライアントが要求してからレスポンスに変更がないかを +判断するため、レスポンスオブジェクトと元のリクエストを比較します。 + +レスポンスが変更無いと見なされる場合、ビューの描画処理は行われず、クライアントには何も返さず +処理時間を短縮、帯域幅を節約します。レスポンスステータスコードは `304 Not Modified` +にセットされます。 + +自動的なチェックは、 `checkHttpCache` を `false` にすることで +行わないようにすることができます。 : + +``` php +public function initialize(): void +{ + parent::initialize(); + $this->loadComponent('RequestHandler', [ + 'checkHttpCache' => false + ]); +} +``` + +## カスタムビュークラスの利用 + +JsonView/XmlView を利用する場合、カスタムビュークラスでデフォルトのシリアライズ方法を上書きしたり、 +独自のカスタムクラスを追加したい場合があるでしょう。 + +その場合、既存のタイプや新規タイプのクラスをマッピングすることができます。 +また、 `viewClassMap` 設定を使用して、これを自動的に設定することができます。 : + +``` php +public function initialize(): void +{ + parent::initialize(); + $this->loadComponent('RequestHandler', [ + 'viewClassMap' => [ + 'json' => 'ApiKit.MyJson', + 'xml' => 'ApiKit.MyXml', + 'csv' => 'ApiKit.Csv' + ] + ]); +} +``` diff --git a/docs/ja/controllers/middleware.md b/docs/ja/controllers/middleware.md new file mode 100644 index 0000000000..c5a902ac20 --- /dev/null +++ b/docs/ja/controllers/middleware.md @@ -0,0 +1,288 @@ +# ミドルウェア + +ミドルウェアオブジェクトは、再利用可能で構成可能なリクエスト処理、あるいは +レスポンス構築処理の層でアプリケーションを「ラップ」する機能を提供します。 +視覚的には、アプリケーションは中央で終了し、ミドルウェアはタマネギのように +アプリの周囲を包み込みます。ここでは、Routes、Assets、例外処理、および +CORS ヘッダーミドルウェアでラップされたアプリケーションを確認できます。 + +![image](/middleware-setup.png) + +アプリケーションによってリクエストが処理されると、最も外側のミドルウェアから +リクエストが入力されます。各ミドルウェアは、リクエスト/レスポンスを次のレイヤーに委譲するか、 +またはレスポンスを返すことができます。レスポンスを返すことで、下位層がリクエストを見なくなります。 +たとえば、開発中にプラグインの画像のリクエストを処理する AssetMiddleware がその例です。 + +![image](/middleware-request.png) + +ミドルウェアがリクエストを処理するアクションを受け取らない場合、コントローラーが用意され、 +そのアクションを呼び出すか、例外が発生してエラーページが生成されます。 + +ミドルウェアは CakePHP における新しい HTTP スタックの部分で、 PSR-7 のリクエスト +およびレスポンスのインターフェイスを活用しています。 PSR-7 標準の活用によって、 +[Packagist](https://packagist.org) で利用可能な、あらゆる PSR-7 互換の +ミドルウェアを使うことができます。 + +## CakePHP のミドルウェア + +CakePHP はウェブアプリケーションの一般的なタスクを処理するいくつかのミドルウェアを提供します。 + +- `Cake\Error\Middleware\ErrorHandlerMiddleware` はラップされたミドルウェアからくる + 例外を捕まえ、 [エラーと例外の処理](../development/errors) の例外ハンドラーを使ってエラーページを描画します。 +- `Cake\Routing\AssetMiddleware` はリクエストが、プラグインの webroot フォルダー + あるいはテーマのそれに格納された CSS 、 JavaScript または画像ファイルといった、 + テーマまたはプラグインのアセットファイルを参照するかどうかを確認します。 +- `Cake\Routing\Middleware\RoutingMiddleware` は受け取った URL を解析して、 + リクエストにルーティングパラメーターを割り当てるために `Router` を使用します。 +- `Cake\I18n\Middleware\LocaleSelectorMiddleware` はブラウザーによって送られる + `Accept-Language` ヘッダーによって自動で言語を切り替えられるようにします。 +- `Cake\Http\Middleware\HttpsEnforcerMiddleware` の利用はHTTPSを要求します。 +- `Cake\Http\Middleware\SecurityHeadersMiddleware` は、 `X-Frame-Options` + のようなセキュリティに関連するヘッダーをレスポンスに簡単に追加することができます。 +- `Cake\Http\Middleware\EncryptedCookieMiddleware` は、難読化されたデータで + Cookie を操作する必要がある場合に備えて、暗号化された Cookie を操作する機能を提供します。 +- `Cake\Http\Middleware\CsrfProtectionMiddleware` は、アプリケーションに CSRF + 保護を追加します。 +- `Cake\Http\Middleware\BodyParserMiddleware` は `Content-Type` ヘッダーに基づいて + JSON, XML, その他のエンコードされたリクエストボディーをデコードすることができます。 +- `Cake\Http\Middleware\CspMiddleware` を使用すると、 + アプリケーションに Content-Security-Policy ヘッダを追加するのがより簡単になります。 + +## ミドルウェアの使用 + +ミドルウェアは、アプリケーションの全体、または個々のルーティングスコープに適用できます。 + +全てのリクエストにミドルウェアを適用するには、 `App\Application` クラスの +`middleware` メソッドを使用してください。 +アプリケーションの `middleware` フックメソッドはリクエスト処理の開始時に呼ばれて、 +`MiddlewareQueue` オブジェクトを加えることができます。 : + +``` php +namespace App; + +use Cake\Core\Configure; +use Cake\Error\Middleware\ErrorHandlerMiddleware; +use Cake\Http\BaseApplication; +use Cake\Http\MiddlewareQueue; + +class Application extends BaseApplication +{ + public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue + { + // ミドルウェアのキューにエラーハンドラーを結びつけます。 + $middlewareQueue->add(new ErrorHandlerMiddleware(Configure::read('Error'), $this)); + + return $middlewareQueue; + } +} +``` + +`MiddlewareQueue` の末尾に追加するだけではなくて、さまざまな操作をすることができます。 : + +``` php +$layer = new \App\Middleware\CustomMiddleware; + +// 追加されたミドルウェアは行列の末尾になります。 +$middlewareQueue->add($layer); + +// 追加されたミドルウェアは行列の先頭になります。 +$middlewareQueue->prepend($layer); + +// 特定の位置に挿入します。もし位置が範囲外の場合、 +// 末尾に追加されます。 +$middlewareQueue->insertAt(2, $layer); + +// 別のミドルウェアの前に挿入します。 +// もしその名前のクラスが見つからない場合、 +// 例外が発生します。 +$middlewareQueue->insertBefore( + 'Cake\Error\Middleware\ErrorHandlerMiddleware', + $layer +); + +// 別のミドルウェアの後に挿入します。 +// もしその名前のクラスが見つからない場合、 +// ミドルウェアは末尾に追加されます。 +$middlewareQueue->insertAfter( + 'Cake\Error\Middleware\ErrorHandlerMiddleware', + $layer +); +``` + +If your middleware is only applicable to a subset of routes or individual +controllers you can use [Route scoped middleware](../development/routing#route-scoped-middleware), +or [Controller middleware](../controllers#controller-middleware). + +### プラグインからのミドルウェア追加 + +プラグインは `middleware` フックメソッドを使って、 +ミドルウェアをアプリケーションのミドルウェアキューに適用することができます。 : + +``` php +// ContactManager プラグインの bootstrap.php の中で +namespace ContactManager; + +use Cake\Core\BasePlugin; +use Cake\Http\MiddlewareQueue; +use ContactManager\Middleware\ContactManagerContextMiddleware; + +class Plugin extends BasePlugin +{ + public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue + { + $middlewareQueue->add(new ContactManagerContextMiddleware()); + + return $middlewareQueue; + } +} +``` + +## ミドルウェアの作成 + +ミドルウェアは無名関数(クロージャ)として、あるいは呼び出し可能なクラスとしても実装できます。 +また、 `Psr\Http\ServerMiddlewareInterface` を拡張するクラスとして実装することもできます。 +クロージャは小さな課題に適している一方で、テストを行うのを難しくしますし、複雑な `Application` +クラスを作ってしまいます。 CakePHP のミドルウェアクラスは、いくつかの規約を持っています。 + +- ミドルウェアクラスのファイルは **src/Middleware** に置かれるべきです。例えば + **src/Middleware/CorsMiddleware.php** です。 +- ミドルウェアクラスには `Middleware` と接尾語が付けられるべきです。例えば + `LinkMiddleware` です。 +- ミドルウェアは、 `Psr\Http\ServerMiddlewareInterface`\` を実装しなければなりません。 + +ミドルウェアは `$handler->handle()` を呼ぶか、独自のレスポンスを作成することによって、レスポンスを +返すことができます。我々の単純なミドルウェアで、両方のオプションを見ることができます。 : + +``` php +// src/Middleware/TrackingCookieMiddleware.php の中で +namespace App\Middleware; + +use Cake\Http\Cookie\Cookie; +use Cake\I18n\Time; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Psr\Http\Server\MiddlewareInterface; + +class TrackingCookieMiddleware implements MiddlewareInterface +{ + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface + { + // Calling $handler->handle() を呼ぶことで、アプリケーションのキューの中で + // *次の* ミドルウェアにコントロールを任せます。 + $response = $handler->handle($request); + + if (!$request->getCookie('landing_page')) { + $expiry = new Time('+ 1 year'); + $response = $response->withCookie(new Cookie( + 'landing_page', + $request->getRequestTarget(), + $expiry + )); + } + + return $response; + } +} +``` + +さて、我々はごく単純なミドルウェアを作成しましたので、それを我々のアプリケーションに +加えてみましょう。 : + +``` php +// src/Application.php の中で +namespace App; + +use App\Middleware\TrackingCookieMiddleware; +use Cake\Http\MiddlewareQueue; + +class Application +{ + public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue + { + // 単純なミドルウェアをキューに追加します + $middlewareQueue->add(new TrackingCookieMiddleware()); + + // もう少しミドルウェアをキューに追加します + + return $middlewareQueue; + } +} +``` + +## ルーティングミドルウェア + +ルーティングミドルウェアは、アプリケーションのルートの適用や、リクエストが実行するプラグイン、 +コントローラー、アクションを解決することができます。起動時間を向上させるために、 +アプリケーションで使用されているルートコレクションをキャッシュすることができます。 +キャッシュされたルートを有効にするために、目的の [キャッシュ設定](../core-libraries/caching#cache-configuration) +をパラメーターとして指定します。 : + +``` php +// Application.php の中で +public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue +{ + // ... + $middlewareQueue->add(new RoutingMiddleware($this, 'routing')); +} +``` + +上記は、生成されたルートコレクションを格納するために `routing` キャッシュエンジンを使用します。 + +## クッキー暗号化ミドルウェア + +アプリケーションが難読化してユーザーの改ざんから保護したいデータを含むクッキーがある場合、 +CakePHP のクッキー暗号化ミドルウェアを使用して、ミドルウェア経由でクッキーデータを透過的に +暗号化や復号化することができます。 クッキーデータは、OpenSSL 経由で AES を使用して +暗号化されます。 : + +``` php +use Cake\Http\Middleware\EncryptedCookieMiddleware; + +$cookies = new EncryptedCookieMiddleware( + // 保護するクッキーの名前 + ['secrets', 'protected'], + Configure::read('Security.cookieKey') +); + +$middlewareQueue->add($cookies); +``` + +> [!NOTE] +> クッキーデータで使用する暗号化キーは、クッキーデータ *のみ* に使用することを +> お勧めします。 + +このミドルウェアが使用する暗号化アルゴリズムとパディングスタイルは、 +CakePHP の以前のバージョンの `CookieComponent` と後方互換性があります。 + +## ボディパーサミドルウェア + +アプリケーションが JSON、XML、またはその他のエンコードされたリクエストボディを受け入れる場合、 +`BodyParserMiddleware` を使用すると、それらのリクエストを配列にデコードして、 +`$request->getParsedData()` および `$request->getData()` で利用可能です。 +デフォルトでは `json` ボディのみがパースされますが、オプションでXMLパースを有効にすることができます。 +独自のパーサーを定義することもできます。 : + +``` php +use Cake\Http\Middleware\BodyParserMiddleware; + +// JSONのみがパースされます。 +$bodies = new BodyParserMiddleware(); + +// XMLパースを有効にする +$bodies = new BodyParserMiddleware(['xml' => true]); + +// JSONパースを無効にする +$bodies = new BodyParserMiddleware(['json' => false]); + +// content-type ヘッダーの値にマッチする独自のパーサを +// それらをパース可能な callable に追加します。 +$bodies = new BodyParserMiddleware(); +$bodies->addParser(['text/csv'], function ($body, $request) { + // Use a CSV parsing library. + return Csv::parse($body); +}); +``` diff --git a/docs/ja/controllers/pages-controller.md b/docs/ja/controllers/pages-controller.md new file mode 100644 index 0000000000..b83cad7f97 --- /dev/null +++ b/docs/ja/controllers/pages-controller.md @@ -0,0 +1,11 @@ +# ページコントローラー + +CakePHP の公式スケルトンアプリは、デフォルトコントローラー **PagesController.php** を提供しています。 +これは静的なコンテンツを提供するためのシンプルで補助的なコントローラーです。 +インストール後のホームページはこのコントローラーとビューファイル **templates/Pages/home.php** +を利用して作成されました。もし **templates/Pages/about_us.php** というビューファイルを作成すれば、 +この URL **http://example.com/pages/about_us** でアクセスできます。 +ページコントローラーは必要に応じて気軽に修正できます。 + +コンポーザを利用してアプリを "bake" すると、ページコントローラーが **src/Controller/** +フォルダー以下に作成されます。 diff --git a/docs/ja/controllers/pagination.md b/docs/ja/controllers/pagination.md new file mode 100644 index 0000000000..1a7e7d93cb --- /dev/null +++ b/docs/ja/controllers/pagination.md @@ -0,0 +1,292 @@ +# ページネーション + +フレキシブルでかつユーザーフレンドリーなウェブアプリケーションを作成する際の主たる障害の +一つとなるのが、直感的なユーザーインターフェイスです。多くのアプリケーションはすぐに巨大となり +かつ複雑になり、デザイナーやプログラマーは、何百件、何千件ものレコードが表示されることに +対応しきれなくなってきます。リファクタリングするには時間がかかり、パフォーマンスやユーザー満足度が +犠牲になることが多いです。 + +1ページあたりに表示されるレコードの数を一定数に抑えることは、すべてのアプリケーションにとって +重大な課題であり、開発者にとって頭の痛い問題でした。CakePHP は素早く、かつ簡単に、 +データをページ分けする方法を提供することで、開発者への負担を軽減します。 + +CakePHP におけるページネーションは、コントローラーにおけるコンポーネントによって提供され、 +ページ分けされたクエリをより簡単にビルドできるようにします。ビューの中の +`Cake\View\Helper\PaginatorHelper` は、ページネーションのリンクや +ボタンを作り出すことを容易にすることに使われます。 + +## 基本的な使用方法 + +一度ロードされれば、ORMテーブルクラスや `Query` オブジェクトをページ分割することができます。 : + +``` php +public function index() +{ + // ORM テーブルのページ分割 + $this->set('articles', $this->paginate($this->Articles)); + + // 部分的に完了したクエリをページ分割する + $query = $this->Articles->find('published')->contain('Comments'); + $this->set('articles', $this->paginate($query)); +} +``` + +## 高度な使用方法 + +`$paginate` のコントローラプロパティや `paginate()` の引数 +`$settings` として設定することで、より複雑なユースケースをサポートしています。 +これらの条件はページ分割クエリの基礎となります。 +これらの条件は URLから渡される `sort`, `direction`, `limit`, `page` +のパラメータによって拡張されます。 : + +``` php +class ArticlesController extends AppController +{ + protected array $paginate = [ + 'limit' => 25, + 'order' => [ + 'Articles.title' => 'asc', + ], + ]; +} +``` + +> [!TIP] +> デフォルトの `order` オプションは配列として定義されていなければなりません。 + +ページネーションオプションを [Custom Find Methods](../orm/retrieving-data-and-resultsets#custom-find-methods) にバンドルする方が +すっきりしていてシンプルです。 +`finder` オプションを使用することで、ページ分割の際にファインダーを使用することができます。 : + +``` php +class ArticlesController extends AppController +{ + protected array $paginate = [ + 'finder' => 'published', + ]; +} +``` + +ファインダーメソッドに追加のオプションが必要な場合は、これらの値を finder: に渡すことができます。 : + +``` php +class ArticlesController extends AppController +{ + // タグごとに記事を検索する + public function tags() + { + $tags = $this->request->getParam('pass'); + + $customFinderOptions = [ + 'tags' => $tags + ]; + + // カスタム Finder メソッドは、ArticlesTable.php の中で "findTagged" と呼ばれる + // 以下のような構文となっている + // public function findTagged(Query $query, array $options) { + // そのため、taggedをキーとして使用する + $settings = [ + 'finder' => [ + 'tagged' => $customFinderOptions + ] + ]; + $articles = $this->paginate($this->Articles, $settings); + $this->set(compact('articles', 'tags')); + } +} +``` + +一般的なページネーションの値を定義することに加え、コントローラーには1セット以上の +ページネーションに関するデフォルト設定を定義することができます。そのためには、 +設定を加えたいモデルの後に、配列におけるキー名称を加えるだけです。 : + +``` php +class ArticlesController extends AppController +{ + protected array $paginate = [ + 'Articles' => [], + 'Authors' => [], + ]; +} +``` + +`Articles` や `Authors` のキーの値は、基本的な `$paginate` 配列に含まれる +すべてのプロパティを含めることができます。 + +一度 `paginate()` を使って結果を作成した後は コントローラのリクエストは +ページングパラメータで更新されます。 +ページングのメタデータは `$this->request->getParam('paging')` で取得できます。 + +## シンプルなページネーション + +デフォルトではページネーションは `count()` クエリを使って結果セットのサイズを計算し、 +ページ番号のリンクを表示できるようにしています。 +非常に大きなデータセットでは、このcountクエリは非常に高価になります。 +'Next' と 'Previous' リンクだけを表示したい場合は、カウントクエリを行わない +'simple' paginator を使うことができます。 : + +``` php +class ArticlesController extends AppController +{ + protected array $paginate = [ + 'className' => 'Simple', // Or use Cake\Datasource\Paging\SimplePaginator::class FQCN + ]; +} +``` + +`SimplePaginator` を使っている場合、ページ番号やカウンターデータ、最後のページへのリンク、 +総レコード数のコントロールを生成することはできません。 + +## 複数のクエリのページ分割 + +コントローラの `$paginate` プロパティと `paginate()` メソッドを呼び出す際に +`scope` オプションを使うことで、1つのコントローラのアクションの中で複数のモデルを +ページ分割することができます。 : + +``` php +// ページ分割するプロパティ +protected array $paginate = [ + 'Articles' => ['scope' => 'article'], + 'Tags' => ['scope' => 'tag'] +]; + +// コントローラーアクションにおいて +$articles = $this->paginate($this->Articles, ['scope' => 'article']); +$tags = $this->paginate($this->Tags, ['scope' => 'tag']); +$this->set(compact('articles', 'tags')); +``` + +`scope` オプションを指定すると、 `PaginatorComponent` がスコープされた +クエリ文字列パラメータを検索するようになります。 +例えば、以下のURLはタグと記事を同時にページ分割するのに使えます。 : + + /dashboard?article[page]=1&tag[page]=3 + +スコープされたHTML要素やページネーション用のURLを生成する方法については +[Paginator Helper Multiple](../views/helpers/paginator#paginator-helper-multiple) のセクションを参照してください。 + +## 同じモデルを複数回ページ分割する + +1つのコントローラアクション内で同じモデルを複数回ページ分割するには、 +モデルのエイリアスを定義する必要があります。 +テーブルレジストリの使用方法の詳細については、 [Table Registry Usage](../orm/table-objects#table-registry-usage) を参照してください。 : + +``` php +// コントローラーアクションにおいて +$this->paginate = [ + 'Articles' => [ + 'scope' => 'published_articles', + 'limit' => 10, + 'order' => [ + 'id' => 'desc', + ], + ], + 'UnpublishedArticles' => [ + 'scope' => 'unpublished_articles', + 'limit' => 10, + 'order' => [ + 'id' => 'desc', + ], + ], +]; + +$publishedArticles = $this->paginate( + $this->Articles->find('all', scope: 'published_articles') + ->where(['published' => true]) +); + +// ページ分割コンポーネントで差別化できるようにテーブルオブジェクトを追加登録します。 +$unpublishedArticlesTable = $this->fetchTable('UnpublishedArticles', [ + 'className' => 'App\Model\Table\ArticlesTable', + 'table' => 'articles', + 'entityClass' => 'App\Model\Entity\Article', +]); + +$unpublishedArticles = $this->paginate( + $unpublishedArticlesTable->find('all', scope: 'unpublished_articles') + ->where(['published' => false]) +); +``` + +## ソート時に使用するフィールドの制御 + +デフォルトでは、テーブルが持つ非仮想カラムに対してソートを行うことができます。 +これはインデックス化されていないカラムをソートしてしまうことになり、 +ソートするのにコストがかかるため、望ましくないこともあります。 +ソートできるフィールドのホワイトリストを `sortableFields` オプションを使って設定することができます。 +このオプションは関連するデータやページ分割クエリの一部である計算フィールドをソートしたい場合に必要です。 : + +``` php +protected array $paginate = [ + 'sortableFields' => [ + 'id', 'title', 'Users.username', 'created', + ], +]; +``` + +ホワイトリストにないフィールドでソートしようとするリクエストは無視されます。 + +## 1ページあたりの最大行数を制限する + +ページごとに取得される結果の数は `limit` パラメータとしてユーザーに公開されます。 +一般的に、ユーザーがページ分割されたセットのすべての行を取得できるようにすることは望ましくありません。 +オプションの `maxLimit` は、外部からこの制限値を高く設定することはできないことを保証します。 +デフォルトでは、CakePHPはフェッチできる行の最大数を100に制限しています。 +もしこのデフォルト値がアプリケーションにとって適切でない場合は、 +ページ分割オプションの一部として調整することができます。 : + +``` php +protected array $paginate = [ + // Other keys here. + 'maxLimit' => 10 +]; +``` + +リクエストのリミットパラメータがこの値よりも大きければ、 `maxLimit` の値まで減らされます。 + +## 範囲外のページ要求 + +`Controller::paginate()` は、存在しないページにアクセスしようとすると `NotFoundException`\` をスローします。 + +そのため、通常のエラーページをレンダリングさせるか、 try catch ブロックを使用して +`NotFoundException` が発生した場合に適切な処理を行うことができます。 : + +``` php +use Cake\Http\Exception\NotFoundException; + +public function index() +{ + try { + $this->paginate(); + } catch (NotFoundException $e) { + // 最初のページや最後のページにリダイレクトするようにします。 + // $e->getPrevious()->getAttributes('pagingParams')を指定すると、必要な情報が得られます。 + } +} +``` + +## paginatorクラスを直接利用する + +paginatorクラスを直接利用することも可能です。 : + +``` php +// paginatorのインスタンスを生成する +$paginator = new \Cake\Datasource\Paginator\NumericPaginator(); + +// モデルをページネーションする +$results = $paginator->paginate( + // ページ分割が必要なクエリまたはテーブルインスタンス + $this->fetchTable('Articles'), + // リクエストパラメーター + $this->request->getQueryParams(), + // Config array having the same structure as options as Controller::$paginate + [ + 'finder' => 'latest', + ] +); +``` + +## ビューのページネーション + +ページネーションナビゲーションのリンクの作り方は、 `Cake\View\Helper\PaginatorHelper` +のドキュメントを確認してください。 diff --git a/docs/ja/controllers/request-response.md b/docs/ja/controllers/request-response.md new file mode 100644 index 0000000000..28465c188e --- /dev/null +++ b/docs/ja/controllers/request-response.md @@ -0,0 +1,1259 @@ +# リクエストとレスポンスオブジェクト + +リクエストとレスポンスオブジェクトは、HTTP リクエストとレスポンスの周辺の抽象化を提供します。 +CakePHP のリクエストオブジェクトは、入ってきたリクエストを省みることができる一方、 +レスポンスオブジェクトは容易にコントローラーから HTTP レスポンスを作成することができます。 + +
    + +\$this-\>request + +
    + + + +## リクエスト + +`class` Cake\\Http\\**ServerRequest** + +`ServerRequest` は、CakePHP で使用されるデフォルトのリクエストオブジェクトです。 +リクエストデータへの応答と対話が中心的な機能となります。リクエストごとに Request は一つ作られ、 +リクエストデータを使うアプリケーションの様々なレイヤーに参照が渡されます。 +デフォルトのリクエストは `$this->request` に設定され、コントローラー、セル、ビュー、 +ヘルパーの中で利用できます。またコントローラーの参照を使うことでコンポーネントの中からも +アクセスすることが出来ます。 `ServerRequest` の役割は以下の通りです。 + +- GET, POST, そして FILES 配列を慣れ親しんだデータ構造に変換する処理を行います。 +- リクエストに関連する内省的環境を提供します。送信されたヘッダーやクライアントの IP アドレス、 + サーバーが実行されているサブドメイン/ドメインの情報などが含まれます。 +- リクエストパラメーターへのアクセス方法をインデックス付き配列とオブジェクトのプロパティーの + 両方の形式で提供します。 + +3.4.0 以降、 CakePHP のリクエストオブジェクトは、CakePHP の外部のライブラリーを +使用しやすくするため [PSR-7 ServerRequestInterface](https://www.php-fig.org/psr/psr-7/) +を実装します。 + +### リクエストパラメーター + +リクエストは、 `getParam()` メソッドを介して、ルーティングパラメーターを用意しています。 : + +``` php +$controllerName = $this->request->getParam('controller'); +``` + +全てのルーティングパラメーターを配列として取得するためには `getAttribute()` を使用します。 : + +``` php +$parameters = $this->request->getAttribute('params'); +``` + +すべての [Route Elements](../development/routing#route-elements) は、このインターフェイスを通してアクセスされます。 + +[Route Elements](../development/routing#route-elements) に加えて [Passed Arguments](../development/routing#passed-arguments) へのアクセスがしばしば必要になります。 +これらは両方ともリクエストオブジェクトと同様に利用可能です。 : + +``` php +// 渡された引数 +$passedArgs = $this->request->getParam('pass'); +``` + +すべての渡された引数にアクセスする方法が提供されています。この中には CakePHP の内部で使っている +重要で役に立つパラメーターが存在し、また、リクエストパラメーターの中ですべて見つけられます。 + +- `plugin` リクエストを処理するプラグインです。プラグインが存在しない場合は null になります。 +- `controller` 現在のリクエストを処理するコントローラーです。 +- `action` 現在のリクエストを処理するアクションです。 +- `prefix` 現在のアクションのプレフィックスです。詳しくは、 [Prefix Routing](../development/routing#prefix-routing) をご覧ください。 + +### クエリー文字列パラメーター + +`method` Cake\\Http\\ServerRequest::**getQuery**($name, $default = null) + +クエリー文字列パラメーターは、 `getQuery()` メソッドを使って読み取ることができます。 : + +``` php +// URL は /posts/index?page=1&sort=title +$page = $this->request->getQuery('page'); +``` + +query プロパティーに直接アクセスするか、エラーが発生しない方法で URL クエリー配列を読むために +`getQuery()` メソッドを使用することができます。キーが存在しない場合、 `null` が返ります。 : + +``` php +$foo = $this->request->getQuery('value_that_does_not_exist'); +// $foo === null + +// デフォルト値も提供できます。 +$foo = $this->request->getQuery('does_not_exist', 'default val'); +``` + +`getQueryParams()` を使用すると全てのクエリー文字列パラメーターにアクセスできます。 : + +``` php +$query = $this->request->getQueryParams(); +``` + +### リクエストのボディーデータ + +`method` Cake\\Http\\ServerRequest::**getData**($name, $default = null) + +すべての POST データは `Cake\Http\ServerRequest::getData()` を使ってアクセスされます。 +フォームデータが `data` 接頭辞を含んでいる場合、接頭辞は取り除かれるでしょう。例えば : + +``` php +// name 属性が 'MyModel[title]' の入力は次のようにアクセスします。 +$title = $this->request->getData('title'); +``` + +ドット区切りの名前を使用して、ネストされたデータにアクセスできます。 例えば : + +``` php +$value = $this->request->getData('address.street_name'); +``` + +存在しない名前の場合は `$default` の値が返されます。 : + +``` php +$foo = $this->request->getData('Value.that.does.not.exist'); +// $foo == null +``` + +また、異なるリクエストのボディをパースするために [ボディパーサミドルウェア](../controllers/middleware#body-parser-middleware) を使うこともできます。 +これは `ServerRequest::getData()` でアクセスできるようにするための配列です。 + +すべてのデータパラメータにアクセスしたい場合は `getParsedBody()` を使うことができます。 : + +``` php +$data = $this->request->getParsedBody(); +``` + +### ファイルのアップロード + +アップロードしたファイルは、上で説明した `Cake\Http\ServerRequest::getData()` +メソッドを使用して、リクエスト内容のデータからアクセスすることができます。 +例えば、name属性が `attachment` であるinput要素のファイルは、以下のようにアクセスできます。 : + +``` php +$attachment = $this->request->getData('attachment'); +``` + +デフォルトでは、ファイルのアップロードは、リクエストデータの中で、 +[\Psr\Http\Message\UploadedFileInterface](https://www.php-fig.org/psr/psr-7/#16-uploaded-files) +を実装したオブジェクトとして表現されます。 +上記の例では、 `$attachment` がオブジェクトを保持していますが、 +現在の実装では、上記の例の `$attachment` 変数は、 +デフォルトでは `\LaminasDiactorosUploadedFile` のインスタンスとなります。 + +アップロードしたファイルへのアクセスは非常に簡単です。 +ここでは、古い形式のファイルアップロード配列で提供されていたことと同じようにデータを取得する方法を説明します。 : + +``` php +$name = $attachment->getClientFilename(); +$type = $attachment->getClientMediaType(); +$size = $attachment->getSize(); +$tmpName = $attachment->getStream()->getMetadata('uri'); +$error = $attachment->getError(); +``` + +アップロードされたファイルを一時的な場所から目的の場所に移動させるのに +手動で一時的なファイルにアクセスする必要はありません。 +代わりに `moveTo()` メソッドを使用することで簡単に行うことができます。 : + +``` php +$attachment->moveTo($targetPath); +``` + +HTTP環境では、 `moveTo()` メソッドはファイルが実際にアップロードされたファイルであるかどうかを +自動的に検証し、必要に応じて例外をスローします。 +アップロードされたファイルという概念自体がないCLI環境では、 +それに関係なくファイルを移動できるので、ファイルアップロードのテストが非常に簡単になります。 + +ファイルアップロード配列を使用するように戻すには、 +設定値 `App.uploadedFilesAsObjects` を `false` に設定してください。 +例えば、 `config/app.php` で以下のように設定します。 : + +``` text +return [ + // ... + 'App' => [ + // ... + 'uploadedFilesAsObjects' => false, + ], + // ... +]; +``` + +このオプションを無効にすると、ファイルのアップロードはリクエストデータの中で配列として表現されます。 +それは、ネストされた入力/名前があっても変わらない正規化された構造を持っています。 +これは PHP のスーパーグローバル変数 `$_FILES` で表現する方法とは異なります。 +(詳細は [PHPマニュアル](https://www.php.net/manual/en/features.file-upload.php) を参照してください)。 +つまり、 `$attachment` の値は次のようになります。 : + +``` php +[ + 'name' => 'attachment.txt', + 'type' => 'text/plain', + 'size' => 123, + 'tmp_name' => '/tmp/hfz6dbn.tmp' + 'error' => 0 +] +``` + +> [!TIP] +> アップロードされたファイルは、リクエストデータとは別のオブジェクトとして +> `Cake\Http\ServerRequest::getUploadedFile()` と +> `Cake\Http\ServerRequest::getUploadedFiles()` メソッドを使用しています。 +> これらのメソッドは `App.uploadedFilesAsObjects` の設定に関係なく、常にオブジェクトを返します。 + +`method` Cake\\Http\\ServerRequest::**getUploadedFile**($path) + +アップロードされたファイルの特定のパスで返します。 +パスは `Cake\Http\ServerRequest::getData()` メソッドと同じドット構文を使用します。 : + +``` php +$attachment = $this->request->getUploadedFile('attachment'); +``` + +`Cake\Http\ServerRequest::getData()` と違って、 `Cake\Http\ServerRequest::getUploadedFile()` +は、実際にアップロードされたファイルが指定されたパスに存在する場合にのみデータを返します。 +通常であれば、ファイルではないリクエストのbodyデータが指定されたパスに存在する場合、このメソッドは `null` を返します。 + +`method` Cake\\Http\\ServerRequest::**getUploadedFiles**() + +アップロードされたすべてのファイルを正規化された配列構造で返します。 +上の例では、ファイルの入力名が `attachment` の場合、構造は次のようになります。 : + +``` php +[ + 'attachment' => object(Laminas\Diactoros\UploadedFile) { + // ... + } +] +``` + +`method` Cake\\Http\\ServerRequest::**withUploadedFiles**(array $files) + +このメソッドは、リクエストオブジェクトのアップロードファイルを設定します。 +これは [\Psr\Http\Message\UploadedFileInterface](https://www.php-fig.org/psr/psr-7/#16-uploaded-files). +を実装したオブジェクトの配列を受け付けます。 +これは、既存のアップロードされている可能性のあるすべてのファイルを置き換えます。 : + +``` php +$files = [ + 'MyModel' => [ + 'attachment' => new \Laminas\Diactoros\UploadedFile( + $streamOrFile, + $size, + $errorStatus, + $clientFilename, + $clientMediaType + ), + 'anotherAttachment' => new \Laminas\Diactoros\UploadedFile( + '/tmp/hfz6dbn.tmp', + 123, + \UPLOAD_ERR_OK, + 'attachment.txt', + 'text/plain' + ), + ], +]; + +$this->request = $this->request->withUploadedFiles($files); +``` + +> [!NOTE] +> このメソッドでリクエストに追加したアップロードファイルは、リクエスト本文では利用 *できません*。 +> すなわち、 `Cake\Http\ServerRequest::getData()` を経由して受け取ることはできません! +> リクエストデータに(も)必要な場合は、 `Cake\Http\ServerRequest::withData()` か +> `Cake\Http\ServerRequest::withParsedBody()` を経由して設定する必要があります。 + +### PUT、PATCH または DELETE データ + +`method` Cake\\Http\\ServerRequest::**input**($callback, [$options]) + +REST サービスを構築しているとき `PUT` と `DELETE` リクエストのデータを受付けることが +よくあります。 `application/x-www-form-urlencoded` リクエストボディーのデータは +`PUT` と `DELETE` リクエストでは自動的に構文解析され `$this->data` に設定されます。 +もし JSON や XML データを受け付けている場合、どうやってリクエストボディーにアクセスすればいいのかに +ついては以下の説明を見て下さい。 + +入力されたデータにアクセスする場合、オプション機能でそれをデコードすることができます。 +XML や JSON のリクエストボディーのコンテンツと対話するときに便利です。 +デコード機能のための追加のパラメーターは、 `input()` の引数として渡すことができます。 : + +``` php +$jsonData = $this->request->input('json_decode'); +``` + +### 環境変数 (\$ SERVER と \$ ENV より) + +`method` Cake\\Http\\ServerRequest::**env**($key, $value = null) + +`ServerRequest::env()` は、 `env()` グローバル関数のラッパーで、グローバルな +`$_SERVER` や `$_ENV` を変更することなくゲッター/セッターとして動作します。 : + +``` php +// ホストの取得 +$host = $this->request->env('HTTP_HOST'); + +// 値を設定。一般的にはテストに役立ちます。 +$this->request->env('REQUEST_METHOD', 'POST'); +``` + +`getServerParams()` を使用すると、全ての環境変数にアクセスできます。 : + +``` php +$env = $this->request->getServerParams(); +``` + +### XML または JSON データ + +[REST](../development/rest) を採用しているアプリケーションでは URL エンコードされていない +post 形式でデータを交換することがしばしばあります。 `Cake\Http\ServerRequest::input()` +を使用すると、任意の形式の入力データを読み込むことができます。 +デコード関数が提供されることでデシリアライズされたコンテンツを受け取ることができます。 : + +``` php +// PUT/POST アクションで投稿されたデータを JSON 形式にエンコードで取得する +$jsonData = $this->request->input('json_decode'); +``` + +`json_decode` で「結果を配列として受け取る」パラメーターのように、デシリアライズメソッドの中には +呼び出し時に追加パラメーターが必要なものがあります。同様に、 Xml を DOMDocument オブジェクトに +変換したい場合、 `Cake\Http\ServerRequest::input()` は、 +追加のパラメーターを渡すことができます。 : + +``` php +// PUT/POST アクションで投稿されたデータを XML エンコードで取得する +$data = $this->request->input('Cake\Utility\Xml::build', ['return' => 'domdocument']); +``` + +### パス情報 + +リクエストオブジェクトはまたアプリケーションのパスについての役立つ情報を提供しています。 +`base` や `webroot` 属性は URL の生成や、 アプリケーションが +サブディレクトリーにいるのかどうかの決定に役立ちます。様々な属性が使用できます。 : + +``` php +// 現在のリクエスト URL が /subdir/articles/edit/1?page=1 であると仮定 + +// /subdir/articles/edit/1?page=1 を保持 +$here = $request->getRequestTarget(); + +// /subdir を保持 +$base = $request->getAttribute('base'); + +// /subdir/ を保持 +$base = $request->getAttribute('webroot'); +``` + +### リクエストの状態をチェック + +`method` Cake\\Http\\ServerRequest::**is**($type, $args...) + +リクエストオブジェクトは、特定のリクエストに一定の条件を検査する簡単な方法を提供します。 +`is()` メソッドを使用することで、多くの一般的な条件を確認するだけでなく、 +他のアプリケーション固有の要求基準を検査することができます。 : + +``` php +$isPost = $this->request->is('post'); +``` + +新しい種類の検出器を作成するために `Cake\Http\ServerRequest::addDetector()` +を使用することで利用可能なリクエスト検出器を拡張することができます。4種類の異なる検出器を作成できます。 + +- 環境変数の比較 - 環境変数の比較、 `env()` から取得された値と提供された値が + 等しいかどうかを比較します。 +- パターン値の比較 - パターン値の比較では `env()` から取得された値と正規表現を比較します。 +- オプションベースの比較 - オプションベースの比較では正規表現を作成するためにオプションのリストを使います。 + 既に定義済みのオプション検出器を追加するための呼び出しはオプションをマージするでしょう。 +- コールバック検出器 - コールバック検出器はチェックをハンドリングするために 'callback' タイプを + 提供します。コールバックはパラメーターとしてだけリクエストオブジェクトを受け取ります。 + +`method` Cake\\Http\\ServerRequest::**addDetector**($name, $options) + +いくつかの例: + +``` php +// environment detector の追加 +$this->request->addDetector( + 'post', + ['env' => 'REQUEST_METHOD', 'value' => 'POST'] +); + +// pattern value detector の追加 +$this->request->addDetector( + 'iphone', + ['env' => 'HTTP_USER_AGENT', 'pattern' => '/iPhone/i'] +); + +// option detector の追加 +$this->request->addDetector('internalIp', [ + 'env' => 'CLIENT_IP', + 'options' => ['192.168.0.101', '192.168.0.100'] +]); + +// header detector を value comparison 付きで追加 +$this->request->addDetector('fancy', [ + 'env' => 'CLIENT_IP', + 'header' => ['X-Fancy' => 1] +]); + +// header detector を callable comparison 付きで追加 +$this->request->addDetector('fancy', [ + 'env' => 'CLIENT_IP', + 'header' => ['X-Fancy' => function ($value, $header) { + return in_array($value, ['1', '0', 'yes', 'no'], true); + }] +]); + +// callback detector を追加。有効な callable 形式でなければなりません。 +$this->request->addDetector( + 'awesome', + function ($request) { + return $request->getParam('awesome'); + } +); + +// 追加の引数を使用する detector を追加 +$this->request->addDetector( + 'csv', + [ + 'accept' => ['text/csv'], + 'param' => '_ext', + 'value' => 'csv', + ] +); +``` + +利用可能な組み込みの検出器は以下の通りです。 + +- `is('get')` 現在のリクエストが GET かどうかを調べます。 +- `is('put')` 現在のリクエストが PUT かどうかを調べます。 +- `is('patch')` 現在のリクエストが PATCH かどうかを調べます。 +- `is('post')` 現在のリクエストが POST かどうかを調べます。 +- `is('delete')` 現在のリクエストが DELETE かどうかを調べます。 +- `is('head')` 現在のリクエストが HEAD かどうかを調べます。 +- `is('options')` 現在のリクエストが OPTIONS かどうかを調べます。 +- `is('ajax')` 現在のリクエストが X-Requested-With = XMLHttpRequest + に由来するものかどうかを調べます。 +- `is('ssl')` リクエストが SSL 経由かどうかを調べます。 +- `is('flash')` リクエストに Flash の User-Agent があるかどうかを調べます。 +- `is('json')` リクエストに 「json」 の拡張子を持ち 「application/json」 + MIME タイプを受付けるかどうかを調べます。 +- `is('xml')` リクエストが 「xml」拡張子を持ち、「application/xml」または「text/xml」 + MIME タイプを受付けるかどうかを調べます。 + +`ServerRequest` は、 +`Cake\Http\ServerRequest::domain()` 、 +`Cake\Http\ServerRequest::subdomains()` 、 +`Cake\Http\ServerRequest::host()` +のようにサブドメインでアプリケーションを助けるためのメソッドを含みます。 + +### セッションデータ + +特定のリクエストのセッションにアクセスするには、 `getSession()` メソッドか `session` 属性を使用します。 : + +``` php +$session = $this->request->getSession(); +$session = $this->request->getAttribute('session'); + +$userName = $session->read('Auth.User.name'); +``` + +詳細については、セッションオブジェクトを使用する方法のための [セッション](../development/sessions) +ドキュメントを参照してください。 + +### ホストとドメイン名 + +`method` Cake\\Http\\ServerRequest::**domain**($tldLength = 1) + +アプリケーションが実行されているドメイン名を返します。 : + +``` php +// 'example.org' を表示 +echo $request->domain(); +``` + +`method` Cake\\Http\\ServerRequest::**subdomains**($tldLength = 1) + +アプリケーションが実行されているサブドメインを配列で返します。 : + +``` php +// 'my.dev.example.org' の場合、 ['my', 'dev'] を返す +$subdomains = $request->subdomains(); +``` + +`method` Cake\\Http\\ServerRequest::**host**() + +アプリケーションのホスト名を返します。 : + +``` php +// 'my.dev.example.org' を表示 +echo $request->host(); +``` + +### HTTP メソッドの読み込み + +`method` Cake\\Http\\ServerRequest::**getMethod**() + +リクエストの HTTP メソッドを返します。 : + +``` php +// POST を出力 +echo $request->getMethod(); +``` + +### アクションが受け入れる HTTP メソッドの制限 + +`method` Cake\\Http\\ServerRequest::**allowMethod**($methods) + +許可された HTTP メソッドを設定します。 +もしマッチしなかった場合、 `MethodNotAllowedException` を投げます。 +405 レスポンスには、通過できるメソッドを持つ `Allow` ヘッダーが含まれます。 : + +``` php +public function delete() +{ + // POST と DELETE のリクエストのみ受け入れます + $this->request->allowMethod(['post', 'delete']); + ... +} +``` + +### HTTP ヘッダーの読み込み + +リクエストで使われている `HTTP_*` ヘッダーにアクセスできます。 +例えば : + +``` php +// 文字列としてヘッダーを取得 +$userAgent = $this->request->getHeaderLine('User-Agent'); + +// 全ての値を配列で取得 +$acceptHeader = $this->request->getHeader('Accept'); + +// ヘッダーの存在を確認 +$hasAcceptHeader = $this->request->hasHeader('Accept'); +``` + +いくつかの apache インストール環境では、 `Authorization` ヘッダーにアクセスできませんが、 +CakePHP は、必要に応じて apache 固有のメソッドを介して利用できるようにします。 + +`method` Cake\\Http\\ServerRequest::**referer**($local = true) + +リクエストのリファラーを返します。 + +`method` Cake\\Http\\ServerRequest::**clientIp**() + +現在アクセスしているクライアントの IP アドレスを返します。 + +### プロキシーヘッダーの信頼 + +アプリケーションがロードバランサーの背後にあったり、クラウドサービス上で実行されている場合、 +しばしばリクエストでロードバランサーのホスト、ポートおよびスキームを取得します。 +多くの場合、ロードバランサーはまた、オリジナルの値として `HTTP-X-Forwarded-*` ヘッダーを送信します。 +転送されたヘッダーは、CakePHP によって使用されることはありません。リクエストオブジェクトで +これらのヘッダーを使用するには、 `trustProxy` プロパティーを `true` にを設定します。 : + +``` php +$this->request->trustProxy = true; + +// これらのメソッドがプロキシーのヘッダーを使用するようになります。 +$port = $this->request->port(); +$host = $this->request->host(); +$scheme = $this->request->scheme(); +$clientIp = $this->request->clientIp(); +``` + +一度プロキシが信頼されると、 `clientIp()` メソッドは `X-Forwarded-For` +ヘッダの中の *最後の* IPドレスを使用します。 +もし、アプリケーションが複数のプロキシの背後にある場合は +`setTrustedProxies()` を使ってコントロール内のプロキシのIPアドレスを定義することができます。 : + +``` php +$request->setTrustedProxies(['127.1.1.1', '127.8.1.3']); +``` + +プロキシが信頼された後は `clientIp()` は `X-Forwarded-For` ヘッダの最初のIPアドレスを使用します。 + +### Accept ヘッダーの確認 + +`method` Cake\\Http\\ServerRequest::**accepts**($type = null) + +クライアントがどのコンテンツタイプを受付けるかを調べます。また、特定のコンテンツタイプが +受付られるかどうかを調べます。 + +すべてのタイプを取得: + +``` php +$accepts = $this->request->accepts(); +``` + +あるタイプについての確認: + +``` php +$acceptsJson = $this->request->accepts('application/json'); +``` + +`method` Cake\\Http\\ServerRequest::**acceptLanguage**($language = null) + +クライアントが受付けるすべての言語を取得します。また、特定の言語が受付られるかどうかを調べます。 + +受付られる言語のリストを取得: + +``` php +$acceptsLanguages = $this->request->acceptLanguage(); +``` + +特定の言語が受付られるかどうかの確認: + +``` php +$acceptsSpanish = $this->request->acceptLanguage('es-es'); +``` + +### クッキーの読込み + +リクエストのクッキーはいくつかのメソッドを介して読むことができます。 : + +``` php +// クッキーの値、またはクッキーが存在しない場合 null を取得 +$rememberMe = $this->request->getCookie('remember_me'); + +// 値の読み込み、またはデフォルトの 0 を取得 +$rememberMe = $this->request->getCookie('remember_me', 0); + +// ハッシュとして全てのクッキーを取得 +$cookies = $this->request->getCookieParams(); + +// Get a CookieCollection instance +$cookies = $this->request->getCookieCollection() +``` + +クッキーコレクションの操作方法については、 `Cake\Http\Cookie\CookieCollection` +のドキュメントをご覧ください。 + +### アップロードされたファイル + +リクエストはアップロードされたファイルのデータを `getData()` または `getUploadedFiles()` で +`UploadedFileInterface` オブジェクトとして公開します。 : + +``` php +// アップロードファイルオブジェクトのリストを取得 +$files = $request->getUploadedFiles(); + +// ファイルデータの読み込み +$files[0]->getStream(); +$files[0]->getSize(); +$files[0]->getClientFileName(); + +// ファイルの移動 +$files[0]->moveTo($targetPath); +``` + +### URIの操作 + +リクエストは、リクエストされたURIと対話するためのメソッドを含むURIオブジェクトを含みます。 : + +``` php +// URIの取得 +$uri = $request->getUri(); + +// URIからデータを読み出す +$path = $uri->getPath(); +$query = $uri->getQuery(); +$host = $uri->getHost(); +``` + +
    + +\$this-\>response + +
    + +## レスポンス + +`class` Cake\\Http\\**Response** + +`Cake\Http\Response` は、CakePHP のデフォルトのレスポンスクラスです。 +いくつかの機能と HTTP レスポンスの生成をカプセル化します。 +また送信予定のヘッダーを調べるためにモックやスタブとしてテストの手助けをします。 +`Cake\Http\ServerRequest` と同様に、 `Controller`, +`RequestHandlerComponent` 及び `Dispatcher` に以前あった多くのメソッドを +`Cake\Http\Response` が統合します。 +古いメソッドは非推奨になり、 `Cake\Http\Response` の使用を推奨します。 + +`Response` は次のような共通のレスポンスをラップするためのインターフェイスを提供します。 + +- リダイレクトのヘッダーを送信。 +- コンテンツタイプヘッダーの送信。 +- 任意のヘッダーの送信。 +- レスポンスボディーの送信。 + +### コンテンツタイプの扱い + +`method` Cake\\Http\\Response::**withType**($contentType = null) + +`Cake\Http\Response::withType()` を使用して、アプリケーションのレスポンスの +コンテンツタイプを制御することができます。アプリケーションが Response に組み込まれていない +コンテンツの種類に対処する必要がある場合は、以下のように `setTypeMap()` を使って設定することができます。 : + +``` php +// vCard タイプを追加 +$this->response->setTypeMap('vcf', ['text/v-card']); + +// レスポンスのコンテンツタイプを vcard に設定 +$this->response = $this->response->withType('vcf'); +``` + +大抵の場合、追加のコンテンツタイプはコントローラーの `~Controller::beforeFilter()` +コールバックの中で設定したいと思うので、 `RequestHandlerComponent` が提供する +ビューの自動切り替え機能を活用できます。 + + + +### ファイルの送信 + +`method` Cake\\Http\\Response::**withFile**($path, $options = []) + +リクエストに対する応答としてファイルを送信する機会があります。 +`Cake\Http\Response::withFile()` を使用してそれを達成することができます。 : + +``` php +public function sendFile($id) +{ + $file = $this->Attachments->getFile($id); + $response = $this->response->withFile($file['path']); + // レスポンスオブジェクトを返すとコントローラーがビューの描画を中止します + return $response; +} +``` + +上記の例のようにメソッドにファイルのパスを渡す必要があります。CakePHP は、 +CakeHttpResponse::\$\_mimeTypes に登録された、よく知られるファイルタイプであれば +正しいコンテンツタイプヘッダーを送ります。 `Cake\Http\Response::withFile()` を呼ぶ前に +`Cake\Http\Response::withType()` メソッドを使って、新しいタイプを追加できます。 + +もし、あなたが望むなら、 オプションを明記することによって、ブラウザー上に表示する代わりにファイルを +ダウンロードさせることができます。 : + +``` php +$response = $this->response->withFile( + $file['path'], + ['download' => true, 'name' => 'foo'] +); +``` + +サポートされているオプションは次のとおりです。 + +name +name は、ユーザーに送信される代替ファイル名を指定することができます。 + +download +ヘッダーでダウンロードを強制するように設定する必要があるかどうかを示すブール値。 + +### 文字列をファイルとして送信 + +動的に生成された pdf や ics のようにディスク上に存在しないファイルを返すことができます。 : + +``` php +public function sendIcs() +{ + $icsString = $this->Calendars->generateIcs(); + $response = $this->response; + + // レスポンスのボディーに文字列コンテンツを挿入する + $response = $response->withStringBody($icsString); + + $response = $response->withType('ics'); + + // 任意のダウンロードファイル名を指定できます + $response = $response->withDownload('filename_for_download.ics'); + + // レスポンスオブジェクトを返すとコントローラーがビューの描画を中止します + return $response; +} +``` + +コールバックはボディーを文字列として返すこともできます。 : + +``` php +$path = '/some/file.png'; +$this->response->body(function () use ($path) { + return file_get_contents($path); +}); +``` + +### ヘッダーの設定 + +`method` Cake\\Http\\Response::**withHeader**($header, $value) + +ヘッダーの設定は `Cake\Http\Response::withHeader()` メソッドで行われます。 +すべての PSR-7 インターフェイスのメソッドと同様に、このメソッドは新しいヘッダーを含む +*新しい* インスタンスを返します。 : + +``` php +// 一つのヘッダーを追加/置換 +$response = $response->withHeader('X-Extra', 'My header'); + +// 一度に複数ヘッダーを設定 +$response = $response->withHeader('X-Extra', 'My header') + ->withHeader('Location', 'http://example.com'); + +// 既存のヘッダーに値を追加 +$response = $response->withAddedHeader('Set-Cookie', 'remember_me=1'); +``` + +セットされた際、ヘッダーは送られません。これらのヘッダーは、 `Cake\Http\Server` によって +レスポンスが実際に送られるまで保持されます。 + +便利なメソッド `Cake\Http\Response::withLocation()` を使うと +直接リダイレクトヘッダーの設定や取得ができます。 + +### ボディーの設定 + +`method` Cake\\Http\\Response::**withStringBody**($string) + +レスポンスボディーとして文字列を設定するには、次のようにします。 : + +``` php +// ボディーの中に文字列をセット +$response = $response->withStringBody('My Body'); + +// json レスポンスにしたい場合 +$response = $response->withType('application/json') + ->withStringBody(json_encode(['Foo' => 'bar'])); +``` + +`method` Cake\\Http\\Response::**withBody**($body) + +`withBody()` を使って、 `Laminas\Diactoros\MessageTrait` によって提供される +レスポンスボディーを設定するには、 : + +``` php +$response = $response->withBody($stream); +``` + +`$stream` が `Psr\Http\Message\StreamInterface` +オブジェクトであることを確認してください。新しいストリームを作成する方法は、以下をご覧ください。 + +`Laminas\Diactoros\Stream` ストリームを使用して、 +ファイルからレスポンスをストリーム化することもできます。 : + +``` php +// ファイルからのストリーム化 +use Laminas\Diactoros\Stream; + +$stream = new Stream('/path/to/file', 'rb'); +$response = $response->withBody($stream); +``` + +また、 `CallbackStream` を使用してコールバックをストリーム化できます。 +クライアントへストリーム化する必要のある画像、CSV ファイル もしくは PDF +のようなリソースがある場合に便利です。 : + +``` php +// コールバックからのストリーム化 +use Cake\Http\CallbackStream; + +// 画像の作成 +$img = imagecreate(100, 100); +// ... + +$stream = new CallbackStream(function () use ($img) { + imagepng($img); +}); +$response = $response->withBody($stream); +``` + +### 文字コードの設定 + +`method` Cake\\Http\\Response::**withCharset**($charset) + +レスポンスの中で使われる文字コードの種類を設定します。 : + +``` php +$this->response = $this->response->withCharset('UTF-8'); +``` + +### ブラウザーキャッシュとの対話 + +`method` Cake\\Http\\Response::**withDisableCache**() + +時々、コントローラーアクションの結果をキャッシュしないようにブラウザーに強制する必要がでてきます。 +`Cake\Http\Response::withDisableCache()` はそういった目的で使われます。 : + +``` php +public function index() +{ + // キャッシュの無効化 + $this->response = $this->response->withDisabledCache(); +} +``` + +> [!WARNING] +> Internet Explorer にファイルを送ろうとしている場合、SSL ドメインからの +> キャッシュを無効にすることで結果をエラーにすることができます。 + +`method` Cake\\Http\\Response::**withCache**($since, $time = '+1 day') + +クライアントにレスポンスをキャッシュして欲しいことを伝えられます。 +`Cake\Http\Response::withCache()` を使って: + +``` php +public function index() +{ + // キャッシュの有効化 + $this->response = $this->response->withCache('-1 minute', '+5 days'); +} +``` + +上記の例では、訪問者の体感スピード向上のため、クライアントにレスポンス結果を +5日間キャッシュするように伝えています。 +`withCache()` メソッドは、第一引数に `Last-Modified` ヘッダーの値を設定します。 +第二引数に `Expires` ヘッダーと `max-age` ディレクティブの値を設定します。 +Cache-Control の `public` ディレクティブも設定されます。 + +### HTTP キャッシュのチューニング + +アプリケーションの速度を改善するための簡単で最善の方法の一つは HTTP キャッシュを使う事です。 +このキャッシュモデルの元では、modified time, response entity tag などいくつかのヘッダーを +設定することでレスポンスのキャッシュコピーを使うべきかどうかをクライアントが決定できるように +助ける事が求められます。 + +キャッシュやデータが変更されたときに無効化(更新)するロジックのコードを持つのではなく、 +HTTP は二つのモデル、expiration と validation を使います。これらは大抵の場合、 +自身でキャッシュを管理するよりかなり単純です。 + +`Cake\Http\Response::withCache()` と独立して、HTTP キャッシュヘッダーを +チューニングするための様々なメソッドが使えます。この点に関して、ブラウザーやリバースプロキシーの +キャッシュよりも有利だと言えます。 + +#### Cache Control ヘッダー + +`method` Cake\\Http\\Response::**withSharable**($public, $time = null) + +キャッシュ制御ヘッダーは expiration モデルの元で使われ、複数の指示を含んでいます。 +ブラウザーやプロキシーがどのようにキャッシュされたコンテンツを扱うのかをその指示で変更することができます。 +`Cache-Control` ヘッダーは以下の通りです。 : + + Cache-Control: private, max-age=3600, must-revalidate + +`Response` のいくつかのユーティリティメソッドを用いることで、最終的に有効な `Cache-Control` +ヘッダーを生成します。一つ目は、 `withSharable()` メソッドです。 +このメソッドは異なるユーザーやクライアントの間で共有出来ることを考慮されたレスポンスかどうかを示します。 +このメソッドは実際には、このヘッダーが `public` または `private` のどちらなのかを制御しています。 +private としてレスポンスを設定することは、レスポンスのすべてまたはその一部が特定のユーザー用であることを +示しています。共有キャッシュのメリットを活かすためにはコントロールディレクティブを public に設定する +必要があります。 + +このメソッドの二番目のパラメーターはキャッシュの `max-age` を指定するために使われます。このパラメーターは +レスポンスが古いと見なされる秒数を表しています。 : + +``` php +public function view() +{ + // ... + // Cache-Control を 3600 秒の間、public として設定 + $this->response = $this->response->withSharable(true, 3600); +} + +public function my_data() +{ + // ... + // Cache-Control を 3600 秒の間、private として設定 + $this->response = $this->response->withSharable(false, 3600); +} +``` + +`Response` は `Cache-Control` ヘッダーの中で各コンポーネントを設定するための分割されたメソッドを +公開しています。 + +#### Expiration ヘッダー + +`method` Cake\\Http\\Response::**withExpires**($time) + +`Expires` ヘッダーに、レスポンスが古いと見なされる日時を設定できます。 +このヘッダーは `withExpires()` メソッドを使って設定されます。 : + +``` php +public function view() +{ + $this->response = $this->response->withExpires('+5 days'); +} +``` + +またこのメソッドは、`DateTime` インスタンスや `DateTime` クラスによって +構文解析可能な文字列を受け付けます。 + +#### Etag ヘッダー + +`method` Cake\\Http\\Response::**withEtag**($tag, $weak = false) + +HTTP におけるキャッシュの検証はコンテンツが定期的に変化するような場合によく使われ、 +キャッシュが古いと見なせる場合にのみレスポンスコンテンツが生成されることをアプリケーションに求めます。 +このモデルのもとでは、クライアントはページを直接使う代わりにキャッシュの中に保存し続け、 +アプリケーションに毎回リソースが変更されたかどうかを尋ねます。 +これは画像や他のアセットといった静的なリソースに対して使われる場合が多いです。 + +`withEtag()` メソッド (entity tag と呼ばれる) は要求されたリソースを +識別するための一意な文字列です。大抵の場合はファイルのチェックサムのようなもので、 +リソースが一致するかどうかを調べるためにキャッシュはチェックサムを比較するでしょう。 + +実際にこのヘッダーを使うメリットを得るためには、手動で +`isNotModified()` メソッドを呼び出すかコントローラーに +[リクエストハンドリング](../controllers/components/request-handling) を読み込まなければなりません。 : + +``` php +public function index() +{ + $articles = $this->Articles->find('all')->all(); + + // 記事内容の単純なチェックサムです + // 現実世界のアプリケーションでは、もっと効率的な実装を使用する必要があります + $checksum = md5(json_encode($articles)); + + $response = $this->response->withEtag($checksum); + if ($response->isNotModified($this->request)) { + return $response; + } + + $this->response = $response; + // ... +} +``` + +> [!NOTE] +> ほとんどのプロキシーユーザーは、おそらくパフォーマンスと互換性の理由から、Etags の代わりに +> Last Modified ヘッダーの使用を検討してください。 + +#### Last Modified ヘッダー + +`method` Cake\\Http\\Response::**withModified**($time) + +HTTP キャッシュの検証モデルのもとでは、リソースが最後に変更された日時を示すために +`Last-Modified` ヘッダーを設定することができます。このヘッダーを設定すると CakePHP が +キャッシュしているクライアントにレスポンスが変更されたのかどうかを返答する手助けとなります。 + +実際にこのヘッダーを使うメリットを得るためには、 +`isNotModified()` メソッドを呼び出すかコントローラーに +[リクエストハンドリング](../controllers/components/request-handling) を読み込まなければなりません。 : + +``` php +public function view() +{ + $article = $this->Articles->find()->first(); + $response = $this->response->withModified($article->modified); + if ($response->isNotModified($this->request)) { + return $response; + } + $this->response; + // ... +} +``` + +#### Vary ヘッダー + +`method` Cake\\Http\\Response::**withVary**($header) + +時には同じ URL で異なるコンテンツを提供したいと思うかもしれません。 +これは多国語対応ページがある場合やブラウザーごとに異なる HTML を返すようなケースでしばしばおこります。 +そのような状況では `Vary` ヘッダーを使えます。 : + +``` php +$response = $this->response->withVary('User-Agent'); +$response = $this->response->withVary('Accept-Encoding', 'User-Agent'); +$response = $this->response->withVary('Accept-Language'); +``` + +#### Not-Modified レスポンスの送信 + +`method` Cake\\Http\\Response::**isNotModified**(Request $request) + +リクエストオブジェクトとレスポンスのキャッシュヘッダーを比較し、まだキャッシュが有効かどうかを決定します。 +もしまだ有効な場合、レスポンスのコンテンツは削除され 304 Not Modified ヘッダーが送られます。 : + +``` php +// コントローラーアクションの中で +if ($this->response->isNotModified($this->request)) { + return $this->response; +} +``` + +### クッキーの設定 + +クッキーは、配列または `Cake\Http\Cookie\Cookie` オブジェクトを使って +レスポンスに追加することができます。 : + +``` php +use Cake\Http\Cookie\Cookie; +use DateTime; + +// クッキーを追加 +$this->response = $this->response->withCookie(Cookie::create( + 'remember_me', + 'yes', + // すべてのキーはオプションです + [ + 'expires' => new DateTime('+1 year'), + 'path' => '', + 'domain' => '', + 'secure' => false, + 'http' => false, + ] +)); +``` + +クッキーオブジェクトの使い方は [Creating Cookies](#creating-cookies) セクションをご覧ください。 +`withExpiredCookie()` を使ってレスポンスに期限切れのクッキーを送ることができます。 +これにより、ブラウザはローカルクッキーを削除します。 : + +``` php +$this->response = $this->response->withExpiredCookie(new Cookie('remember_me')); +``` + +## クロスオリジンリクエストヘッダー(CORS)の設定 + +[HTTP アクセス制御](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) 関連の +ヘッダーを定義するために、流れるようなインターフェイスの `cors()` メソッドが使用できます。 : + +``` php +$this->response = $this->response->cors($this->request) + ->allowOrigin(['*.cakephp.org']) + ->allowMethods(['GET', 'POST']) + ->allowHeaders(['X-CSRF-Token']) + ->allowCredentials() + ->exposeHeaders(['Link']) + ->maxAge(300) + ->build(); +``` + +以下の基準が満たされた場合のみ、 CORS 関連ヘッダーはレスポンスに適用されます。 + +1. リクエストは `Origin` ヘッダーがあります。 +2. リクエストの `Origin` 値が許可された Origin 値のいずれかと一致します。 + +## 不変レスポンスに伴うよくある失敗 + +レスポンスオブジェクトはレスポンスを不変オブジェクトとして扱う +いくつかのメソッドを提供しています。不変オブジェクトは、偶発的な副作用の追跡を困難になるのを予防し、 +その変更順序のリファクタリングに起因するメソッド呼び出しに起因する間違いを減らします。 +それらは多くの利点を提供しますが、不変オブジェクトには慣れが必要です。 +`with` で始まるメソッドは、レスポンスに対して不変な方法で動作し、 +**常に** 、 **新しい** インスタンスを返します。変更されたインスタンスを保持し忘れるのは、 +不変オブジェクトを扱うときに人々が最も頻繁にする失敗です。 : + +``` php +$this->response->withHeader('X-CakePHP', 'yes!'); +``` + +上記のコードでは、レスポンスは `X-CakePHP` ヘッダーがありません。 +`withHeader()` メソッドの戻り値を保持していないためです。 +上記のコードを修正するには、次のように記述します。 : + +``` php +$this->response = $this->response->withHeader('X-CakePHP', 'yes!'); +``` + +## クッキーコレクション + +`class` Cake\\Http\\Cookie\\**CookieCollection** + +`CookieCollection` オブジェクトは、リクエストオブジェクトとレスポンスオブジェクトから +アクセス可能です。イミュータブルパターンを使ってクッキーのグループとやり取りすることができ、 +リクエストとレスポンスの不変性が維持されます。 + +### クッキーの作成 + +`class` Cake\\Http\\Cookie\\**Cookie** + +`Cookie` オブジェクトは、コンストラクタオブジェクトを介して、または +イミュータブルパターンに従って流れるようなインターフェースを使用することによって +定義することができます。 : + +``` php +use Cake\Http\Cookie\Cookie; + +// コンストラクタの中の全ての引数 +$cookie = new Cookie( + 'remember_me', // 名前 + 1, // 値 + new DateTime('+1 year'), // 有効期限、適用する場合 + '/', // パス、該当する場合 + 'example.com', // ドメイン名、適用する場合 + false, // secure のみ? + true // http のみ ? +); + +// ビルダーメソッドを使用 +$cookie = (new Cookie('remember_me')) + ->withValue('1') + ->withExpiry(new DateTime('+1 year')) + ->withPath('/') + ->withDomain('example.com') + ->withSecure(false) + ->withHttpOnly(true); +``` + +クッキーを作成したら、新規または既存の `CookieCollection` に追加することができます。 : + +``` php +use Cake\Http\Cookie\CookieCollection; + +// 新規のコレクションを作成 +$cookies = new CookieCollection([$cookie]); + +// 既存のコレクションに追加 +$cookies = $cookies->add($cookie); + +// 名前でクッキーを削除 +$cookies = $cookies->remove('remember_me'); +``` + +> [!NOTE] +> コレクションは不変であり、クッキーを追加したりコレクションからクッキーを削除すると、 +> *新規に* コレクションが作成されることに注意してください。 + +レスポンスにクッキーオブジェクトを追加することができます。 : + +``` php +// クッキーを1つ追加 +$response = $this->response->withCookie($cookie); + +// クッキーコレクション全体を置き換える +$response = $this->response->withCookieCollection($cookies); +``` + +レスポンスにセットするクッキーは [Encrypted Cookie Middleware](../controllers/middleware#encrypted-cookie-middleware) を使って +暗号化することができます。 + +### クッキーの読込み + +`CookieCollection` インスタンスを取得すると、それに含まれるクッキーにアクセスできます。 : + +``` php +// クッキーが存在するかどうかをチェック +$cookies->has('remember_me'); + +// コレクション内のクッキーの数を取得 +count($cookies); + +// クッキーインスタンスを取得 +$cookie = $cookies->get('remember_me'); +``` + +`Cookie` オブジェクトを取得すると、その状態をやりとりしたり変更したりできます。 +クッキーは不変なので、クッキーを変更した場合にコレクションを更新する必要があることに +注意してください。 : + +``` php +// 値の取得 +$value = $cookie->getValue() + +// JSON 値の中のデータにアクセス +$id = $cookie->read('User.id'); + +// 状態のチェック +$cookie->isHttpOnly(); +$cookie->isSecure(); +``` diff --git a/docs/ja/core-libraries/app.md b/docs/ja/core-libraries/app.md new file mode 100644 index 0000000000..7221ed664b --- /dev/null +++ b/docs/ja/core-libraries/app.md @@ -0,0 +1,121 @@ +# Appクラス + +`class` Cake\\Core\\**App** + +App クラスはリソースの位置とパスの管理を担当します。 + +## クラスの検索 + +`static` Cake\\Core\\App::**className**($name, $type = '', $suffix = '') + +この方法は CakePHP 全体でクラス名を解決するために使われます。 +CakePHP が使用する短い形式の名前を解決し、完全解決されたクラス名を返します。 : + +``` php +// 短いクラス名を名前空間とサフィックスで解決します。 +App::className('Flash', 'Controller/Component', 'Component'); +// Cake\Controller\Component\FlashComponent を返します + +// プラグイン名を解決します。 +App::className('DebugKit.Toolbar', 'Controller/Component', 'Component'); +// Returns DebugKit\Controller\Component\ToolbarComponent + +// \を含む名前はそのまま返されます。 +App::className('App\Cache\ComboCache'); +// App\Cache\ComboCache を返します。 +``` + +クラスを解決する時、 `App` 名前空間による解決が試みられ、 +もしそのクラスが存在しなければ `Cake` 名前空間による解決が行われます。 +もし両方のクラス名が存在しない場合、 `false` が返されます。 + +## Finding Paths to Resources + +`static` Cake\\Core\\App::**path**(string $package, ?string $plugin = null) + +The method returns paths set using `App.paths` app config: + +``` php +// Get the templates path set using ``App.paths.templates`` app config. +App::path('templates'); +``` + +The same way you can retrieve paths for `locales`, `plugins`. + +## 名前空間のパスの検索 + +`static` Cake\\Core\\App::**classPath**(string $package, ?string $plugin = null) + +規約上のパスについて位置を得るために使われます。 : + +``` php +// アプリケーション中の Controller/ までのパスを得ます +App::classPath('Controller'); +``` + +これはアプリケーションを構成するすべての名前空間に対して行うことができます。 + +`App::classPath()` は既定のパスのみを返し、 +オートローダーに設定された追加のパスに関するいかなる情報も返しません。 + +`static` Cake\\Core\\App::**core**(string $package) + +CakePHP 内部のパッケージのパスを検索するために使われます。 : + +``` php +// Cache エンジンのパスを得ます +App::core('Cache/Engine'); +``` + +## テーマの検出 + +テーマはプラグインなので、テーマのパスを取得するには上のメソッドを使うことができます。 + +## ベンダーファイルの読込 + +理想的にはベンダーファイルは `Composer` でオートロードされるべきで、 +もし Composer でベンダーファイルをオートロードまたはインストールできない場合、 +それらを読み込むために `require` を使う必要があるかもしれません。 + +もしライブラリーを Composer でインストールできないのであれば、Composer の規約に沿ったディレクトリーである +`vendor/$author/$package` に各ライブラリーをインストールするのが最善です。 +もし AcmeLib というライブラリーを持っていたなら、 `vendor/Acme/AcmeLib` にインストールするのです。 +仮に PSR-0 互換のクラス名を使ってないとしたら、 +あなたのアプリケーションの `composer.json` 中で次のようにオートロードできます。 : + +``` json +"autoload": { + "psr-4": { + "App\\": "src/", + "App\\Test\\": "tests/" + }, + "classmap": [ + "vendor/Acme/AcmeLib" + ] +} +``` + +もしもあなたのベンダーライブラリーがクラスを使っておらず、 +代わりに関数を提供する場合、 `files` オートロードの手法を使って +各リクエストの最初にそれらのファイルを読み込むように Composer を設定することができます。 : + +``` json +"autoload": { + "psr-4": { + "App\\": "src/", + "App\\Test\\": "tests/" + }, + "files": [ + "vendor/Acme/AcmeLib/functions.php" + ] +} +``` + +ベンダーライブラリーの設定をした後はあなたのアプリケーションのオートローダーを再生成する必要があります。 : + +``` bash +$ php composer.phar dump-autoload +``` + +もしもあなたのアプリケーションで Composer を使っていないとしたら、 +自分ですべてのベンダーライブラリーを手動で読み込む必要があるでしょう。 diff --git a/docs/ja/core-libraries/caching.md b/docs/ja/core-libraries/caching.md new file mode 100644 index 0000000000..fc5f988e7a --- /dev/null +++ b/docs/ja/core-libraries/caching.md @@ -0,0 +1,602 @@ +# キャッシュ + +`class` Cake\\Cache\\**Cache** + +速くより近くのストレージシステムに必要なデータの二次コピーを持つことにより、 +高価なリソースや低速なリソースの読み込みを行うためにキャッシュを使用できます。 +たとえば、高価なクエリの結果や頻繁に変更されないリモートウェブサービスへのアクセスを +キャッシュに格納することができます。キャッシュに保存されると、キャッシュからの読み込みは、 +リモートリソースにアクセスよりもはるかに高速です。 + +CakePHP のキャッシュは、 `Cache` クラスを使用します。このクラスは、 +様々なキャッシュの実装を扱うための静的なインターフェイスや統一された API を提供します。 +CakePHP には、いくつかのキャッシュエンジンが用意されていて、 +独自のバックエンドを構築する必要な場合、シンプルなインターフェイスを提供します。 +以下が、組み込みのキャッシュエンジンです。 + +- `File` File キャッシュはローカルファイルを使用するシンプルなキャッシュです。 + 最も遅いキャッシュエンジンで、アトミックな操作のための多くの機能を持ちません。 + しかし、ディスクストレージは非常に安価なので、頻繁に書き込みが行なわれない + 大きなオブジェクトや要素の保存はファイルに適しています。 +- `Memcached` [Memcached](https://php.net/memcached) 拡張を使います。 +- `Redis` [phpredis](https://github.com/phpredis/phpredis) 拡張を使います。 + Redis は高速で、Memcached と同様の永続キャッシュシステム、アトミックな操作を提供します。 +- `Apcu` APCu キャッシュは、PHP の [APCu](https://php.net/apcu) 拡張を使用します。 + この拡張はオブジェクトを保存するためにウェブサーバー上の共有メモリーを使います。 + これはとても高速で、かつアトミックな読み込み/書き込みの機能を提供することが可能になります。 +- `Wincache` Wincache は [Wincache](https://php.net/wincache) 拡張を使います。 + Wincache は APC と同様の機能とパフォーマンスを持ちますが、Windows と IIS に最適化されています。 +- `Array` はすべてのデータを配列に保存します。 + 永続的なストレージを提供せず、test suites内での使用を想定しています。 +- `Null` nullエンジンは実質何も保存せず、すべての読み込み操作を失敗させます。 + +あなたが選択したキャッシュエンジンに関わらず、 +アプリケーションは `Cake\Cache\Cache` とやり取りします。 + +## Cache エンジンの設定 + +`static` Cake\\Cache\\Cache::**setConfig**($key, $config = null) + +アプリケーションは、ブート処理中に任意の数の「エンジン」を設定できます。 +キャッシュエンジンの設定は、 **config/app.php** で定義されています。 + +最適なパフォーマンスを得るには、CakePHP では2つのキャッシュエンジンを定義する必要があります。 + +- `_cake_core_` は、ファイル構成の保存と、 + [国際化と地域化](../core-libraries/internationalization-and-localization) + ファイルの結果のパースに使用されます。 +- `_cake_model_` は、アプリケーション上のモデルに対するスキーマの説明を保存するために使用されます。 + +複数のエンジン設定を使用することで、必要に応じてストレージを段階的に変更できます。 +例えば、以下のように **config/app.php** に設定できます。 : + +``` php +// ... +'Cache' => [ + 'short' => [ + 'className' => 'File', + 'duration' => '+1 hours', + 'path' => CACHE, + 'prefix' => 'cake_short_' + ], + // 完全な名前空間つきの名前を使用。 + 'long' => [ + 'className' => 'Cake\Cache\Engine\FileEngine', + 'duration' => '+1 week', + 'probability' => 100, + 'path' => CACHE . 'long' . DS, + ] +] +// ... +``` + +オプション設定は `DSN` を指定することもできます。 +これは環境変数や `PaaS` プロバイダーと一緒に動作するときに便利です。 : + +``` php +Cache::setConfig('short', [ + 'url' => 'memcached://user:password@cache-host/?timeout=3600&prefix=myapp_', +]); +``` + +DSN を使用するとき、追加のクエリー文字列要素としてパラメーターやオプションが定義できます。 + +実行時におけるキャッシュエンジンの設定もできます。 : + +``` php +// 短い名前で +Cache::setConfig('short', [ + 'className' => 'File', + 'duration' => '+1 hours', + 'path' => CACHE, + 'prefix' => 'cake_short_' +]); + +// 完全な名前空間つきの名前を使用。 +Cache::setConfig('long', [ + 'className' => 'Cake\Cache\Engine\FileEngine', + 'duration' => '+1 week', + 'probability' => 100, + 'path' => CACHE . 'long' . DS, +]); + +// オブジェクトで +$object = new FileEngine($config); +Cache::setConfig('other', $object); +``` + +これらのエンジン設定の名前 ('short' や 'long') は `Cake\Cache\Cache::write()` と +`Cake\Cache\Cache::read()` の `$config` パラメーターとして使われます。 +キャッシュエンジンを設定する場合は、次の構文を使用してクラス名を参照することができます。 : + +``` php +// 省略名 (App\ または Cake 名前空間の中) +Cache::setConfig('long', ['className' => 'File']); + +// プラグインの省略名 +Cache::setConfig('long', ['className' => 'MyPlugin.SuperCache']); + +// 完全な名前空間 +Cache::setConfig('long', ['className' => 'Cake\Cache\Engine\FileEngine']); + +// CacheEngineInterface を実装したオブジェクト +Cache::setConfig('long', ['className' => $myCache]); +``` + +> [!NOTE] +> FileEngine 使用時に、正しいパーミッションでのキャッシュファイルを指定して作成するには、 +> `mask` オプションの設定が必要です。 + +### エンジンのオプション + +各エンジンは次のオプションを受け入れます。 + +- `duration` このキャッシュ設定内のアイテムの存続期間を指定します。 + `strototime()` 互換表現として指定されます。 +- `groups` この設定に格納されているすべてのキーに関連付けられているグループまたは「タグ」のリスト。 + キャッシュから完全なグループを削除するのに便利です。 +- `prefix` すべてのエントリーの先頭に追加されます。 + キースペースを別のキャッシュ設定または別のアプリケーションと共有する必要がある場合に適しています。 +- `probability` キャッシュ GC クリーンアップの可能性。 + 0 に設定すると、 `Cache::gc()` が自動的に呼び出されなくなります。 + +### FileEngine オプション + +FileEngine は次のエンジン固有オプションを使用します。 + +- `isWindows` ホストがウインドウズであるかどうかで自動的に設定されます。 +- `lock` ファイルを書き込む前にロックする必要があるかどうか。 +- `mask` 作成されたファイルに使用されるマスク。 +- `path` キャッシュファイルを保存する場所へのパス。 デフォルトはシステムの一時ディレクトリです。 + +### RedisEngine オプション + +RedisEngine は次のエンジン固有オプションを使用します。 + +- `port` Redis サーバーが動作しているポート。 +- `host` Redis サーバーが動作しているホスト。 +- `database` 接続に使用するデータベース番号。 +- `password` Redis サーバーのパスワード。 +- `persistent` Redis への永続的な接続を行うかどうか。 +- `timeout` Redis の接続タイムアウト。 +- `unix_socket` Redis の UNIX ソケットへのパス。 + +### MemcacheEngine オプション + +- `compress` データを圧縮するかどうか。 +- `username` Memcache サーバーにアクセスするためのログイン名。 +- `password` Memcache サーバーにアクセスするためのパスワード。 +- `persistent` 永続的な接続の名前。同じ永続的な値を使用するすべての設定は、 + 単一の基本接続を共有します。 +- `serialize` データをシリアライズするために使用されるシリアライザエンジン。 + 利用可能なエンジンは php、igbinary、json です。 + php のほかに、memcached 拡張は適切なシリアライザのサポートでコンパイルする必要があります。 +- `servers` memcached サーバーの文字列または配列。配列の場合、MemcacheEngine + はそれらをプールとして使用します。 +- `options` memcached クライアントの追加のオプション。オプション =\> 値 の配列でなければなりません。 + `\Memcached::OPT_*` 定数をキーとして使用してください。 + +### キャッシュフォールバックの設定 + +書き込み不可能なフォルダーに書き込む `FileEngine` や、 `RedisEngine` が Redis に +接続できないなど、エンジンが利用できない場合、エンジンは `NullEngine` にフォールバックし、 +ログ可能なエラーを引き起こします。これにより、キャッシュ障害のためにアプリケーションが +キャッチされない例外をスローするのを防ぎます。 + +`fallback` 設定キーを使ってキャッシュ設定を指定された設定にフォールバックするよう設定できます。 : + +``` php +Cache::setConfig('redis', [ + 'className' => 'Redis', + 'duration' => '+1 hours', + 'prefix' => 'cake_redis_', + 'host' => '127.0.0.1', + 'port' => 6379, + 'fallback' => 'default', +]); +``` + +Redis サーバーが予期せず失敗した場合、 `redis` キャッシュ設定への書き込みは、 +`default` キャッシュ設定への書き込みにフォールバックします。このシナリオで `default` キャッシュ設定への +書き込みも失敗した場合、 `NullEngine` へ再び縮退運転し、キャッチされない例外をスローするのを防ぎます。 + +`false` でキャッシュフォールバックを無効にすることができます。 : + +``` php +Cache::setConfig('redis', [ + 'className' => 'Redis', + 'duration' => '+1 hours', + 'prefix' => 'cake_redis_', + 'host' => '127.0.0.1', + 'port' => 6379, + 'fallback' => false +]); +``` + +フォールバックがない場合、キャッシュ障害は例外として発生します。 + +### 設定されたキャッシュエンジンを削除する + +`static` Cake\\Cache\\Cache::**drop**($key) + +一度設定が作成されたら、変更することはできません。代わりに、 +`Cake\Cache\Cache::drop()` と `Cake\Cache\Cache::config()` +を使用して、設定を削除して再作成する必要があります。キャッシュエンジンを削除すると、設定が削除され、 +アダプターが構築されていれば破棄されます。 + +## キャッシュへの書き込み + +`static` Cake\\Cache\\Cache::**write**($key, $value, $config = 'default') + +`Cache::write()` はキャッシュに \$value を書き込みます。 +この値は後で `$key` で参照したり、削除したりすることができます。 +オプションの設定を指定して、キャッシュを保存することもできます。 +`$config` を指定しない場合、デフォルトが使用されます。 +`Cache::write()` はあらゆるタイプのオブジェクトを格納することができ、 +以下のようにモデルの結果を格納するのに理想的です。 : + +``` php +$posts = Cache::read('posts'); +if ($posts === null) { + $posts = $someService->getAllPosts(); + Cache::write('posts', $posts); +} +``` + +`Cache::write()` と `Cache::read()` を使用して、データベースへのアクセスを減らし、 +posts を取得しています。 + +> [!NOTE] +> CakePHP ORM で作成したクエリーの結果をキャッシュする場合は、 [Caching Query Results](../orm/query-builder#caching-query-results) +> セクションで説明しているように、Query オブジェクトのビルトインキャッシュ機能を使用する方が良いです。 + +### 一度に複数のキーを書き込む + +`static` Cake\\Cache\\Cache::**writeMany**($data, $config = 'default') + +一度に複数のキャッシュキーを書き込む必要が出るかもしれません。 +`write()` を複数回呼び出すこともできますが、 `writeMany()` は +CakePHP がより効率的なストレージ API を使用できるようにします。 +例えば Memcached を使用する場合、 `writeMany()` を使用して、 +複数回のネットワーク接続を節約できます。 : + +``` php +$result = Cache::writeMany([ + 'article-' . $slug => $article, + 'article-' . $slug . '-comments' => $comments +]); + +// $result は以下を含みます +['article-first-post' => true, 'article-first-post-comments' => true] +``` + +### アトミックな書き込み + +`static` Cake\\Cache\\Cache::**add**($key, $value $config = 'default') + +`Cache::add()` を使用すると、キーがキャッシュに存在しない場合に、 +アトミックにキーを値に設定することができます。 +もし、キーがすでにキャッシュに存在する場合や、書き込みに失敗した場合は、 +`add()` は `false` を返します: + +``` php +// キーをロックとして機能するように設定する +$result = Cache::add($lockKey, true); +if (!$result) { + return; +} +// 一度に1つのプロセスしかアクティブにできないアクションを行う。 + +// ロックキーを外す。 +Cache::delete($lockKey); +``` + +> [!WARNING] +> ファイルベースのキャッシュは、アトミックライトをサポートしていません。 + +### Read-through キャッシュ + +`static` Cake\\Cache\\Cache::**remember**($key, $callable, $config = 'default') + +Cache を使用すると、Read-through キャッシュを簡単に行うことができます。 +指定されたキャッシュキーが存在する場合、それが返されます。 +キーが存在しない場合、呼び出し可能オブジェクトが呼び出され、結果がキャッシュに格納されます。 + +たとえば、リモートサービスコールの結果をキャッシュすることがよくあります。 +あなたはこれをシンプルにするために `remember()` を使うことができます。 : + +``` php +class IssueService +{ + public function allIssues($repo) + { + return Cache::remember($repo . '-issues', function () use ($repo) { + return $this->fetchAll($repo); + }); + } +} +``` + +## キャッシュからの読み込み + +`static` Cake\\Cache\\Cache::**read**($key, $config = 'default') + +`Cache::read()` は、 `$key` 配下に格納されたキャッシュされた値を +`$config` から読み込むために使用されます。 `$config` が null の場合、 +デフォルトの設定が使用されます。 `Cache::read()` は、有効なキャッシュであれば +キャッシュされた値を返し、キャッシュが期限切れになっているか存在しない場合は `null` を返します。 +キャッシュの内容は false と評価される可能性があるので、必ず厳密な比較演算子 +`===` または `!==` を使用してください。 + +例: + +``` php +$cloud = Cache::read('cloud'); +if ($cloud !== null) { + return $cloud; +} + +// クラウドデータを生成する +// ... + +// キャッシュにデータを保存する +Cache::write('cloud', $cloud); + +return $cloud; +``` + +`short` という別のキャッシュ設定を使っている場合、 +下記のように `Cache::read()` と `Cache::write()` に明記してください。 : + +``` php +// デフォルトの代わりにshort からキー"cloud" を読み込む +$cloud = Cache::read('cloud', 'short'); +if ($cloud === null) { + // cloudデータ生成 + // ... + + // デフォルトの代わりにshort でデータをキャッシュ保存 + Cache::write('cloud', $cloud, 'short'); +} + +return $cloud; +``` + +### 一度に複数のキーを読み込む + +`static` Cake\\Cache\\Cache::**readMany**($keys, $config = 'default') + +一度に複数のキーを書き込んだ後、あなたは恐らくそれらを同様に読み込みたいでしょう。 +`read()` を複数回呼び出すこともできますが、 `readMany()` は CakePHP が +より効率的なストレージ API を使用できるようにします。例えば Memcached を使用している場合、 +`readMany()` を使用して、複数回のネットワーク接続を節約できます。 : + +``` php +$result = Cache::readMany([ + 'article-' . $slug, + 'article-' . $slug . '-comments' +]); +// $result は以下を含みます +['article-first-post' => '...', 'article-first-post-comments' => '...'] +``` + +## キャッシュからの削除 + +`static` Cake\\Cache\\Cache::**delete**($key, $config = 'default') + +`Cache::delete()` を使うと、キャッシュされたオブジェクトをストアから完全に削除できます。 : + +``` php +// キーの削除 +Cache::delete('my_key'); +``` + +4.4.0 以降、 `RedisEngine` は `deleteAsync()` メソッドも提供し、 +`UNLINK` オペレーションを使用してキャッシュキーを削除します: + +``` php +Cache::pool('redis')->deleteAsync('my_key'); +``` + +### 一度に複数のキーの削除 + +`static` Cake\\Cache\\Cache::**deleteMany**($keys, $config = 'default') + +一度に複数のキーを書き込んだら、それらを削除したいかもしれません。 +`delete()` を複数回呼び出すこともできますが、 `deleteMany()` は CakePHP が +より効率的なストレージ API を使用できるようにします。例えば Memcached を使用している場合、 +`deleteMany()` を使用して、複数回のネットワーク接続を節約できます。 : + +``` php +$result = Cache::deleteMany([ + 'article-' . $slug, + 'article-' . $slug . '-comments' +]); +// $result は以下を含みます +['article-first-post' => true, 'article-first-post-comments' => true] +``` + +## キャッシュデータのクリア + +`static` Cake\\Cache\\Cache::**clear**($config = 'default') + +キャッシュ設定から、すべてのキャッシュされた値を破棄します。Apcu、Memcached、Wincache +などのエンジンでは、キャッシュ設定のプレフィックスを使用してキャッシュエントリーを削除します。 +異なるキャッシュ設定には異なる接頭辞が付いていることを確認してください。 : + +``` php +// すべてのキーをクリアする。 +Cache::clear(); +``` + +4.4.0 以降では、 `RedisEngine` は `clearBlocking()` メソッドも提供し、 +`UNLINK` オペレーションを使ってキャッシュキーを削除します: + +``` php +Cache::pool('redis')->clearBlocking(); +``` + +> [!NOTE] +> APCu と Wincache は、ウェブサーバーと CLI 用に分離されたキャッシュを使用するため、 +> 別々にクリアする必要があります。(CLI ではウェブサーバーのキャッシュをクリアできません) + +## キャッシュを使用してカウンターを保存する + +`static` Cake\\Cache\\Cache::**increment**($key, $offset = 1, $config = 'default') + +`static` Cake\\Cache\\Cache::**decrement**($key, $offset = 1, $config = 'default') + +アプリケーション内のカウンターは、キャッシュに保存するのに適しています。 +例として、コンテストの残りの「枠」の単純なカウントダウンをキャッシュに格納することができます。 +Cache クラスは簡単な方法でカウンター値をインクリメント/デクリメントするアトミックな方法を公開しています。 +競合のリスクを軽減し、同時に2人のユーザーが値を1つ下げて誤った値にする可能性があるため、 +これらの値にはアトミック操作が重要です。 + +整数値を設定した後、 `increment()` および `decrement()` を使用して整数値を操作できます。 : + +``` php +Cache::write('initial_count', 10); + +// 設定した後に +Cache::decrement('initial_count'); + +// または +Cache::increment('initial_count'); +``` + +> [!NOTE] +> インクリメントとデクリメントは FileEngine では機能しません。 +> 代わりに、APCu、Wincache、Redis または Memcached を使用する必要があります。 + +## キャッシュを使用して共通のクエリー結果を格納する + +まれにしか変更されない、またはキャッシュに大量の読み込みが行われるような結果をキャッシュすることによって、 +アプリケーションのパフォーマンスを大幅に向上させることができます。 +この完璧な例は、 `Cake\ORM\Table::find()` の結果です。 +この Query オブジェクトを使用すると、 `cache()` メソッドを使用して結果をキャッシュできます。 +詳細は、 [Caching Query Results](../orm/query-builder#caching-query-results) セクションを参照してください。 + +## グループの使用 + +たまに、複数のキャッシュエントリーを特定のグループまたは名前空間に属するようにマークしたい場合があります。 +同じグループ内のすべてのエントリーで共有される情報が変更されるたびに、キーを大量に無効化したいというのは +一般的な要件です。これは、キャッシュ設定でグループを宣言することで可能です。 : + +``` php +Cache::setConfig('site_home', [ + 'className' => 'Redis', + 'duration' => '+999 days', + 'groups' => ['comment', 'article'] +]); +``` + +`method` Cake\\Cache\\Cache::**clearGroup**($group, $config = 'default') + +ホームページに生成された HTML をキャッシュに保存したいが、 +コメントや投稿がデータベースに追加されるたびにこのキャッシュを自動的に無効にしたいとします。 +`comment` と `article` グループを追加することで、このキャッシュ設定に保存されているキーに、 +両方のグループ名で効果的にタグを付けできます。 + +たとえば、新しい投稿が追加されるたびに、 `article` グループに関連付けられたすべてのエントリーを +削除するように Cache エンジンに指示できます。 : + +``` php +// src/Model/Table/ArticlesTable.php +public function afterSave($event, $entity, $options = []) +{ + if ($entity->isNew()) { + Cache::clearGroup('article', 'site_home'); + } +} +``` + +`static` Cake\\Cache\\Cache::**groupConfigs**($group = null) + +`groupConfigs()` を使用すると、グループと設定の間のマッピングを取得できます。 +つまり、同じグループを持ちます。 : + +``` php +// src/Model/Table/ArticlesTable.php + +/** + * すべてのキャッシュ設定をクリアする前述の例のバリエーション + * 同じグループを持つ + */ +public function afterSave($event, $entity, $options = []) +{ + if ($entity->isNew()) { + $configs = Cache::groupConfigs('article'); + foreach ($configs['article'] as $config) { + Cache::clearGroup('article', $config); + } + } +} +``` + +グループは、同じエンジンと同じ接頭辞を使用して、すべてのキャッシュ設定で共有されます。 +グループを使用していて、グループの削除を使用する場合は、すべての設定の共通プレフィックスを選択します。 + +## 全体的にキャッシュを有効または無効にする + +`static` Cake\\Cache\\Cache::**disable**() + +キャッシュの有効期限に関連する問題を把握しようとするときに、 +キャッシュの読み込みと書き込みをすべて無効にする必要があります。 +`enable()` と `disable()` を使ってこれを行うことができます。 : + +``` php +// すべてのキャッシュ読み取りとキャッシュ書き込みを無効にする。 +Cache::disable(); +``` + +無効にすると、すべての読み込みと書き込みは `null` を返却します。 + +`static` Cake\\Cache\\Cache::**enable**() + +無効にすると、 `enable()` を使用してキャッシュを再び有効にすることができます。 : + +``` php +// すべてのキャッシュの読み込みと書き込みを再び有効にする。 +Cache::enable(); +``` + +`static` Cake\\Cache\\Cache::**enabled**() + +もしキャッシュの状態を確認する必要がある場合は、 `enabled()` を使用してください。 + +## キャッシュエンジンの作成 + +独自の `Cache` エンジンは `App\Cache\Engine` やプラグインの `$plugin\Cache\Engine` +の中に提供することができます。キャッシュエンジンはキャッシュディレクトリー内になければなりません。 +`MyCustomCacheEngine` という名前のキャッシュエンジンがあれば、 +**src/Cache/Engine/MyCustomCacheEngine.php** に置かれます。また、プラグインの一部として、 +**plugins/MyPlugin/src/Cache/Engine/MyCustomCacheEngine.php** に置かれます。 +プラグインのキャッシュ設定は、プラグインのドット構文を使用する必要があります。 : + +``` php +Cache::setConfig('custom', [ + 'className' => 'MyPlugin.MyCustomCache', + // ... +]); +``` + +カスタムキャッシュエンジンは、いくつかの抽象メソッドを定義するだけでなく、 +いくつかの初期化メソッドを提供する `Cake\Cache\CacheEngine` を拡張する必要があります。 + +キャッシュエンジンに必要な API は次のとおりです。 + +`class` Cake\\Cache\\**CacheEngine** + +`method` Cake\\Cache\\CacheEngine::**write**($key, $value) + +`method` Cake\\Cache\\CacheEngine::**read**($key) + +`method` Cake\\Cache\\CacheEngine::**delete**($key) + +`method` Cake\\Cache\\CacheEngine::**clear**($check) + +`method` Cake\\Cache\\CacheEngine::**clearGroup**($group) + +`method` Cake\\Cache\\CacheEngine::**decrement**($key, $offset = 1) + +`method` Cake\\Cache\\CacheEngine::**increment**($key, $offset = 1) diff --git a/docs/ja/core-libraries/collections.md b/docs/ja/core-libraries/collections.md new file mode 100644 index 0000000000..d3e31fa59b --- /dev/null +++ b/docs/ja/core-libraries/collections.md @@ -0,0 +1,1276 @@ +# コレクション + +`class` Cake\\Collection\\**Collection** + +コレクションクラスは、配列または `Traversable` オブジェクトを操作するためのツールのセットを +提供します。もし今までに underscore.js を使用したことがあるなら、コレクションクラスに何を +期待できるかの考え方を理解しているはずです。 + +コレクションのインスタンスはイミュータブルです。コレクションを変更すると、 +代わりに新しいコレクションを生成します。これは、操作が副作用を含まないので、 +コレクションオブジェクトの働きがより予測可能になります。 + +## 簡単な例 + +コレクションは、配列または `Traversable` オブジェクトを使用して作成することができます。 +また、CakePHP での ORM と対話するたびに、コレクションと対話することになるでしょう。 +コレクションの簡単な使い方は次のようになります。 : + +``` php +use Cake\Collection\Collection; + +$items = ['a' => 1, 'b' => 2, 'c' => 3]; +$collection = new Collection($items); + +// 1より大きい要素を含む +// 新しいコレクションを作成 +$overOne = $collection->filter(function ($value, $key, $iterator) { + return $value > 1; +}); +``` + +また、 `new Collection()` の代わりに `collection()` ヘルパー関数を使用することができます。 : + +``` php +$items = ['a' => 1, 'b' => 2, 'c' => 3]; + +  // 両方ともコレクションのインスタンスを作成します。 +$collectionA = new Collection($items); +$collectionB = collection($items); +``` + +ヘルパーメソッドの利点は、 `(new Collection($items))` よりも連鎖が容易であるということです。 + +`Cake\Collection\CollectionTrait` は、あなたのアプリケーションにある任意の +`Traversable` オブジェクトにコレクションのような機能を統合することができます。 + +## メソッド一覧 + +| | | | | +|--------------|--------------|-----------------|-----| +| `append` | `appendItem` | `avg` | | +| `buffered` | `chunk` | `chunkWithKeys` | | +| `combine` | `compile` | `contains` | | +| `countBy` | `each` | `every` | | +| `extract` | `filter` | `first` | | +| `firstMatch` | `groupBy` | `indexBy` | | +| `insert` | `isEmpty` | `last` | | +| `listNested` | `map` | `match` | | +| `max` | `median` | `min` | | +| `nest` | `prepend` | `prependItem` | | +| `reduce` | `reject` | `sample` | | +| `shuffle` | `skip` | `some` | | +| `sortBy` | `stopWhen` | `sumOf` | | +| `take` | `through` | `transpose` | | +| `unfold` | `zip` | | | + +## 反復 + +`method` Cake\\Collection\\Collection::**each**($callback) + +コレクションは、 `each()` と `map()` メソッドで反復したり新しいコレクションに +変換することができます。 `each()` メソッドは新しいコレクションを作成しませんが、 +コレクション内の任意のオブジェクトを変更できます。 : + +``` php +$collection = new Collection($items); +$collection = $collection->each(function ($value, $key) { + echo "要素 $key: $value"; +}); +``` + +`each()` の戻り値はコレクションオブジェクトです。即時にコレクション内の各値にコールバックを +適用する反復処理します。 + +`method` Cake\\Collection\\Collection::**map**($callback) + +`map()` メソッドは、元のコレクション内の各オブジェクトに適用されるコールバックの出力に基づいて +新しいコレクションを作成します。 : + +``` php +$items = ['a' => 1, 'b' => 2, 'c' => 3]; +$collection = new Collection($items); + +$new = $collection->map(function ($value, $key) { + return $value * 2; +}); + +// $result には [2, 4, 6] が含まれています。 +$result = $new->toList(); + +// $result には ['a' => 2, 'b' => 4, 'c' => 6] が含まれています。 +$result = $new->toArray(); +``` + +`map()` メソッドは、新しいイテレータを作成し、反復する時に得られた項目を遅延して作成します。 + +`method` Cake\\Collection\\Collection::**extract**($path) + +`map()` 関数の最も一般的な用途の1つはコレクションから単一の列を抽出することです。 +特定のプロパティーの値を含む要素のリストを構築したい場合は、 `extract()` メソッドを +使用することができます。 : + +``` php +$collection = new Collection($people); +$names = $collection->extract('name'); + +// $result には ['mark', 'jose', 'barbara'] が含まれています。 +$result = $names->toList(); +``` + +コレクションクラス内の他の多くの関数と同様に、列を抽出するために、ドット区切りのパスを +指定することができます。この例では、記事のリストから著者名を含むコレクションを返します。 : + +``` php +$collection = new Collection($articles); +$names = $collection->extract('author.name'); + +// $result には ['Maria', 'Stacy', 'Larry'] が含まれています。 +$result = $names->toList(); +``` + +最後に、あなたが取得したいプロパティーがパスで表現できない場合は、 +それを返すようにコールバック関数を使用することができます。 : + +``` php +$collection = new Collection($articles); +$names = $collection->extract(function ($article) { + return $article->author->name . ', ' . $article->author->last_name; +}); +``` + +しばしば、他の構造の内部に深くネストされている複数の配列やオブジェクトに存在する共通のキーで +プロパティーを抽出する必要があります。これらの例については、パスのキーに `{*}` マッチャを +使用することができます。このマッチャは、 HasMany や BelongsToMany の関連データを照合する時に +便利です。 : + +``` php +$data = [ + [ + 'name' => 'James', + 'phone_numbers' => [ + ['number' => 'number-1'], + ['number' => 'number-2'], + ['number' => 'number-3'], + ] + ], + [ + 'name' => 'James', + 'phone_numbers' => [ + ['number' => 'number-4'], + ['number' => 'number-5'], + ] + ] +]; + +$numbers = (new Collection($data))->extract('phone_numbers.{*}.number'); +$numbers->toList(); +// 戻り値は ['number-1', 'number-2', 'number-3', 'number-4', 'number-5'] +``` + +この最後の例では、 他の例とは異なり `toList()` メソッドを使用していますが、 +おそらく重複したキーで結果を取得する場合に重要になります。 `toList()` メソッドを +使用することにより、重複するキーが存在する場合でも、すべての値を取得することが保証されます。 + +`Cake\Utility\Hash::extract()` とは異なり、このメソッドは +`{*}` ワイルドカードのみをサポートしています。 +他のすべてのワイルドカードと属性のマッチャはサポートされていません。 + +`method` Cake\\Collection\\Collection::**combine**($keyPath, $valuePath, $groupPath = null) + +既存のコレクションの中のキーと値から作られた新しいコレクションを作成することができます。 +キーと値の両方のパスは、ドット記法のパスで指定することができます。 : + +``` php +$items = [ + ['id' => 1, 'name' => 'foo', 'parent' => 'a'], + ['id' => 2, 'name' => 'bar', 'parent' => 'b'], + ['id' => 3, 'name' => 'baz', 'parent' => 'a'], +]; +$combined = (new Collection($items))->combine('id', 'name'); + +// 配列に変換すると、結果は次のようになります。 +[ + 1 => 'foo', + 2 => 'bar', + 3 => 'baz', +]; +``` + +また、オプションでパスに基づいた結果のグループ化に `groupPath` を使用することができます。 : + +``` php +$combined = (new Collection($items))->combine('id', 'name', 'parent'); + +// 配列に変換すると、結果は次のようになります。 +[ + 'a' => [1 => 'foo', 3 => 'baz'], + 'b' => [2 => 'bar'] +]; +``` + +最後に、動的にキーと値とグループのパスを構築するために *クロージャー* を使用することができます。 +例えば、エンティティーや(ORM によって `Cake/Time` インスタンスに変換された) 日付で作業する場合、 +日付で結果をグループ化するのによいでしょう。 : + +``` php +$combined = (new Collection($entities))->combine( + 'id', + function ($entity) { return $entity; }, + function ($entity) { return $entity->date->toDateString(); } +); + +// 配列に変換すると、結果は次のようになります。 +[ + 'date string like 2015-05-01' => ['entity1->id' => entity1, 'entity2->id' => entity2, ..., 'entityN->id' => entityN] + 'date string like 2015-06-01' => ['entity1->id' => entity1, 'entity2->id' => entity2, ..., 'entityN->id' => entityN] +] +``` + +`method` Cake\\Collection\\Collection::**stopWhen**(callable $c) + +`stopWhen()` メソッドを使用して、任意の時点で反復を停止することができます。 +コレクションの中でこのメソッドを呼び出すと、新しいコレクションを作成し、要素のいずれかで、 +渡された callable が false を返した場合、結果の引き渡しを停止します。 : + +``` php +$items = [10, 20, 50, 1, 2]; +$collection = new Collection($items); + +$new = $collection->stopWhen(function ($value, $key) { + // 30 より大きい最初の値で停止します。 + return $value > 30; +}); + +// $result には [10, 20] が含まれています。 +$result = $new->toList(); +``` + +`method` Cake\\Collection\\Collection::**unfold**(callable $callback) + +時々、コレクション内の要素に、複数の要素を持つ配列やイテレータが含まれています。 +すべての要素に対して一回の反復で済むように内部構造を平坦化したい場合は、 +`unfold()` メソッドが使用できます。これは、コレクション内のネストされた +すべての単一の要素をもたらす新しいコレクションを作成します。 : + +``` php +$items = [[1, 2, 3], [4, 5]]; +$collection = new Collection($items); +$new = $collection->unfold(); + +// $result には [1, 2, 3, 4, 5] が含まれています。 +$result = $new->toList(); +``` + +`unfold()` に callable を渡すとき、 要素が元のコレクション内の各項目から +展開されるかを制御することができます。これは、ページ制御するサービスからのデータを +得るのに便利です。 : + +``` php +$pages = [1, 2, 3, 4]; +$collection = new Collection($pages); +$items = $collection->unfold(function ($page, $key) { + // 結果のページを返す架空のウェブサービス + return MyService::fetchPage($page)->toList(); +}); + +$allPagesItems = $items->toList(); +``` + +PHP 5.5 以降を使用している場合は、 コレクション内の各アイテムを必要なだけ +複数の要素として返すために `unfold()` の中で `yield` キーワードを使用することができます。 : + +``` php +$oddNumbers = [1, 3, 5, 7]; +$collection = new Collection($oddNumbers); +$new = $collection->unfold(function ($oddNumber) { + yield $oddNumber; + yield $oddNumber + 1; +}); + +// $result には [1, 2, 3, 4, 5, 6, 7, 8] が含まれています。 +$result = $new->toList(); +``` + +`method` Cake\\Collection\\Collection::**chunk**($chunkSize) + +コレクション内の大量のアイテムを扱う場合には、一つ一つの要素を処理する代わりにバッチ処理が適しています。 +コレクションをある程度の大きさの複数の配列に分割するために、 `chunk()` 関数を使用することができます。 : + +``` php +$items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; +$collection = new Collection($items); +$chunked = $collection->chunk(2); +$chunked->toList(); // [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]] +``` + +`chunk` 関数は、例えばデータベースの結果のために、バッチ処理を行う場合、 特に便利です。 : + +``` php +$collection = new Collection($articles); +$collection->map(function ($article) { + // article のプロパティーを変更します。 + $article->property = 'changed'; + }) + ->chunk(20) + ->each(function ($batch) { + myBulkSave($batch); // この関数は、バッチごとに呼び出されます。 + }); +``` + +`method` Cake\\Collection\\Collection::**chunkWithKeys**($chunkSize) + +`chunk()` 同様、 `chunkWithKeys()` は、コレクションを小さい塊に薄切りにしますが、 +キーは保持されます。これは、連想配列を分割するのに便利です。 : + +``` php +$collection = new Collection([ + 'a' => 1, + 'b' => 2, + 'c' => 3, + 'd' => [4, 5] +]); +$chunked = $collection->chunkWithKeys(2)->toList(); +// 作成物 +[ + ['a' => 1, 'b' => 2], + ['c' => 3, 'd' => [4, 5]] +] +``` + +## フィルタリング + +`method` Cake\\Collection\\Collection::**filter**($callback) + +コレクションは、コールバック関数の結果に基づいてフィルタリングし、新しいコレクションを作成が容易になります。 +基準のコールバックに一致する要素の新しいコレクションを作成するには、 `filter()` を使用することができます。 : + +``` php +$collection = new Collection($people); +$ladies = $collection->filter(function ($person, $key) { + return $person->gender === 'female'; +}); +$guys = $collection->filter(function ($person, $key) { + return $person->gender === 'male'; +}); +``` + +`method` Cake\\Collection\\Collection::**reject**(callable $c) + +`filter()` の逆の関数は `reject()` です。このメソッドは、打ち消しのフィルタリングを行い、 +フィルター関数に一致する要素を削除します。 : + +``` php +$collection = new Collection($people); +$ladies = $collection->reject(function ($person, $key) { + return $person->gender === 'male'; +}); +``` + +`method` Cake\\Collection\\Collection::**every**($callback) + +フィルター関数で真偽のテストを行うことができます。コレクション内のすべての要素が条件を +満たしているかどうかを確認するには、 `every()` が使用できます。 : + +``` php +$collection = new Collection($people); +$allYoungPeople = $collection->every(function ($person) { + return $person->age < 21; +}); +``` + +`method` Cake\\Collection\\Collection::**some**($callback) + +フィルター関数に合致する要素が、コレクションに少なくとも1つ含まれているかどうかを +`some()` メソッドを使用して確認することができます。 : + +``` php +$collection = new Collection($people); +$hasYoungPeople = $collection->some(function ($person) { + return $person->age < 21; +}); +``` + +`method` Cake\\Collection\\Collection::**match**($conditions) + +指定したプロパティーを持つ要素のみを含んだ新しいコレクションを抽出する場合、 +`match()` メソッドを使用しましょう。 : + +``` php +$collection = new Collection($comments); +$commentsFromMark = $collection->match(['user.name' => 'Mark']); +``` + +`method` Cake\\Collection\\Collection::**firstMatch**($conditions) + +プロパティー名は、ドット区切りのパスになります。ネストされたエンティティーを横断し、 +それらに含まれる値を一致させることができます。コレクションから、最初に一致した要素が必要な場合、 +`firstMatch()` を使用することができます。 : + +``` php +$collection = new Collection($comments); +$comment = $collection->firstMatch([ + 'user.name' => 'Mark', + 'active' => true +]); +``` + +上記の通り、 `match()` と `firstMatch()` の両方は、一致させたい複数の条件を指定できます。 +また、条件は、異なるパスで、一致する複雑な条件を表現することができます。 + +## 集約 + +`method` Cake\\Collection\\Collection::**reduce**($callback, $initial) + +`map()` の反対の操作は、一般的には `reduce` です。 +この関数を使用すると、コレクション内のすべての要素から1つの結果を得ることができます。 : + +``` php +$totalPrice = $collection->reduce(function ($accumulated, $orderLine) { + return $accumulated + $orderLine->price; +}, 0); +``` + +上記の例では、 `$totalPrice` は、コレクションに含まれるすべての価格の合計になります。 +`reduce()` 関数の第二引数に、reduce 操作を開始するための初期値を渡していることに注意してください。 : + +``` php +$allTags = $collection->reduce(function ($accumulated, $article) { + return array_merge($accumulated, $article->tags); +}, []); +``` + +`method` Cake\\Collection\\Collection::**min**(string|callable $callback, $type = SORT_NUMERIC) + +プロパティーに基づいて、コレクションの最小値を抽出するには、 `min()` 関数を使用します。 +これは、コレクションから、見つかったプロパティーの最小値だけでなく完全な要素を返します。 : + +``` php +$collection = new Collection($people); +$youngest = $collection->min('age'); + +echo $youngest->name; +``` + +また、パスまたはコールバック関数を指定することで、比較するプロパティーを表現することができます。 : + +``` php +$collection = new Collection($people); +$personYoungestChild = $collection->min(function ($person) { + return $person->child->age; +}); + +$personWithYoungestDad = $collection->min('dad.age'); +``` + +`method` Cake\\Collection\\Collection::**max**(string|callable $callback, $type = SORT_NUMERIC) + +同様に、 `max()` 関数を使用すると、コレクションから最も高いプロパティー値を持つ要素を返します。 : + +``` php +$collection = new Collection($people); +$oldest = $collection->max('age'); + +$personOldestChild = $collection->max(function ($person) { + return $person->child->age; +}); + +$personWithOldestDad = $collection->max('dad.age'); +``` + +`method` Cake\\Collection\\Collection::**sumOf**($path = null) + +最後に、 `sumOf()` メソッドは、すべての要素のプロパティーの合計を返します。 : + +``` php +$collection = new Collection($people); +$sumOfAges = $collection->sumOf('age'); + +$sumOfChildrenAges = $collection->sumOf(function ($person) { + return $person->child->age; +}); + +$sumOfDadAges = $collection->sumOf('dad.age'); +``` + +`method` Cake\\Collection\\Collection::**avg**($path = null) + +コレクション内の要素の平均値を計算します。必要に応じて、平均値を生成するためのマッチャーパスや +値を抽出する関数を指定してください。 : + +``` php +$items = [ + ['invoice' => ['total' => 100]], + ['invoice' => ['total' => 200]], +]; + +// 平均値: 150 +$average = (new Collection($items))->avg('invoice.total'); +``` + +`method` Cake\\Collection\\Collection::**median**($path = null) + +要素の集合の中央値を計算します。必要に応じて、中央値を生成するためのマッチャーパスや +値を抽出する関数を指定してください。 : + +``` php +$items = [ + ['invoice' => ['total' => 400]], + ['invoice' => ['total' => 500]], + ['invoice' => ['total' => 100]], + ['invoice' => ['total' => 333]], + ['invoice' => ['total' => 200]], +]; + +// 中央値: 333 +$median = (new Collection($items))->median('invoice.total'); +``` + +### グループ化とカウント + +`method` Cake\\Collection\\Collection::**groupBy**($callback) + +コレクションの要素がプロパティーに同じ値を持つ場合、キー別にグループ化した +新しいコレクションを作ることができます。 : + +``` php +$students = [ + ['name' => 'Mark', 'grade' => 9], + ['name' => 'Andrew', 'grade' => 10], + ['name' => 'Stacy', 'grade' => 10], + ['name' => 'Barbara', 'grade' => 9] +]; +$collection = new Collection($students); +$studentsByGrade = $collection->groupBy('grade'); + +// 配列に変換すると、結果は次のようになります。 +[ + 10 => [ + ['name' => 'Andrew', 'grade' => 10], + ['name' => 'Stacy', 'grade' => 10] + ], + 9 => [ + ['name' => 'Mark', 'grade' => 9], + ['name' => 'Barbara', 'grade' => 9] + ] +] +``` + +例のごとく、動的にグループを生成するために、ネストされたプロパティーのドットで区切られたパス +または独自のコールバック関数のいずれかを指定することができます。 : + +``` php +$commentsByUserId = $comments->groupBy('user.id'); + +$classResults = $students->groupBy(function ($student) { + return $student->grade > 6 ? 'approved' : 'denied'; +}); +``` + +`method` Cake\\Collection\\Collection::**countBy**($callback) + +グループごとの出現数を知りたい場合は、 `countBy()` メソッドを使用して行うことができます。 +それは既にあなたもご存知の `groupBy` と同じ引数を受け取ります。 : + +``` php +$classResults = $students->countBy(function ($student) { + return $student->grade > 6 ? 'approved' : 'denied'; +}); + +// 配列に変換すると、結果は次のようになります。 +['approved' => 70, 'denied' => 20] +``` + +`method` Cake\\Collection\\Collection::**indexBy**($callback) + +グループ化したいプロパティーに対して要素が一意であることがわかっている一定のケースがあります。 +グループごとに単一の結果が欲しいなら、 `indexBy()` 関数を使用することができます。 : + +``` php +$usersById = $users->indexBy('id'); + +// 配列に変換すると、結果は次のようになります。 +[ + 1 => 'markstory', + 3 => 'jose_zap', + 4 => 'jrbasso' +] +``` + +`groupBy()` 関数と同じように、プロパティーパスまたはコールバックを使用することができます。 : + +``` php +$articlesByAuthorId = $articles->indexBy('author.id'); + +$filesByHash = $files->indexBy(function ($file) { + return md5($file); +}); +``` + +`method` Cake\\Collection\\Collection::**zip**($items) + +`zip()` メソッドを使用して、異なるコレクションの要素をグループ化することができます。 +このメソッドは、各コレクションから同じ位置に配置されている要素をグループ化する配列の +新しいコレクションを返します。 : + +``` php +$odds = new Collection([1, 3, 5]); +$pairs = new Collection([2, 4, 6]); +$combined = $odds->zip($pairs)->toList(); // [[1, 2], [3, 4], [5, 6]] +``` + +また、一度に複数のコレクションを zip することができます。 : + +``` php +$years = new Collection([2013, 2014, 2015, 2016]); +$salaries = [1000, 1500, 2000, 2300]; +$increments = [0, 500, 500, 300]; + +$rows = $years->zip($salaries, $increments)->toList(); +// 戻り値: +[ + [2013, 1000, 0], + [2014, 1500, 500], + [2015, 2000, 500], + [2016, 2300, 300] +] +``` + +既にお見せした通り、 `zip()` メソッドは、多次元配列を転置するのに非常に便利です。 : + +``` php +$data = [ + 2014 => ['jan' => 100, 'feb' => 200], + 2015 => ['jan' => 300, 'feb' => 500], + 2016 => ['jan' => 400, 'feb' => 600], +] + +// jan と feb のデータを取得 + +$firstYear = new Collection(array_shift($data)); +$firstYear->zip($data[0], $data[1])->toList(); + +// また PHP >= 5.6 で $firstYear->zip(...$data) + +// 戻り値 +[ + [100, 300, 400], + [200, 500, 600] +] +``` + +## ソート + +`method` Cake\\Collection\\Collection::**sortBy**($callback, $order = SORT_DESC, $sort = SORT_NUMERIC) + +コレクションの値は、カラムまたはカスタム関数に基づいて昇順または降順でソートすることができます。 +コレクションの値から新たにソートされたコレクションを作成するには、 `sortBy` を使用することができます。 : + +``` php +$collection = new Collection($people); +$sorted = $collection->sortBy('age'); +``` + +上で見たように、コレクションの値に存在するカラム名またはプロパティー名を渡すことで並べ替えることができます。 +また、代わりにドット表記を使用して、プロパティーのパスを指定することができます。 +次の例では、その著者の名前で記事をソートします。 : + +``` php +$collection = new Collection($articles); +$sorted = $collection->sortBy('author.name'); +``` + +`sortBy()` メソッドは、コレクション内の2つの異なる値を比較する値を動的に選択する抽出関数を +指定するのに十分な柔軟性があります。 : + +``` php +$collection = new Collection($articles); +$sorted = $collection->sortBy(function ($article) { + return $article->author->name . '-' . $article->title; +}); +``` + +コレクションのソート順を指定するには、昇順や降順にソートするために、2番目のパラメーターに +`SORT_ASC` や `SORT_DESC` のどちらかを指定する必要があります。 +デフォルトでは、コレクションは降順にソートされます。 : + +``` php +$collection = new Collection($people); +$sorted = $collection->sortBy('age', SORT_ASC); +``` + +時には、一貫性のある結果を得るように、比較しようとしているデータのタイプを指定する必要があります。 +この目的のためには、 `sortBy()` 関数の第3引数に次のいずれかの定数を指定する必要があります。 + +- **SORT_NUMERIC**: 数字を比較 +- **SORT_STRING**: 文字列値を比較 +- **SORT_NATURAL**: 数字を含む文字列をソート。これらの数字は、自然な方法の並び順になります。 + 例: "2" の後に "10" を表示。 +- **SORT_LOCALE_STRING**: 現在のロケールに基づいて文字列を比較。 + +デフォルトでは、 `SORT_NUMERIC` が使用されます。 : + +``` php +$collection = new Collection($articles); +$sorted = $collection->sortBy('title', SORT_ASC, SORT_NATURAL); +``` + +
    + +
    + +Warning + +
    + +  複数回ソートされたコレクションで反復処理することは高コストです。そのような計画をしている場合、 +コレクションを配列への変換を検討したり、 単純に `compile()` メソッドを使用してください。 + +
    + +## ツリーデータの操作 + +`method` Cake\\Collection\\Collection::**nest**($idPath, $parentPath) + +全てのデータが、線形に表現できるわけではありません。 +コレクションは、簡単に階層またはネストされた構造を、構築したり平坦化することができます。 +親の識別子プロパティーによって子がグループ化されるような、ネストされた構造を作成するには、 +`nest()` メソッドが簡単です。 + +この関数には、2つのパラメーターが必要です。 +1つ目は、項目の識別子を表すプロパティーです。 +2つ目のパラメーターは、親項目の識別子を表すプロパティーの名前です。 : + +``` php +$collection = new Collection([ + ['id' => 1, 'parent_id' => null, 'name' => 'Birds'], + ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds'], + ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'], + ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'], + ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish'], + ['id' => 6, 'parent_id' => null, 'name' => 'Fish'], +]); + +$collection->nest('id', 'parent_id')->toList(); +// 戻り値 +[ + [ + 'id' => 1, + 'parent_id' => null, + 'name' => 'Birds', + 'children' => [ + ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds', 'children' => []], + ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle', 'children' => []], + ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull', 'children' => []], + ] + ], + [ + 'id' => 6, + 'parent_id' => null, + 'name' => 'Fish', + 'children' => [ + ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish', 'children' => []], + ] + ] +]; +``` + +子要素は、コレクション内の各項目の `children` プロパティーの中にネストされています。 +このデータ表現のタイプは、メニューを描画したり、ツリー内の特定のレベルまでの要素を走査するのに便利です。 + +`method` Cake\\Collection\\Collection::**listNested**($order = 'desc', $nestingKey = 'children') + +`nest()` の逆の関数は `listNested()` です。このメソッドは、ツリー構造を線形構造に +戻すように平坦にすることができます。このメソッドは、2つのパラメーターを持ちます。 +1つ目は、走査モード(昇順、降順または、そのまま)であり、 +2つ目は、コレクション内の各要素の子を含むプロパティー名です。 + +前の例で構築したネストされたコレクションを入力として利用し、それを平らにすることができます。 : + +``` php +$nested->listNested()->toList(); + +// 戻り値 +[ + ['id' => 1, 'parent_id' => null, 'name' => 'Birds', 'children' => [...]], + ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds'], + ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'], + ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'], + ['id' => 6, 'parent_id' => null, 'name' => 'Fish', 'children' => [...]], + ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish'] +] +``` + +デフォルトでは、ツリーはルートから末端へと走査されます。 +また、ツリーの中の末端の要素のみを返すように指示することができます。 : + +``` php +$nested->listNested('leaves')->toList(); + +// 戻り値 +[ + ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'], + ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'], + ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish'] +] +``` + +ツリーをネストされたリストに変換すると、リスト出力の書式設定方法を設定するには、 +`printer()` メソッドを使用することができます。 : + +``` php +$nested->listNested()->printer('name', 'id', '--')->toArray(); + +// 戻り値 +[ + 3 => 'Eagle', + 4 => 'Seagull', + 5 -> '--Clown Fish', +] +``` + +`printer()` メソッドはまた、キーとまたは値を生成するためにコールバックを使用することができます。 : + +``` php +$nested->listNested()->printer( + function ($el) { + return $el->name; + }, + function ($el) { + return $el->id; + } +); +``` + +## その他のメソッド + +`method` Cake\\Collection\\Collection::**isEmpty**() + +コレクションに要素が含まれているかどうかを確認することができます。 : + +``` php +$collection = new Collection([]); +// 戻り値は true +$collection->isEmpty(); + +$collection = new Collection([1]); +// 戻り値は false +$collection->isEmpty(); +``` + +`method` Cake\\Collection\\Collection::**contains**($value) + +コレクションは、 `contains()` メソッドを使用して、ある特定の値が含まれているかどうかを、 +すぐに確認することができます。 : + +``` php +$items = ['a' => 1, 'b' => 2, 'c' => 3]; +$collection = new Collection($items); +$hasThree = $collection->contains(3); +``` + +比較は `===` 演算子を使用して実行されます。 +緩い比較タイプを行いたい場合は、 `some()` メソッドを使用することができます。 + +`method` Cake\\Collection\\Collection::**shuffle**() + +時には、コレクションでランダムな順序の値を表示したいこともあるでしょう。 +ランダムな位置にそれぞれの値を返す新しいコレクションを作成するためには、 +`shuffle` を使用してください。 : + +``` php +$collection = new Collection(['a' => 1, 'b' => 2, 'c' => 3]); + +// これは [2, 3, 1] を返します。 +$collection->shuffle()->toList(); +``` + +`method` Cake\\Collection\\Collection::**transpose**() + +コレクションを transpose (行列の転置) すると、元の列のそれぞれで作られた行を含む +新しいコレクションを取得します。 : + +``` php +$items = [ + ['Products', '2012', '2013', '2014'], + ['Product A', '200', '100', '50'], + ['Product B', '300', '200', '100'], + ['Product C', '400', '300', '200'], +] +$transpose = (new Collection($items))->transpose()->toList(); + +// 戻り値 +[ + ['Products', 'Product A', 'Product B', 'Product C'], + ['2012', '200', '300', '400'], + ['2013', '100', '200', '300'], + ['2014', '50', '100', '200'], +] +``` + +### 要素の取り出し + +`method` Cake\\Collection\\Collection::**sample**($length = 10) + +手早く統計分析を行うときにコレクションをシャッフルすることが有用であることが多いです。 +この種のタスクを行う一般的な操作は、より多くのテストが実行できるよう、コレクションから、 +いくつかのランダムな値を取り出します。例えば、いくつかの A/B テストを適用したい5ユーザーを +ランダムに選びたい場合、 `sample()` 関数を使用することができます。 : + +``` php +$collection = new Collection($people); + +// このコレクションからランダムに最大 20 ユーザーを取り出します。 +$testSubjects = $collection->sample(20); +``` + +`sample()` は、最大で最初の引数で指定した値の数だけ取り出します。 +sample を満たすためのコレクション内に十分な要素がない場合、 +ランダムな順序で全てのコレクションが返されます。 + +`method` Cake\\Collection\\Collection::**take**($length, $offset) + +コレクションのスライスを取り出したいときは、 `take()` 関数を使用してください。 +その関数は二番目の引数で渡されたポジションから開始して、最初の引数で指定した値の数だけの +新しいコレクションを作成します。 : + +``` php +$topFive = $collection->sortBy('age')->take(5); + +// ポジション 4 から始まるコレクションから5人取り出します。 +$nextTopFive = $collection->sortBy('age')->take(5, 4); +``` + +ポジションはゼロが基準なので、最初のポジション番号は `0` です。 + +`method` Cake\\Collection\\Collection::**skip**($length) + +`take()` の第二引数は、コレクションから取得する前にいくつかの要素をスキップすることができますが、 +特定のポジションの後にある残りの要素を取る方法として、同じ目的のために `skip()` を使用できます。 : + +``` php +$collection = new Collection([1, 2, 3, 4]); +$allExceptFirstTwo = $collection->skip(2)->toList(); // [3, 4] +``` + +`method` Cake\\Collection\\Collection::**first**() + +`take()` の最も一般的な用途の1つは、コレクションの最初の要素を取得することです。 +同じ目標を達成するためのショートカットメソッドとして `first()` メソッドを使用しています。 : + +``` php +$collection = new Collection([5, 4, 3, 2]); +$collection->first(); // 戻り値は 5 +``` + +`method` Cake\\Collection\\Collection::**last**() + +同様に、`last()` メソッドを使用して、コレクションの最後の要素を取得することができます。 : + +``` php +$collection = new Collection([5, 4, 3, 2]); +$collection->last(); // 戻り値は 2 +``` + +### コレクションの拡張 + +`method` Cake\\Collection\\Collection::**append**(array|Traversable $items) + +複数のコレクションから1つのコレクションを作成することができます。 +これは、さまざまなソースからデータを収集し、それを連結し、 +非常にスムーズに他のコレクション関数を適用することができます。 +`append()` メソッドは両方のソースの値を含む新しいコレクションを返します。 : + +``` php +$cakephpTweets = new Collection($tweets); +$myTimeline = $cakephpTweets->append($phpTweets); + +// 両方のソースから cakefest を含むつぶやき +$myTimeline->filter(function ($tweet) { + return strpos($tweet, 'cakefest'); +}); +``` + +`method` Cake\\Collection\\Collection::**appendItem**($value, $key) + +オプションのキーを持つアイテムをコレクションに追加できます。 +コレクション内の既存のキーを指定した場合、値は上書きされません。 : + +``` php +$cakephpTweets = new Collection($tweets); +$myTimeline = $cakephpTweets->appendItem($newTweet, 99); +``` + +`method` Cake\\Collection\\Collection::**prepend**($items) + +`prepend()` メソッドは両方のソースの値を含む新しいコレクションを返します。 : + +``` php +$cakephpTweets = new Collection($tweets); +$myTimeline = $cakephpTweets->prepend($phpTweets); +``` + +`method` Cake\\Collection\\Collection::**prependItem**($value, $key) + +オプションのキーを持つアイテムをコレクションに追加できます。 +コレクション内の既存のキーを指定した場合、値は上書きされません。 : + +``` php +$cakephpTweets = new Collection($tweets); +$myTimeline = $cakephpTweets->prependItem($newTweet, 99); +``` + +> [!WARNING] +> 異なるソースから追加するときは、両方のコレクションのいくつかのキーが同じこともありえます。 +> 例えば、2つの単純な配列を付加します。これは、 `toArray()` を使用してコレクションを +> 配列に変換するときに問題を示すことができます。あるコレクションの値で、キーを基にして +> 以前のコレクションの値を上書きしたくないなら、キーを削除して、すべての値を保持するために +> `toList()` を呼び出すことを確認してください。 + +### 要素の更新 + +`method` Cake\\Collection\\Collection::**insert**($path, $items) + +時には、2つの別々のデータの集合があり、一方の集合の要素を、 +他方のそれぞれの要素に挿入したいこともあるでしょう。もともとデータのマージや結合を +サポートしないデータソースからデータを取得する際に非常に一般的なケースです。 + +あるコレクションの各要素を別のコレクションの各要素のプロパティーに挿入することができる +`insert()` メソッドを提供します。 : + +``` php +$users = [ + ['username' => 'mark'], + ['username' => 'juan'], + ['username' => 'jose'] +]; + +$languages = [ + ['PHP', 'Python', 'Ruby'], + ['Bash', 'PHP', 'Javascript'], + ['Javascript', 'Prolog'] +]; + +$merged = (new Collection($users))->insert('skills', $languages); +``` + +配列に変換すると、 `$merged` コレクションは、次のようになります。 : + +``` php +[ + ['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']], + ['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']], + ['username' => 'jose', 'skills' => ['Javascript', 'Prolog']] +]; +``` + +`insert()` メソッドの最初のパラメーターは、要素がその位置に挿入することができるように示した +プロパティーのドット区切りのパスです。第2引数は、コレクションオブジェクトに変換することができるものです。 + +要素が順番に挿入されていることを確認してください。第2のコレクションの最初の要素は、 +第1のコレクションの最初の要素にマージされます。 + +第1のコレクションに挿入する第2のコレクションに十分な要素が存在しない場合、 +対象のプロパティーは、 `null` 値が入力されます。 : + +``` php +$languages = [ + ['PHP', 'Python', 'Ruby'], + ['Bash', 'PHP', 'Javascript'] +]; + +$merged = (new Collection($users))->insert('skills', $languages); + +// 結果 +[ + ['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']], + ['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']], + ['username' => 'jose', 'skills' => null] +]; +``` + +`insert()` メソッドは、配列の要素や `ArrayAccess` インターフェイスを実装するオブジェクトを +操作することができます。 + +### コレクションメソッドの再利用 + +コレクションのメソッドにクロージャーを使用することは、 +なすべき仕事が小さくて目的に合うと素晴らしいのですが、とてもすぐに厄介な事になります。 +異なる多くのメソッドの呼び出しが必要だったり、クロージャーメソッドの長さが数行では収まらないときに、 +より顕著になります。 + +コレクションのメソッドで使用されるロジックは、アプリケーションの複数の部分で再利用できる場合もあります。 +複雑なコレクションのロジックを抽出してクラスに分離することを検討してください。 +例えば、このような長いクロージャーを想像してください。 : + +``` php +$collection + ->map(function ($row, $key) { + if (!empty($row['items'])) { + $row['total'] = collection($row['items'])->sumOf('price'); + } + + if (!empty($row['total'])) { + $row['tax_amount'] = $row['total'] * 0.25; + } + + // コードが続きます・・・ + + return $modifiedRow; + }); +``` + +これは、別のクラスを作成することでリファクタリングすることができます。 : + +``` php +class TotalOrderCalculator +{ + public function __invoke($row, $key) + { + if (!empty($row['items'])) { + $row['total'] = collection($row['items'])->sumOf('price'); + } + + if (!empty($row['total'])) { + $row['tax_amount'] = $row['total'] * 0.25; + } + + // コードが続きます・・・ + + return $modifiedRow; + } +} + +// map() 呼び出しでロジックを使用 +$collection->map(new TotalOrderCalculator) +``` + +`method` Cake\\Collection\\Collection::**through**($callback) + +時々、コレクションメソッド呼び出しの連鎖は、特定の順序で呼び出された場合にのみ、 +アプリケーションの他の部分で再利用可能になります。これらの例では、 +便利なデータ処理の呼び出しを割り当てるために `__invoke` を実装したクラスと組み合わせて +`through()` を使用することができます。 : + +``` php +$collection + ->map(new ShippingCostCalculator) + ->map(new TotalOrderCalculator) + ->map(new GiftCardPriceReducer) + ->buffered() + ... +``` + +上記のメソッド呼び出しは、毎回繰り返す必要がないように、新しいクラスに抽出することができます。 : + +``` php +class FinalCheckOutRowProcessor +{ + public function __invoke($collection) + { + return $collection + ->map(new ShippingCostCalculator) + ->map(new TotalOrderCalculator) + ->map(new GiftCardPriceReducer) + ->buffered() + ... + } +} + +// 一度に全てのメソッドを呼び出すために through() メソッドを使用できます。 +$collection->through(new FinalCheckOutRowProcessor); +``` + +### コレクションの最適化 + +`method` Cake\\Collection\\Collection::**buffered**() + +コレクションは、多くの場合、その関数の使用を遅延して作成する操作を実行します。 +これは、関数を呼び出すことができていても、それはすぐに実行されないことを意味します。 +これは、このクラス内の多くの関数についても同様です。 +遅延評価は、コレクション内のすべての値を使用していない状況で資源を節約することができます。 +反復が早期に停止した場合、または例外や失敗事例が早期に到達したときは、 +すべての値を使用しない場合があります。 + +また、遅延評価は、いくつかの操作をスピードアップするのに役立ちます。 +次の例を考えてみましょう。 : + +``` php +$collection = new Collection($oneMillionItems); +$collection = $collection->map(function ($item) { + return $item * 2; +}); +$itemsToShow = $collection->take(30); +``` + +コレクションに遅延評価がなかったら、そのうち 30 の要素だけを見せたかったにもかかわらず、 +100 万の操作を実行しているでしょう。 +代わりに、 map の操作は、使用した 30 の要素にのみ適用しました。 +小さいコレクションでも、複数の操作を行うとき、遅延評価から利益を得ることができます。 +たとえば、 `map()` を2回と `filter()` の呼び出しなどです。 + +遅延評価にも欠点があります。早い段階でコレクションを最適化する場合は、 +複数回同じ操作を行うことができました。この例を考えてみましょう。 : + +``` php +$ages = $collection->extract('age'); + +$youngerThan30 = $ages->filter(function ($item) { + return $item < 30; +}); + +$olderThan30 = $ages->filter(function ($item) { + return $item > 30; +}); +``` + +`youngerThan30` と `olderThan30` の両方を反復する場合、 +コレクションは残念ながら二度 `extract()` 操作を実行します。 +コレクションは不変であり、遅延抽出操作は両方のフィルターのために行われることになるためです。 + +幸いにも、一つの関数で、この問題を克服することができます。 +特定操作の値を複数回再利用する場合は、 `buffered()` 関数を使用して +別のコレクションに結果をコンパイルすることができます。 : + +``` php +$ages = $collection->extract('age')->buffered(); +$youngerThan30 = ... +$olderThan30 = ... +``` + +両方のコレクションを反復処理しているときに、抽出操作を一度だけ呼び出します。 + +### 巻き戻し可能なコレクションの作成 + +`buffered()` メソッドは、巻き戻せないイテレータを複数回繰り返し可能なコレクションに +変換するのに便利です。 : + +``` php +// PHP 5.5 以上で +public function results() +{ + ... + foreach ($transientElements as $e) { + yield $e; + } +} +$rewindable = (new Collection(results()))->buffered(); +``` + +### コレクションの複製 + +`method` Cake\\Collection\\Collection::**compile**($preserveKeys = true) + +時には、別のコレクションから要素の複製を取得する必要があります。 +同時に異なる場所から同じセットを反復処理する必要がある場合に便利です。 +別のコレクションからコレクションを複製するために `compile()` メソッドを使用します。 : + +``` php +$ages = $collection->extract('age')->compile(); + +foreach ($ages as $age) { + foreach ($collection as $element) { + echo h($element->name) . ' - ' . $age; + } +} +``` diff --git a/docs/ja/core-libraries/email.md b/docs/ja/core-libraries/email.md new file mode 100644 index 0000000000..3848d4cb5c --- /dev/null +++ b/docs/ja/core-libraries/email.md @@ -0,0 +1,690 @@ +# Mailer + +`class` Cake\\Mailer\\**Mailer**(string|array|null $profile = null) + +`Mailer` は、メール送信の新しいクラスです。このクラスを使用すると、 +アプリケーションの任意の場所からメール送信できます。 + +## 基本的な使用法 + +まず最初に、クラスがロードされていることを確認する必要があります。 : + +``` php +use Cake\Mailer\Mailer; +``` + +`Mailer` をロードしたら、次のようにメールを送信することができます。 : + +``` php +$mailer = new Mailer('default'); +$mailer->setFrom(['me@example.com' => 'My Site']) + ->setTo('you@example.com') + ->setSubject('About') + ->deliver('My message'); +``` + +`Mailer` のセッターメソッドは、クラスのインスタンスを返すので、 +メソッド・チェーンでプロパティーを設定することができます。 + +`Mailer` は、受信者を定義するためのいくつかの方法があります - `setTo()` 、 `setCc()` 、 +`setBcc()` 、 `addTo()` 、 `addCc()` そして `addBcc()` 。主な違いは最初の3つは、 +すでに設定されていたものを上書きし、後者は単にそれぞれのフィールドに複数の受信者を追加することです。 : + +``` php +$mailer = new Mailer(); +$mailer->setTo('to@example.com', 'To Example'); +$mailer->addTo('to2@example.com', 'To2 Example'); +// メールの To 受信者は to@example.com と to2@example.com +$mailer->setTo('test@example.com', 'ToTest Example'); +// メールの To 受信者は test@example.com +``` + +### 送り主の選択 + +他の人々に代わってメールを送信するとき、Sender ヘッダーを使用して、 +元の送り主を定義することは良い考えです。 `setSender()` を使用して行えます。 : + +``` php +$mailer = new Mailer(); +$mailer->setSender('app@example.com', 'MyApp emailer'); +``` + +> [!NOTE] +> 別の人の代わりにメール送信するときに送り主 (envelope sender) をセットするのは良い考えです。 +> これは、配信失敗に関するメッセージの受信を防ぐことができます。 + +## 設定 + +メーラーのプロファイルとメールのトランスポートは、アプリケーションの +設定ファイルを使用して作成されます。 `Email` と `EmailTransport` は +それぞれメーラーのプロファイルとメールのトランスポート設定を定義します。 +アプリケーションのブートストラップ設定は `setConfig()` を使用することにより +`Configure` から `Mailer` および `TransportFactory` クラスに渡されます。 +プロファイルおよびトランスポートを定義することにより、アプリケーションコードにおいて +設定データの自由を保ち、メンテナンスおよび配備をより困難にする重複を避けることができます。 + +あらかじめ定義された設定をロードするには、 `setProfile()` メソッドを使用するか、 +または `Mailer` のコンストラクターに渡すことができます。 : + +``` php +$mailer = new Mailer(); +$mailer->setProfile('default'); + +// または、コンストラクター内で +$mailer = new Mailer('default'); +``` + +設定名の文字列を渡す代わりに、オプションの配列をロードすることもできます。 : + +``` php +$mailer = new Mailer(); +$mailer->setProfile(['from' => 'me@example.org', 'transport' => 'my_custom']); + +// または、コンストラクター内で +$mailer = new Mailer(['from' => 'me@example.org', 'transport' => 'my_custom']); +``` + +### 設定プロファイル + +配信プロファイルを定義すると、再利用可能なプロファイルに共通のメール設定を統合することができます。 +アプリケーションは、必要な数のプロファイルを持つことができます。次の設定キーが使用されます。 + +- `'from'`: 送信者のメールアドレスまたは配列。 `Mailer::setFrom()` を参照。 +- `'sender'`: 実際の送信者のメールアドレスまたは配列。 `Mailer::setSender()` を参照。 +- `'to'`: 宛先のメールアドレスまたは配列。 `Mailer::setTo()` を参照。 +- `'cc'`: CC のメールアドレスまたは配列。 `Mailer::setCc()` を参照。 +- `'bcc'`: BCC のメールアドレスまたは配列。 `Mailer::setCcc()` を参照。 +- `'replyTo'`: メールの返信先のメールアドレスまたは配列。 `Mailer::setReplyTo()` を参照。 +- `'readReceipt'`: 開封通知先メールアドレスまたはアドレスの配列。 `Mailer::setReadReceipt()` を参照。 +- `'returnPath'`: エラーの返信先メールアドレスまたはアドレスの配列。 `Mailer::setReturnPath()` を参照。 +- `'messageId'`: メールのメッセージID。 `Mailer::setMessageId()` を参照。 +- `'subject'`: メッセージのサブジェクト。 `Mailer::setSubject()` を参照。 +- `'message'`: メッセージ本文。レンダリングされた本文を使用する場合は、 この項目を設定しないでください。 +- `'priority'`: メールの優先度 (数値。通常は 1 から 5 で、1 が最高)。 +- `'headers'`: ヘッダー情報。 `Mailer::setHeaders()` を参照。 +- `'viewRender'`: レンダリングされた本文を使用する場合は、ビュークラス名をセット。 + `Mailer::viewRender()` を参照。 +- `'template'`: レンダリングされた本文を使用する場合は、テンプレート名をセット。 + `ViewBuilder::setTemplate()` を参照。 +- `'theme'`: テンプレートをレンダリングする際のテーマ。 `ViewBuilder::setTheme()` を参照。 +- `'layout'`: レンダリングされた本文を使用する場合、描画するレイアウトをセット。 + レイアウトなしでテンプレートをレンダリングしたい場合は、このフィールドに null をセット。 + `ViewBuilder::setTemplate()` を参照。 +- `'viewVars'`: レンダリングされた本文を使用する場合は、ビューで使用する変数の配列をセット。 + `Mailer::setViewVars()` を参照。 +- `'attachments'`: 添付ファイルの一覧。 `Mailer::setAttachments()` を参照。 +- `'emailFormat'`: メールの書式 (html, text または both) `Mailer::setEmailFormat()` を参照。 +- `'transport'`: トランスポート名。 トランスポート設定を参照。 +- `'log'`: メールヘッダーとメッセージをログに記録するログレベル。 + `true` なら LOG_DEBUG を使用します。 [Logging Levels](../core-libraries/logging#logging-levels) を参照。 + ログはスコープ名 `email` で出力されることに注意してください。 [Logging Scopes](../core-libraries/logging#logging-scopes) を参照。 +- `'helpers'`: メールテンプレート内で使用するヘルパーの配列。 `ViewBuilder::setHelpers()` 。 + +> [!NOTE] +> メールアドレスや配列で使用する上記のキーの値 (from, to, cc 他)は、関連するメソッドの第一引数として +> 渡されます。例をあげると `$mailer->setFrom('my@example.com', 'My Site')` は、設定の中では +> `'from' => ['my@example.com' => 'My Site']` と定義されます。 + +## ヘッダーの設定 + +`Mailer` の中に、自由にヘッダーをセットできます。Email を使用する際、 +独自のヘッダーにプレフィックスの `X-` をつけることを忘れないでください。 + +`Mailer::setHeaders()` と `Mailer::addHeaders()` を参照してください。 + +## テンプレートメールの送信 + +メールはしばしば単純なテキストメッセージを超えたものになります。それを容易にするために +CakePHP は、 [ビューレイヤー](../views) を使用してメールを送信することができます。 + +メールのテンプレートは、 `templates/email` と呼ばれる特別なフォルダーに置かれます。 +メールのビューは、普通のビューと同様にレイアウトとエレメントを使用します。 : + +``` php +$mailer = new Mailer(); +$mailer + ->setEmailFormat('html') + ->setTo('bob@example.com') + ->setFrom('app@domain.com') + ->viewBuilder() + ->setTemplate('welcome') + ->setLayout('fancy'); + +$mailer->deliver(); +``` + +上記は、ビューとして **templates/email/html/welcome.php** を使用し、 +レイアウトとして **templates/layout/email/html/fancy.php** を使用します。 +以下のように、マルチパートのテンプレートメールを送信することもできます。 : + +``` php +$mailer = new Mailer(); +$mailer + ->setEmailFormat('both') + ->setTo('bob@example.com') + ->setFrom('app@domain.com') + ->viewBuilder() + ->setTemplate('welcome') + ->setLayout('fancy'); + +$mailer->deliver(); +``` + +この例では、次のテンプレートファイルを使用します。 + +- **templates/email/text/welcome.php** +- **templates/layout/email/text/fancy.php** +- **templates/email/html/welcome.php** +- **templates/layout/email/html/fancy.php** + +テンプレートメールを送信する時、 `text` 、 `html` と `both` のうちの +どれかを送信オプションとして指定します。 + +`Mailer :: viewBuilder()` で取得されたビュービルダーのインスタンスを使用して、 +コントローラーで行うことと似たように、すべてのビュー関連の設定をすることができます。 + +`Mailer::setViewVars()` でビューの変数をセットできます。 : + +``` php +$mailer = new Mailer('templated'); +$mailer->setViewVars(['value' => 12345]); +``` + +または、 ビュービルダーのメソッド +`ViewBuilder::setVar()` および `ViewBuilder::setVars()` を使用できます。 + +以下のようにメールテンプレート内で使用します。 : + +``` html +

    あなたの値は次のとおりです:

    +``` + +メールでも普通のテンプレートファイルと同様にヘルパーを使用できます。 +デフォルトでは、 `HtmlHelper` のみがロードされます。 +`ViewBuilder::setHelpers()` メソッドを使うことで追加でヘルパーをロードできます。 : + +``` php +$mailer->viewBuilder()->setHelpers(['Html', 'Custom', 'Text']); +``` + +ヘルパーを設定する時は、’Html’ を含めて下さい。そうしなければ、メールテンプレートにロードされません。 + +もし、プラグインの中でテンプレートを使用してメール送信したい場合、おなじみの `プラグイン記法` +を使います。 : + +``` php +$mailer = new Mailer(); +$mailer->viewBuilder()->setTemplate('Blog.new_comment'); +``` + +上記の例は、 Blog プラグインのテンプレートとレイアウトを使用しています。 + +いくつかのケースで、プラグインで用意されたデフォルトのテンプレートを上書きしたい場合があるかもしれません。 +テーマを利用して行うことができます。 : + +``` php +$mailer->viewBuilder() + ->setTemplate('Blog.new_comment') + ->setLayout('Blog.auto_message') + ->setTheme('TestTheme'); +``` + +これは、Blog プラグインを更新せずにあなたのテーマの `new_comment` テンプレートで上書きできます。 +テンプレートファイルは、以下のパスで作成する必要があります: +**templates/plugin/TestTheme/plugin/Blog/email/text/new_comment.php** + +## 添付ファイルの送信 + +`method` Cake\\Mailer\\Mailer::**setAttachments**($attachments) + +メールにファイルを添付することができます。添付するファイルの種類や、 +宛先のメールクライアントにどのようなファイル名で送りたいのかによって幾つかの異なる書式があります。 + +1. 配列: `$email->attachments(['/full/file/path/file.png'])` は、 文字列の場合と同じ振る舞いをします。 + +2. キー付き配列:  + `$mailer->setAttachments(['photo.png' => '/full/some_hash.png'])` は、 + photo.png というファイル名で some_hash.png ファイルを添付します。 + 受信者からは、some_hash.png ではなく photo.png として見えます。 + +3. ネストした配列: + + ``` php + $mailer->setAttachments([ + 'photo.png' => [ + 'file' => '/full/some_hash.png', + 'mimetype' => 'image/png', + 'contentId' => 'my-unique-id' + ] + ]); + ``` + + 上記は、異なる mimetype と独自のコンテンツID を添付します + (添付をインラインに変換する場合にコンテンツIDをセットします)。 + mimetype と contentId はこの形式のオプションです。 + + 3.1. `contentId` を指定した時、HTML 内で `` + のようにファイルを使用できます。 + + 3.2. 添付の `Content-Disposition` ヘッダーを無効にするために + `contentDisposition` オプションを使用できます。これは、outlook を使って + ical の招待状をクライアントに送る時に便利です。 + + 3.3. `file` オプションの代わりに `data` オプションを使うと、 + ファイル本文を文字列として添付することができます。これは、ファイルパスを指定せずに + 添付することができます。 + +### アドレス検証ルールの緩和 + +`method` Cake\\Mailer\\Mailer::**setEmailPattern**($pattern) + +もし、規約に準拠していないアドレスに送信するときにバリデーションに問題がある場合、 +メールアドレスのバリデーションに使用するパターンを緩和することができます。 +いくつかの ISP に送信するときに必要になります。 : + +``` php +$mailer = new Mailer('default'); + +// 規約に準拠しないアドレスに送信できるように +// メールのパターンを緩和します。 +$mailer->setEmailPattern($newPattern); +``` + +## メッセージの即時送信 + +しばしば、メールの素早い送信が必要で、送信ごとに毎回設定のセットアップが必要ないことがあります。 +そのような目的のために `Cake\Mailer\Email::deliver()` が用意されています。 + +`Cake\Mailer\Email::config()` で設定を作成したり、 +`Email::deliver()` スタティックメソッドにすべての必要なオプションを配列で指定することができます。 +例: + +``` css +Email::deliver('you@example.com', 'Subject', 'Message', ['from' => 'me@example.com']); +``` + +このメソッドは、 「you@example.com」宛に、「me@example.com」から、件名「Subject」、 +本文「Message」でメールを送信します。 + +`deliver()` の戻り値は、 すべての設定を持つ `Cake\Mailer\Email` インスタンスです。 +もし、メールを送信せず送信前に幾つか設定変更したい場合、第5引数に `false` をセットしてインスタンスを +取得してください。 + +第3引数には、メッセージの本文か、レンダリングされた本文を使用時には変数の配列を指定します。 + +第4引数は、設定の配列や `Configure` 内の設定名の文字列を指定します。 + +もしあなたが望むのなら、サブジェクトと本文に null をセットして、すべての設定を +(配列か `Configure` を使用して) 第4引数で指定できます。 +全ての設定を知るために [設定](#email-configurations) 一覧を確認してください。 + +## CLI からのメール送信 + +シェルやタスクなどの CLI スクリプトでメールを送信するとき、Email に使用するドメイン名を +セットしなければなりません。(ホスト名が CLI 環境にないとき) ドメイン名は、メッセージ ID +のホスト名として使用されます。 : + +``` php +$mailer->setDomain('www.example.org'); +// メッセージ ID は ```` (無効) の代わりに、 +// ```` (有効) を返します。 +``` + +正しいメッセージ ID は、迷惑メールフォルダーへ振り分けられることを防ぐのに役立ちます。 + +## 再利用可能なメールの作成 + +`Mailer` は、アプリケーション全体で再利用可能なメールを作成することができます。 +また、一ヶ所に複数のメール設定を格納するために使用することができます。 +これは、コードを DRY に保つことができますし、アプリケーション内の他の領域から、 +メールの設定ノイズを除外します。 + +この例では、ユーザー関連のメールが含まれている `Mailer` を作成します。 +`UserMailer` を作成するには、 **src/Mailer/UserMailer.php** ファイルを作成します。 +ファイルの内容は次のようになります。 : + +``` php +namespace App\Mailer; + +use Cake\Mailer\Mailer; + +class UserMailer extends Mailer +{ + public function welcome($user) + { + $this + ->setTo($user->email) + ->setSubject(sprintf('Welcome %s', $user->name)) + ->viewBuilder() + ->setTemplate('welcome_mail'); // デフォルトでテンプレートはメソッドと同じ名前が使われます。 + } + + public function resetPassword($user) + { + $this + ->setTo($user->email) + ->setSubject('Reset password') + ->setViewVars(['token' => $user->token]); + } +} +``` + +この例では、2つのメソッドを作成しました。1つは、ウェルカムメールを送信するため、もう1つは、 +パスワードのリセットメールを送信するためのものです。これらの各メソッドは、 +ユーザー `Entity` を受け取り、各メールを設定するために、そのプロパティーを利用しています。 + +これで、アプリケーション内のどこからでも、ユーザー関連のメールを送信するために +`UserMailer` を使用することができます。例えば、ウェルカムメールを送信したいのであれば、 +以下のようにするとよいでしょう。 : + +``` php +namespace App\Controller; + +use Cake\Mailer\MailerAwareTrait; + +class UsersController extends AppController +{ + use MailerAwareTrait; + + public function register() + { + $user = $this->Users->newEmptyEntity(); + if ($this->request->is('post')) { + $user = $this->Users->patchEntity($user, $this->request->getData()) + if ($this->Users->save($user)) { + $this->getMailer('User')->send('welcome', [$user]); + } + } + $this->set('user', $user); + } +} +``` + +アプリケーションのコードからユーザーへのウェルカムメールの送信を完全に分離したい場合、 +`UserMailer` が `Model.afterSave` イベントを受け取ることができます。 +イベントを受け取ることによって、アプリケーションのユーザー関連のクラスは、 +メール関連のロジックや命令から完全に解放されます。 +たとえば、 `UserMailer` に以下を追加することができます。 : + +``` php +public function implementedEvents() +{ + return [ + 'Model.afterSave' => 'onRegistration' + ]; +} + +public function onRegistration(EventInterface $event, EntityInterface $entity, ArrayObject $options) +{ + if ($entity->isNew()) { + $this->send('welcome', [$entity]); + } +} +``` + +Mailer オブジェクトは、イベントリスナーとして登録され、 `onRegistration()` メソッドは +`Model.afterSave` イベントが起こるたびに呼び出されます。 : + +``` php +// Users イベントマネージャへアタッチする +$this->Users->getEventManager()->on($this->getMailer('User')); +``` + +> [!NOTE] +> イベントリスナーオブジェクトを登録する方法については、 +> [Registering Event Listeners](../core-libraries/events#registering-event-listeners) のドキュメントを参照してください。 + +## トランスポートの設定 + +メールメッセージは、トランスポートによって配信されます。さまざまなトランスポートを使用すると、 +PHP の `mail()` 関数や SMTP サーバーでメッセージを送信したり、 +デバッグが捗るようメッセージを送信しないこともできます。トランスポートを設定すると、 +アプリケーションのコードの外に、設定データを保持することができ、 +単純に設定データを変更できるのでデプロイが簡単になります。 +トランスポートの設定例は、次のようになります。 : + +``` text +// config/app.php の中で +'EmailTransport' => [ + // Mail構成の例 + 'default' => [ + 'className' => 'Mail', + ], + // SMTP構成の例 + 'gmail' => [ + 'host' => 'smtp.gmail.com', + 'port' => 587, + 'username' => 'my@gmail.com', + 'password' => 'secret', + 'className' => 'Smtp', + 'tls' => true + ] +], +``` + +`TransportFactory::setConfig()` を利用して設定することもできます。: + +``` php +use Cake\Mailer\TransportFactory; + +// STMPトランスポートを定義する +TransportFactory::setConfig('gmail', [ + 'host' => 'ssl://smtp.gmail.com', + 'port' => 465, + 'username' => 'my@gmail.com', + 'password' => 'secret', + 'className' => 'Smtp' +]); +``` + +Gmail のように、SSL SMTP サーバーを設定することができます。これを行うには、 host に +`ssl://` プレフィックスをつけて、それに伴い port の値を設定してください。 +また、 `tls` オプションを使用して TLS SMTP を有効にすることもできます。 : + +``` php +use Cake\Mailer\TransportFactory; + +TransportFactory::setConfig('gmail', [ + 'host' => 'smtp.gmail.com', + 'port' => 587, + 'username' => 'my@gmail.com', + 'password' => 'secret', + 'className' => 'Smtp', + 'tls' => true +]); +``` + +上記の設定では、メールメッセージの TLS 通信を可能にします。 + +特定のトランスポートを使用するようにメーラーを構成するには、 +`Cake\Mailer\Mailer::setTransport()` メソッドを使用するか、 +設定にトランスポートを含めることができます。 : + +``` php +// TransportFactory::setConfig() を使用して設定済の名前付きトランスポートを使用します。 +$mailer->setTransport('gmail'); + +// 構築されたオブジェクトを使用します。 +$mailer->setTransport(new \Cake\Mailer\Transport\DebugTransport()); +``` + +> [!WARNING] +> あなたのグーグルアカウントでこれを動作させるためには安全性の低いアプリへのアクセスを +> 有効にする必要があります: [安全性の低いアプリがアカウントにアクセスするのを許可する](https://support.google.com/accounts/answer/6010255) 。 + +> [!NOTE] +> [Gmail の SMTP 設定](https://support.google.com/a/answer/176600?hl=ja) 。 + +> [!NOTE] +> SSL + SMTP を使用するには、PHP のインストール時に SSL が設定されている必要があります。 + +設定オプションは、 `DSN` 文字列として指定することもできます。 +これは、環境変数を使ったり `PaaS` プロバイダーで動作する場合に便利です。 : + +``` css +TransportFactory::setConfig('default', [ + 'url' => 'smtp://my@gmail.com:secret@smtp.gmail.com:587?tls=true', +]); +``` + +DSN 文字列を使用するときは、クエリー文字列引数として任意の追加のパラメーターやオプションを +定義することができます。 + +`static` Cake\\Mailer\\Mailer::**drop**($key) + +設定が完了すると、トランスポートを変更することはできません。 +トランスポートを変更するためには、まずこれを取り消してから再設定する必要があります。 + +### 独自のトランスポートの作成 + +SwiftMailer のような他のメールシステムを使うために独自のトランスポートを作成することができます。 +トランスポートを作るためには、(Example という名前のトランスポートの場合)最初に +**src/Mailer/Transport/ExampleTransport.php** ファイルを作成してください。 +作成開始時点のファイルは次のようになります。 : + +``` php +namespace App\Mailer\Transport; + +use Cake\Mailer\AbstractTransport; +use Cake\Mailer\Message; + +class ExampleTransport extends AbstractTransport +{ + public function send(Message $message): array + { + // 何かをします。 + } +} +``` + +独自のロジックで、 `send(Mailer $mailer)` メソッドを実装してください。 + +## Mailerを利用しないメール送信 + +`Mailer` は、`Cake\Mailer\Message`、`Cake\Mailer\Renderer`、 +および`Cake\Mailer\AbstractTransport` 間の橋渡しをするより高いレベルの抽象化クラスであり、 +メールの設定と配信を簡単にするクラスです。 + +必要であれば、これらのクラスを `Mailer` で直接使用することもできます。 + +例 : + +``` php +$render = new \Cake\Mailer\Renderer(); +$render->viewBuilder() + ->setTemplate('custom') + ->setLayout('sparkly'); + +$message = new \Cake\Mailer\Message(); +$message + ->setFrom('admin@cakephp.org') + ->setTo('user@foo.com') + ->setBody($render->render()); + +$transport = new \Cake\Mailer\Transport\MailTransport(); +$result = $transport->send($message); +``` + +`Renderer` の使用をスキップして、メッセージ本文を直接設定することもできます +`Message::setBodyText()` および `Message::setBodyHtml()` メソッドを使用します。 + +## メールのテスト + +メールをテストするためには、テストケースに `Cake\TestSuite\EmailTrait` を追加します。 +`MailerTrait` は、PHPUnitのフックを使用して、アプリケーションのメールトランスポートをプロキシに置き換えます。 +プロキシはメールのメッセージをインターセプトし、配信されるメールに対してアサーションを実行することを可能とします。 + +テストケースにトレイトを追加してメールのテストを開始します。 +メールでURLを生成する必要がある場合はルートを読み込みます。 : + +``` php +namespace App\Test\TestCase\Mailer; + +use App\Mailer\WelcomeMailer; +use App\Model\Entity\User; + +use Cake\TestSuite\EmailTrait; +use Cake\TestSuite\TestCase; + +class WelcomeMailerTestCase extends TestCase +{ + use EmailTrait; + + public function setUp(): void + { + parent::setUp(); + $this->loadRoutes(); + } +} +``` + +新しいユーザーが登録したときにウェルカムメールを配信するメーラーがあるとします。 +件名と本文にユーザーの名前が含まれていることを確認したい場合は、以下のようにします。 : + +``` php +// WelcomeMailerTestCase クラスにて +public function testName() +{ + $user = new User([ + 'name' => 'Alice Alittea', + 'email' => 'alice@example.org', + ]); + $mailer = new WelcomeMailer(); + $mailer->send('welcome', [$user]); + + $this->assertMailSentTo($user->email); + $this->assertMailContainsText('こんにちは。' . $user->name); + $this->assertMailContainsText('CakePHPへようこそ!'); +} +``` + +## アサーションメソッド + +`Cake\TestSuite\EmailTrait` トレイトは次のアサーションを提供します。 : + +``` php +// 期待した数のメールが送信されたことをアサート +$this->assertMailCount($count); + +// メールが送信されていないことをアサート +$this->assertNoMailSent(); + +// アドレスに対してメールが送信されたことをアサート +$this->assertMailSentTo($address); + +// アドレスからメールが送信されたことをアサート +$this->assertMailSentFrom($address); + +// メールに期待した内容が含まれていることをアサート +$this->assertMailContains($contents); + +// メールに期待したHTMLコンテンツが含まれていることをアサート +$this->assertMailContainsHtml($contents); + +// メールに期待したテキストコンテンツが含まれていることをアサート +$this->assertMailContainsText($contents); + +// メールにメールゲッター内の期待値が含まれていることをアサート(例:"subject") +$this->assertMailSentWith($expected, $parameter); + +// 特定のインデックスのメールがアドレスに対して送信されたことをアサート +$this->assertMailSentToAt($at, $address); + +// 特定のインデックスのメールがアドレスから送信されたことをアサート +$this->assertMailSentFromAt($at, $address); + +// 特定のインデックスのメールに期待した内容が含まれていることをアサート +$this->assertMailContainsAt($at, $contents); + +// 特定のインデックスのメールに期待したHTMLコンテンツが含まれていることをアサート +$this->assertMailContainsHtmlAt($at, $contents); + +// 特定のインデックスのメールに期待したテキストコンテンツが含まれていることをアサート +$this->assertMailContainsTextAt($at, $contents); + +// メールに添付ファイルが含まれていることをアサート +$this->assertMailContainsAttachment('test.png'); + +// 特定のインデックスのメールにメールゲッター内の期待値が含まれていることをアサート(例:"subject") +$this->assertMailSentWithAt($at, $expected, $parameter); +``` diff --git a/docs/ja/core-libraries/events.md b/docs/ja/core-libraries/events.md new file mode 100644 index 0000000000..a05aeaae28 --- /dev/null +++ b/docs/ja/core-libraries/events.md @@ -0,0 +1,523 @@ +# イベントシステム + +メンテナンス性の高いアプリケーションの創造は、科学でもあり芸術でもあります。 +良く知られていることですが、高い品質のコードを保持するための鍵は、 +オブジェクトが疎結合すると同時に、高い凝集度も合わせ持つということです。 +結合が疎であるということが、あるクラスがいかに少ししか外部のオブジェクトに "束縛されて" おらず、 +どの程度そのクラスがそれらの外部オブジェクトに依存しているかの指標となる一方で、 +高い凝集度は、クラスの全てのメソッドおよびプロパティーがそのクラス自身と強く関連を持ちつつ +他のオブジェクトがやるべき仕事をしようとはしないということを意味します。 + +凝集度が失われ、クラスの結合度が増加してしまわないように、依存関係をガチガチにコードすることなく +システム内の別の箇所とクリーンにやりとりすることが必要な場面も確かにあります。 +Observer パターンを使用すると、オブジェクトがイベントを発生させることができ、 +無名のリスナーに対して内部状態の変化について通知することができるので、 +この目的を達成するのに便利なパターンです。 + +Observer パターンにおけるリスナーは、そのようなイベントを受信することが可能で、 +それらに基づいて振る舞いを選択したり、サブジェクトの状態を変更したり、 +単に何かを記録したりします。もしあなたがすでに JavaScript を使っていたなら、 +すでにイベント駆動プログラミングに親しんでいることでしょう。 + +CakePHP は、jQuery などの一般的な JavaScript フレームワークにおいてイベントがトリガーされ +管理される方法のいくつかの側面をエミュレートします。CakePHP の実装においてイベントオブジェクトは、 +全てのリスナーに行き渡ります。イベントオブジェクトは、イベントに関する情報を持ち、任意のポイントで +イベントの伝播を止めることができます。リスナーは自分自身を登録することが可能であるか、もしくは +他のオブジェクトにそのタスクを委任することができ、まだ実行されていないコールバックのために、 +状態とイベント自体を変更する機会を持ちます。 + +イベントシステムは、モデル・ビヘイビアー・コントローラー・ビュー・ヘルパーのコールバックの心臓部に +あたります。もし、あなたがこれらをいつも使用しているなら、すでにある程度、CakePHP のイベントに +親しんでいることになります。 + +## イベントの使用例 + +ショッピングカートプラグインを構築していて、注文ロジックの操作を行いたい場合を考えてみましょう。 +ユーザーへのメール送信や在庫から商品を減らすことをショッピングのロジックに含めたくはありません。 +しかし、これらはプラグインを使用する人にとって重要なタスクです。もし、イベントを使用しないなら、 +これをモデルにビヘイビアーを適用したり、コントローラーにコンポーネントを追加することで実装しようと +するかもしれません。そうすることは、外部でビヘイビアーをロードしたりプラグインコントローラーに +フックを取り付けるためにコードを用意しなくてはならないので、多くの時間を費やすことを意味します。 + +一方、コードの関心事を明確に分離するためにイベントを使用することができます。そして、 +イベントを使用しているプラグインにフックする関心事を追加することができます。 +例えば、カートプラグイン中に、注文の作成を処理する Order モデルがあるとします。 +このアプリケーションで注文が作成される合間に通知したい。そんな時、 Order モデルを +綺麗に保つためにイベントを使用できます。 : + +``` php +// Cart/Model/Table/OrdersTable.php +namespace Cart\Model\Table; + +use Cake\Event\Event; +use Cake\ORM\Table; + +class OrdersTable extends Table +{ + public function place($order) + { + if ($this->save($order)) { + $this->Cart->remove($order); + $event = new Event('Model.Order.afterPlace', $this, [ + 'order' => $order + ]); + $this->getEventManager()->dispatch($event); + + return true; + } + + return false; + } +} +``` + +上記のコードは、注文が作成されていることをアプリケーションの別のパーツに通知することを簡単にできます。 +例えば、メール通知の送信、在庫の更新、ログに関する分析、その他のタスクのような関心ごとに注目した +独立したオブジェクトの中で実行することができます。 + +## イベントマネージャーへのアクセス + +CakePHP の中でイベントはイベントマネージャーに対して動作します。イベントマネージャーは、 +全てのモデル、ビュー、コントローラーの中で `getEventManager()` を使用して取得されます。 : + +``` php +$events = $this->getEventManager(); +``` + +ビューやコントローラーで共有している各モデルは独立したイベントマネージャーを持ちます。 +これは、モデルイベントを自分自身に含むことができ、もし必要であれば、 +ビューで作成されたイベントをコンポーネントやコントローラーに対して作用させることができます。 + +### グローバルイベントマネージャー + +インスタンスレベルのイベントマネージャーに加えて、 CakePHP はアプリケーション内で起こる +任意のイベントを受け取ることができるグローバルイベントマネージャーを提供します。 +これは、面倒で難しい具体的なインスタンスに対してリスナーをアタッチする時に便利です。 +グローバルマネージャーは `Cake\Event\EventManager` のシングルトンインスタンスです。 +グローバルディスパッチャーに登録されたリスナーは、同じ優先度でインスタンスのリスナーよりも前に実行されます。 +静的メソッドを使用してグローバルマネージャーにアクセスすることができます。 : + +``` php +// イベントの前に実行される任意の設定ファイルやコードの一部の中で +use Cake\Event\EventManager; + +EventManager::instance()->on( + 'Model.Order.afterPlace', + $aCallback +); +``` + +重要なことは、同じ名前で異なる内容を持っているイベントがあることを考慮すべきということです。 +そして、グローバルに割り当てられた任意の機能の中のバグを防ぐためにイベントオブジェクトを +チェックすることがいつも必要です。グローバルマネージャーを使用する柔軟性により、 +複雑さが増すことに注意してください。 + +`Cake\Event\EventManager::dispatch()` メソッドは、引数として +イベントオブジェクトを受け取り、すべてのリスナーとコールバックにこのオブジェクトを +伝達させながら通知します。リスナーは、 `afterPlace` イベントの余分なロジックをすべて処理し、 +時間を記録したり、メールを送信したり、別のオブジェクトにユーザー統計を更新したり、必要に応じて +オフラインタスクに委任することもできます。 + +### イベントの追跡 + +特定の `EventManager` から実行されるイベントのリストを維持するために、 +イベントの追跡を有効にすることができます。これを行うには、マネージャーに +`Cake\Event\EventList` を登録してください。 : + +``` php +EventManager::instance()->setEventList(new EventList()); +``` + +マネージャーでイベントを実行した後は、イベントリストからそれを取得することができます。 : + +``` php +$eventsFired = EventManager::instance()->getEventList(); +$firstEvent = $eventsFired[0]; +``` + +追跡は、イベントリストを削除したり、 `Cake\Event\EventManager::trackEvents(false)` +を呼ぶことで無効にできます。 + +## コアイベント + +アプリケーションが受け取れるフレームワーク内のコアイベントが沢山あります。 +CakePHP の各レイヤーで、アプリケーションで使用できるイベントを発行します。 + +- [ORM/Model イベント](../orm/table-objects#table-callbacks) +- [コントローラーイベント](../controllers#controller-life-cycle) +- [ビューイベント](../views#view-events) + +## リスナーの登録 + +リスナーは、イベントのためにコールバックを登録するための好ましい方法です。 +これは、コールバックをいくつか登録したいとあなたが望む任意のクラスに対し +`Cake\Event\EventListenerInterface` インターフェイスを +実装することによって実現されます。 +このインターフェイスを実装しているクラスは、 `implementedEvents()` メソッドを提供し、 +クラスが処理するすべてのイベント名を持つ連想配列を返す必要があります。 + +それでは先ほどの例につづき、ユーザーの購入履歴を計算しグローバルサイトの統計をまとめる役割を果たす +UserStatistic クラスがあると仮定しましょう。これは、リスナークラスを使うとても良い機会です。 +一ヶ所に統計ロジックを集中することでき、イベントに対して必要な反応ができます。 +`UserStatistics` リスナーは以下のように開始します。 : + +``` php +use Cake\Event\EventListenerInterface; + +class UserStatistic implements EventListenerInterface +{ + public function implementedEvents(): array + { + return [ + 'Model.Order.afterPlace' => 'updateBuyStatistic', + ]; + } + + public function updateBuyStatistic($event) + { + // 統計値を更新するコード + } +} + +// UserStatistic オブジェクトを Order のイベントマネージャーに追加 +$statistics = new UserStatistic(); +$this->Orders->getEventManager()->on($statistics); +``` + +上記のコードを見るとわかるように、 `on()` 関数は `EventListener` インターフェイスの +インスタンスを受け取ります。内部的には、イベント·マネージャーは `implementedEvents()` +メソッドを使用して、正しいコールバックを追加します。 + +### 無名リスナーの登録 + +イベントリスナーオブジェクトがリスナーを実装するために一般的に良いやり方ですが、 +イベントリスナーとして任意の `callable` をバインドすることもできます。例えば、 +ログファイルに注文を書き込みたい場合、そのためには無名関数が使えます。 : + +``` php +use Cake\Log\Log; + +$this->Orders->getEventManager()->on('Model.Order.afterPlace', function ($event) { + Log::write( + 'info', + 'A new order was placed with id: ' . $event->getSubject()->id + ); +}); +``` + +無名関数に加えてその他の PHP がサポートする呼び出し可能な形式を使用することもできます。 : + +``` php +$events = [ + 'email-sending' => 'EmailSender::sendBuyEmail', + 'inventory' => [$this->InventoryManager, 'decrement'], +]; +foreach ($events as $callable) { + $eventManager->on('Model.Order.afterPlace', $callable); +} +``` + +特定のイベントをトリガーしないプラグインを操作するときは、 +デフォルトのイベントにイベントリスナーを活用することができます。 +ユーザーからのお問い合わせフォームを扱う「UserFeedback」プラグインを例にあげましょう。 +アプリケーションからは、フィードバックレコードが保存されたことを検知し、最終的には、 +それに基づいて行動したいと思います。グローバルな `Model.afterSave` イベントを +受け取ることができことができます。ただし、より直接的なアプローチを取ることができ、 +本当に必要とするイベントのみ受け取ることができます。 : + +``` php +// 保存操作の前に、以下を作成することが +// できます。 config/bootstrap.php で、 +use Cake\ORM\TableRegistry; +// メールを送信する場合 +use Cake\Mailer\Email; + +FactoryLocator::get('Table')->get('ThirdPartyPlugin.Feedbacks') + ->getEventManager() + ->on('Model.afterSave', function($event, $entity) + { + // 例えば、管理者のメールを送信することができます。 + // 3.4 より前は from()/to()/subject() メソッドを使用してください。 + $email = new Email('default'); + $email->setFrom(['info@yoursite.com' => 'Your Site']) + ->setTo('admin@yoursite.com') + ->setSubject('New Feedback - Your Site') + ->send('Body of message'); + }); +``` + +リスナーオブジェクトをバインドするために、これと同じアプローチを使用することができます。 + +### 既存のリスナーとの対話 + +いくつかのイベントリスナーが登録されていると仮定すると、特定のイベントパターンの有無を、 +ある動作の基礎として使用できます。 : + +``` php +// EventManager にリスナーを追加 +$this->getEventManager()->on('User.Registration', [$this, 'userRegistration']); +$this->getEventManager()->on('User.Verification', [$this, 'userVerification']); +$this->getEventManager()->on('User.Authorization', [$this, 'userAuthorization']); + +// アプリケーションのどこか別の場所で +$events = $this->getEventManager()->matchingListeners('Verification'); +if (!empty($events)) { + // 'Verification' イベントリスナーが存在する場合のロジックを実行。 + // 例えば、存在するリスナーを削除。 + $this->getEventManager()->off('User.Verification'); +} else { + // 'Verification' イベントリスナーが存在しない場合のロジックを実行。 +} +``` + +> [!NOTE] +> `matchingListeners` メソッドに渡されたパターンは、大文字と小文字が区別されます。 + +### 優先順位の設定 + +いくつかのケースでは、リスナーを実行する順番を制御したいこともあるでしょう。 +例としてユーザーの統計情報の場合についてもう一度考えて見ましょう。このリスナーが +スタックの最後に呼び出されることが理想的です。リスナースタックの最後にそれを呼び出すことによって、 +イベントがキャンセルされなかったことや、他のリスナーが例外を発生させていないことを確認できます。 +他のリスナーがサブジェクトやイベントオブジェクトを変更した場合、 +オブジェクトの最終状態を得ることができます。 + +優先順位は、リスナーに追加する際に整数値として定義されます。数字が大きいほど、 +後に実行されるメソッドです。すべてのリスナーのデフォルトの優先度は +`10` に設定されています。もしメソッドをもっと早く実行したい場合は、このデフォルト値よりも +小さい任意の値を使用することで動作します。逆に、コールバックを他よりもあとに実行させたいなら、 +`10` よりも大きい数字を使用してください。 + +2つのコールバックが同じ優先順位キューに割り当てられるた場合は、追加された順番に実行されます。 +コールバックの優先順位を設定するためには `on()` メソッドを用い、 リスナーの優先順位を +設定するためには `implementedEvent()` 関数内での宣言を行います。 : + +``` php +// コールバックの優先順位を設定 +$callback = [$this, 'doSomething']; +$this->getEventManager()->on( + 'Model.Order.afterPlace', + ['priority' => 2], + $callback +); + +// リスナーの優先順位を設定 +class UserStatistic implements EventListenerInterface +{ + public function implementedEvents() + { + return [ + 'Model.Order.afterPlace' => [ + 'callable' => 'updateBuyStatistic', + 'priority' => 100 + ], + ]; + } +} +``` + +ご覧のとおり、 `EventListener` オブジェクトにおける主な違いは、 +呼び出し可能なメソッドと優先順位を指定するために配列を使用する必要があるということです。 +`callable` キーはマネージャーがクラス内のどのような関数が呼ばれるべきかを知るために +読み込むであろう、特別な配列エントリーです。 + +### イベントデータを関数のパラメーターとして取得 + +イベントがそのコンストラクターに渡されたデータを持っている場合、渡されたデータは、 +リスナーの引数に変換されます。ビュー層の afterRender のコールバックの例です。 : + +``` php +$this->getEventManager() + ->dispatch(new Event('View.afterRender', $this, ['view' => $viewFileName])); +``` + +`View.afterRender` コールバックのリスナーは、次のシグネチャを持つ必要があります。 : + +``` javascript +function (Event $event, $viewFileName) +``` + +イベントコンストラクターに渡される各値は、データ配列に表示されている順序で関数のパラメーターに変換されます。 +連想配列を使用する場合は、 `array_values` の結果が、関数の引数の順序を決定します。 + +> [!NOTE] +> 2.x とは異なり、リスナーの引数にイベントデータを変換することは、デフォルトの振る舞いで、 +> 無効にすることはできません。 + +## イベントのディスパッチ + +一度、イベントマネージャーのインスタンスを取得すると、 +`Cake\Event\EventManager::dispatch()` メソッドを使って +イベントをディスパッチできます。このメソッドは `Cake\Event\Event` +クラスのインスタンスを受け取ります。さぁ、イベントをディスパッチしてみましょう。 : + +``` php +// イベントをディスパッチする前に、イベントリスナーをインスタンス化する必要があります。 +// 新しいイベントの作成とディスパッチ。 +$event = new Event('Model.Order.afterPlace', $this, [ + 'order' => $order +]); +$this->getEventManager()->dispatch($event); +``` + +`Cake\Event\Event` は、コンストラクターに3つの引数を受け取ります。 +最初のものはイベント名で、読みやすくすると同時にできるだけ唯一性を維持することを心掛けてください。 +次のような規則をお勧めします: レイヤーレベルで発生する一般的なイベントのための +`Layer.eventName` (例えば `Controller.startup`, `View.beforeRender`) 、そして、 +あるレイヤーの特定のクラスで発生するイベントのための `Layer.Class.eventName` 、 +例えば `Model.User.afterRegister` や `Controller.Courses.invalidAccess` です。 + +2番目の引数は `subject` です。サブジェクトとはイベントに関連付けられているオブジェクトを意味し、 +通常それ自身に関するイベントをトリガーしているものと同じクラスであり、 +`$this` の使用が一般的なケースとなります。とは言え、コンポーネントが +コントローラーイベントをトリガーしたりもできます。サブジェクトクラスは重要です。 +なぜなら、リスナーがオブジェクトのプロパティーへの即時アクセスを取得し、 +それらを動的に検査したり変更するチャンスを持てるようになるからです。 + +最後に、3番目の引数はイベントのパラメーターです。これは、リスナーがそれに基づいて +行動できるようにするための任意のデータです。これは、どのような型の引数でも指定できますが、 +検査を容易にするために連想配列を渡すことをお勧めします。 + +`Cake\Event\EventManager::dispatch()` メソッドは、引数として +イベントオブジェクトを受け取り、すべてのリスナーとコールバックにこのオブジェクトを +伝達させながら通知します。 + +### イベントの中止 + +DOM イベントのように、追加のリスナーへ通知されることを防ぐためにイベントを中止したいときが +あるかもしれません。それ以上処理を進めることができないことをコードが検出した時に保存操作を +中止できるモデルのコールバック (例えば beforeSave) の動作から分かります。 + +イベントを中止するためには、コールバックで `false` を返すか、またはイベントオブジェクトで +`stopPropagation()` メソッドを呼び出すかのいずれかを行うことができます。 : + +``` php +public function doSomething($event) +{ + // ... + return false; // イベントを中止 +} + +public function updateBuyStatistic($event) +{ + // ... + $event->stopPropagation(); +} +``` + +イベントの中止は追加のコールバックが呼び出される事を妨げます。それに加え、イベントを発生させるコードは、 +イベントが中止させられるかそうでないかを元に振る舞いを変えることができます。一般的に、イベントの +'後 (*after*)' に中止することに意味はありませんが、 イベントの '前 (*before*)' に中止する事は、 +全ての操作が起こる事を防止するためにしばしば使用されます。 + +イベントが中止されたかどうかを確認するには、イベントオブジェクトの `isStopped()` +メソッドを呼び出します。 : + +``` php +public function place($order) +{ + $event = new Event('Model.Order.beforePlace', $this, ['order' => $order]); + $this->getEventManager()->dispatch($event); + if ($event->isStopped()) { + return false; + } + if ($this->Orders->save($order)) { + // ... + } + // ... +} +``` + +上記の例では、イベントが `beforePlace` の処理の間に中止した場合は、注文内容は保存されません。 + +### イベントの結果の取得 + +コールバックが null や false 以外の値を返すたびに、それはイベントオブジェクトの +`$result` プロパティーに格納されます。これは、コールバックでイベントの実行を変更したい時に便利です。 +再び `beforePlace` を例にとり、コールバックが `$order` データを変更してみましょう。 + +イベントの結果は、イベントオブジェクトの result プロパティーを直接用いるか、 +またはコールバック自体の値を返すことで変更できます。 : + +``` php +// リスナーコールバック +public function doSomething($event) +{ + // ... + $alteredData = $event->getData('order') + $moreData; + + return $alteredData; +} + +// 別のリスナーコールバック +public function doSomethingElse($event) +{ + // ... + $event->setResult(['order' => $alteredData] + $this->result()); +} + +// イベントの結果を使用 +public function place($order) +{ + $event = new Event('Model.Order.beforePlace', $this, ['order' => $order]); + $this->getEventManager()->dispatch($event); + if (!empty($event->getResult()['order'])) { + $order = $event->getResult()['order']; + } + if ($this->Orders->save($order)) { + // ... + } + // ... +} +``` + +任意のイベントオブジェクトのプロパティーを変更し、次のコールバックに渡された +新たなデータを有することが可能です。ほとんどの場合、オブジェクトが +イベントデータまたは結果として提供され、オブジェクトを直接変更することは、 +参照が同じに保たれ、すべてのコールバック呼び出しで変更が共有されるため、 +最適なソリューションです。 + +### コールバック及びリスナーの削除 + +何らかの理由でイベントマネージャーから任意のコールバックを削除したい場合は、 +`Cake\Event\EventManager::off()` を引数の最初の2つのパラメーターを +追加のときと同様の用い方で呼び出すだけで良いです。 : + +``` php +// 関数の追加 +$this->getEventManager()->on('My.event', [$this, 'doSomething']); + +// 関数の削除 +$this->getEventManager()->off('My.event', [$this, 'doSomething']); + +// 無名関数の追加 +$myFunction = function ($event) { ... }; +$this->getEventManager()->on('My.event', $myFunction); + +// 無名関数の削除 +$this->getEventManager()->off('My.event', $myFunction); + +// EventListener の追加 +$listener = new MyEventLister(); +$this->getEventManager()->on($listener); + +// リスナーから単一のイベントキーを削除 +$this->getEventManager()->off('My.event', $listener); + +// リスナーで実装された全てのコールバックを削除 +$this->getEventManager()->off($listener); +``` + +イベントはあなたのアプリケーション内の関心事を分離させる偉大な方法であり、 +クラスに凝集と疎結合の両方をもたらします。イベントは、アプリケーションコードの疎結合や +拡張可能なプラグインの作成に利用できます。 + +偉大な力には偉大な責任が伴うことを心に留めておいてください。イベントを利用すればするほど、 +デバッグが難しくなり、追加の結合テストが必要になります。 + +## その他の情報 + +- [ビヘイビアー](../orm/behaviors) +- [コンポーネント](../controllers/components) +- [ヘルパー](../views/helpers) +- [Testing Events](../development/testing#testing-events) diff --git a/docs/ja/core-libraries/file-folder.md b/docs/ja/core-libraries/file-folder.md new file mode 100644 index 0000000000..7edbcd1626 --- /dev/null +++ b/docs/ja/core-libraries/file-folder.md @@ -0,0 +1,249 @@ +# Folder & File + +::: info Deprecated in version 4.0 +`File` クラスと `Folder` は バージョン5.0で削除されます。 `SplFileInfo` や `SplFileObject` などの SPL クラス、 および、`RecursiveDirectoryIterator`, `RecursiveRegexIterator` などのような イテレータクラスを使用してください。 +::: + +Folder と File ユーティリティは、ファイルの読み書きやフォルダー内のファイル名一覧の取得、 +その他ディレクトリーに関連するタスクにおいて便利なクラスです。 + +## 基本的な使用法 + +クラスがロードされていることを確認してください。 : + +``` php +use Cake\Filesystem\Folder; +use Cake\Filesystem\File; +``` + +すると、新しいフォルダーインスタンスをセットアップすることができるようになります。 : + +``` php +$dir = new Folder('/path/to/folder'); +``` + +そして、そのフォルダー内から *.php* の拡張子が付いたファイルを +正規表現を使って検索できます。 : + +``` php +$files = $dir->find('.*\.php'); +``` + +これでファイルをループしたり、読み込み、内容の書き込み・追記、 +ファイルの削除などが行えるようになります。 : + +``` php +foreach ($files as $file) { + $file = new File($dir->pwd() . DS . $file); + $contents = $file->read(); + // $file->write('このファイルの内容を上書きします'); + // $file->append('このファイルの最後に追記します。'); + // $file->delete(); // このファイルを削除します + $file->close(); // 終了時にファイルをクローズしましょう +} +``` + +## Folder API + +`class` Cake\\Filesystem\\**Folder**(string $path = false, boolean $create = false, string|boolean $mode = false) + +``` php +// 0755 のパーミッションで新しいフォルダーを作成します +$dir = new Folder('/path/to/folder', true, 0755); + + +フォルダーの現在のパス。 +:php:meth:`Folder::pwd()` は同じ情報を返します。 + + +リストを取得する際に、名前によるソートを実行するかどうか。 + + +フォルダー作成時のモード。デフォルトは ``0755`` です。 +Windows マシンでは何も影響しません。 +``` + +`static` Cake\\Filesystem\\Folder::**addPathElement**(string $path, string $element) + +\$path と \$element の間に適切なスラッシュを加えて返します。 : + +``` php +$path = Folder::addPathElement('/a/path/for', 'testing'); +// $path は /a/path/for/testing となります +``` + +\$element は、配列も指定できます。 : + +``` php +$path = Folder::addPathElement('/a/path/for', ['testing', 'another']); +// $path は /a/path/for/testing/another となります +``` + +`method` Cake\\Filesystem\\Folder::**cd**( $path ) + +`method` Cake\\Filesystem\\Folder::**chmod**(string $path, integer $mode = false, boolean $recursive = true, array $exceptions = []) + +`method` Cake\\Filesystem\\Folder::**copy**(array|string $options = []) + +`static` Cake\\Filesystem\\Folder::**correctSlashFor**(string $path) + +\$path に与えるべき適切なスラッシュを返します。 +(Windows のパスは '\\ で、その他のパスは '/') + +`method` Cake\\Filesystem\\Folder::**create**(string $pathname, integer $mode = false) + +`method` Cake\\Filesystem\\Folder::**delete**(string $path = null) + +`method` Cake\\Filesystem\\Folder::**dirsize**() + +`method` Cake\\Filesystem\\Folder::**errors**() + +`method` Cake\\Filesystem\\Folder::**find**(string $regexpPattern = '.*', boolean $sort = false) + +> [!NOTE] +> フォルダーの find メソッドと findRecursive メソッドは、ファイルのみを検索します。 +> フォルダーとファイルを取得したい場合は、 `Folder::read()` もしくは +> `Folder::tree()` 参照してください。 + +`method` Cake\\Filesystem\\Folder::**findRecursive**(string $pattern = '.*', boolean $sort = false) + +`method` Cake\\Filesystem\\Folder::**inCakePath**(string $path = '') + +`method` Cake\\Filesystem\\Folder::**inPath**(string $path = '', boolean $reverse = false) + +`static` Cake\\Filesystem\\Folder::**isAbsolute**(string $path) + +与えられた \$path が絶対パスであれば `true` を返します。 + +`static` Cake\\Filesystem\\Folder::**isSlashTerm**(string $path) + +与えられた \$path がスラッシュで終了していれば true を返します。(つまり、スラッシュ終端) : + +``` php +$result = Folder::isSlashTerm('/my/test/path'); +// $result = false +$result = Folder::isSlashTerm('/my/test/path/'); +// $result = true +``` + +`static` Cake\\Filesystem\\Folder::**isWindowsPath**(string $path) + +与えられた \$path が Windows のパスであれば `true` を返します。 + +`method` Cake\\Filesystem\\Folder::**messages**() + +`method` Cake\\Filesystem\\Folder::**move**(array $options) + +`static` Cake\\Filesystem\\Folder::**normalizePath**(string $path) + +与えられた \$path を適切なスラッシュに調整して返します。 +(Windows のパスは '\\ で、その他のパスは '/') + +`method` Cake\\Filesystem\\Folder::**pwd**() + +`method` Cake\\Filesystem\\Folder::**read**(boolean $sort = true, array|boolean $exceptions = false, boolean $fullPath = false) + +`method` Cake\\Filesystem\\Folder::**realpath**(string $path) + +`static` Cake\\Filesystem\\Folder::**slashTerm**(string $path) + +引数の \$path に (Windows や、その他の OS で正しい) 終端のスラッシュを付けたパスを返します。 + +`method` Cake\\Filesystem\\Folder::**tree**(null|string $path = null, array|boolean $exceptions = true, null|string $type = null) + +## File API + +`class` Cake\\Filesystem\\**File**(string $path, boolean $create = false, integer $mode = 755) + +``` php +// 0644 のパーミッションで新しいファイルを作成します +$file = new File('/path/to/file.php', true, 0644); + + +ファイルが属するフォルダー・オブジェクト。 + + +拡張子付きのファイル名。 +拡張子なしのファイル名を返す :php:meth:`File::name()` とは異なります。 + + +ファイル情報の配列。 +代わりに :php:meth:`File::info()` を使用してください。 + + +ファイルをオープンしている場合のファイルハンドラを保持します。 + + +ファイルの読み書き時のロックを有効にします。 + + +現在のファイルの絶対パス。 +``` + +`method` Cake\\Filesystem\\File::**append**(string $data, boolean $force = false) + +`method` Cake\\Filesystem\\File::**close**() + +`method` Cake\\Filesystem\\File::**copy**(string $dest, boolean $overwrite = true) + +`method` Cake\\Filesystem\\File::**create**() + +`method` Cake\\Filesystem\\File::**delete**() + +`method` Cake\\Filesystem\\File::**executable**() + +`method` Cake\\Filesystem\\File::**exists**() + +`method` Cake\\Filesystem\\File::**ext**() + +`method` Cake\\Filesystem\\File::**Folder**() + +`method` Cake\\Filesystem\\File::**group**() + +`method` Cake\\Filesystem\\File::**info**() + +`method` Cake\\Filesystem\\File::**lastAccess**( ) + +`method` Cake\\Filesystem\\File::**lastChange**() + +`method` Cake\\Filesystem\\File::**md5**(integer|boolean $maxsize = 5) + +`method` Cake\\Filesystem\\File::**name**() + +`method` Cake\\Filesystem\\File::**offset**(integer|boolean $offset = false, integer $seek = 0) + +`method` Cake\\Filesystem\\File::**open**(string $mode = 'r', boolean $force = false) + +`method` Cake\\Filesystem\\File::**owner**() + +`method` Cake\\Filesystem\\File::**perms**() + +`static` Cake\\Filesystem\\File::**prepare**(string $data, boolean $forceWindows = false) + +ASCII 文字列をファイルへ書き出す事前処理を行います。 +現在の実行環境に合わせて改行文字を変換します。 +Windows なら "\r\n" を、その他の環境なら "\n" が利用されます。 + +`method` Cake\\Filesystem\\File::**pwd**() + +`method` Cake\\Filesystem\\File::**read**(string $bytes = false, string $mode = 'rb', boolean $force = false) + +`method` Cake\\Filesystem\\File::**readable**() + +`method` Cake\\Filesystem\\File::**safe**(string $name = null, string $ext = null) + +`method` Cake\\Filesystem\\File::**size**() + +`method` Cake\\Filesystem\\File::**writable**() + +`method` Cake\\Filesystem\\File::**write**(string $data, string $mode = 'w', boolean$force = false) + +`method` Cake\\Filesystem\\File::**mime**() + +`method` Cake\\Filesystem\\File::**replaceText**( $search, $replace ) + +
    + +双方のクラスの各メソッドの使い方について、より良い解説が必要です。 + +
    diff --git a/docs/ja/core-libraries/form.md b/docs/ja/core-libraries/form.md new file mode 100644 index 0000000000..d3a97a60f4 --- /dev/null +++ b/docs/ja/core-libraries/form.md @@ -0,0 +1,226 @@ +# モデルのないフォーム + +`class` Cake\\Form\\**Form** + +ほとんどの場合、フォームの背後には [ORM エンティティー](../orm/entities) や +[ORM テーブル](../orm/table-objects) あるいはその他の永続的ストアーがありますが、 +時にはユーザー入力を検証し、そのデータが有効であれば何らかのアクションを実行する、 +という場合もあります。この最も一般的な例は問い合わせフォームです。 + +## フォームの作成 + +一般的に Form クラスを使う時は、フォームを定義するためにサブクラスを使います。 +これはテストを簡単にし、フォームを再利用できるようにします。 +フォームは **src/Form** に置かれ、普通はクラスのサフィックスして `Form` を持ちます。 +たとえば、単純な問い合わせフォームは次のようになります。 : + +``` php +// src/Form/ContactForm.php の中で +namespace App\Form; + +use Cake\Form\Form; +use Cake\Form\Schema; +use Cake\Validation\Validator; + +class ContactForm extends Form +{ + protected function _buildSchema(Schema $schema): Schema + { + return $schema->addField('name', 'string') + ->addField('email', ['type' => 'string']) + ->addField('body', ['type' => 'text']); + } + + public function validationDefault(Validator $validator): Validator + { + $validator->minLength('name', 10) + ->email('email'); + + return $validator; + } + + protected function _execute(array $data): bool + { + // メールを送信する + return true; + } +} +``` + +上の例ではフォームが提供する3つのフックメソッドが見えます。 + +- `_buildSchema` は FormHelper が HTML フォームを作成する際に使用する + スキーマデータを定義するために使います。フィールドの型、長さ、および精度を定義できます。 +- `validationDefault` はバリデーターを加えることができる + `Cake\Validation\Validator` のインスタンスを受け取ります。 +- `_execute` では `execute()` が呼ばれ、データが有効な時に望むふるまいを定義します。 + +もちろん必要に応じて追加の公開メソッドを定義することもできます。 + +## リクエストデータの処理 + +フォームを定義したら、リクエストデータを処理し検証するために +コントローラー中でそれを使うことができます。 : + +``` php +// 何らかのコントローラー中で +namespace App\Controller; + +use App\Controller\AppController; +use App\Form\ContactForm; + +class ContactController extends AppController +{ + public function index() + { + $contact = new ContactForm(); + if ($this->request->is('post')) { + if ($contact->execute($this->request->getData())) { + $this->Flash->success('すぐにご連絡いたします。'); + } else { + $this->Flash->error('フォーム送信に問題がありました。'); + } + } + $this->set('contact', $contact); + } +} +``` + +上の例では、データが有効な時にのみフォームの `_execute()` を走らせるために `execute()` +メソッドを実行し、それに応じたフラッシュメッセージを設定しています。 +データ検証のみ行うために `validate()` を +使うこともできます。 : + +``` php +if ($contact->execute($this->request->getData(), 'update')) { + // Handle form success. +} +``` + +This option can also be set to `false` to disable validation. + +We could have also used the `validate()` method to only validate +the request data: + +``` php +$isValid = $form->validate($this->request->getData()); + +// You can also use other validation sets. The following +// would use the rules defined by `validationUpdate()` +$isValid = $form->validate($this->request->getData(), 'update'); +``` + +## フォーム値の設定 + +モデルのないフォームのフィールドにデフォルト値を設定するために、 `setData()` メソッドが使用できます。 +このメソッドで設定された値はフォームオブジェクトの既存のデータを上書きします。 : + +``` php +// 何らかのコントローラー中で +namespace App\Controller; + +use App\Controller\AppController; +use App\Form\ContactForm; + +class ContactController extends AppController +{ + public function index() + { + $contact = new ContactForm(); + if ($this->request->is('post')) { + if ($contact->execute($this->request->getData())) { + $this->Flash->success('すぐにご連絡いたします。'); + } else { + $this->Flash->error('フォーム送信に問題がありました。'); + } + } + + if ($this->request->is('get')) { + $contact->setData([ + 'name' => 'John Doe', + 'email' => 'john.doe@example.com' + ]); + } + + $this->set('contact', $contact); + } +} +``` + +値はリクエストメソッドが GET の時にのみ定義する必要があります。そうしないと、修正が必要なバリデーションエラーの直前の POST データを上書きしてしまいます。 +また、 `set()` を使用して、個々のフィールドまたはフィールドのサブセットを追加または置換することができます。: + +``` php +// 一つのフィールドをセット +$contact->set('name', 'John Doe'); + +// 複数のフィールドをセット; +$contact->set([ + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', +]); +``` + +## フォームエラーの取得 + +フォームが検証されたら、エラーを取得することができます。 : + +``` php +$errors = $form->getErrors(); // $form->errors(); // 3.7.0 より前 +/* $errors の中身 +[ + 'email' => ['有効なメールアドレスが要求されます'] +] +*/ +``` + +## コントローラーから各フォームフィールドを無効化 + +Validator クラスを使用せずに、コントローラーから各フォームフィールドを無効化 +(訳注:無効化は invalidate の訳で、ここでは「誤っていることを示す」の意味です) +することができます。この最も一般的な使用例はリモートサーバー上で検証が行われる時です。 +そうした場合、手動でリモートサーバーからのフィードバックに応じて +そのフィールドを無効化しなければなりません。 : + +``` php +// src/Form/ContactForm.php 中で +public function setErrors($errors) +{ + $this->_errors = $errors; +} +``` + +バリデータークラスのエラーの返し方にならって、 `$errors` はこの形式でなければなりません。 : + +``` php +["フィールド名" => ["検証名" => "表示するエラーメッセージ"]] +``` + +さて、フィールド名を設定することでフォームフィールドを無効化し、 +その時にメッセージを設定できるようになりました。 : + +``` php +// コントローラーの中で +$contact = new ContactForm(); +$contact->setErrors(["email" => ["_required" => "メールアドレスは必須です"]]); +``` + +結果を見るためには「FormHelper で HTML 作成」に進みます。 + +## FormHelper で HTML 作成 + +Form クラスを作ったら、その HTML フォームを作成したいはずです。 +FormHelper は Form オブジェクトを ORM エンティティーとちょうど同じように理解します。 : + +``` php +echo $this->Form->create($contact); +echo $this->Form->control('name'); +echo $this->Form->control('email'); +echo $this->Form->control('body'); +echo $this->Form->button('Submit'); +echo $this->Form->end(); +``` + +上記は先に定義した `ContactForm` フォーム用の HTML フォームを作成します。 +FormHelper で作成される HTML フォームはフィールド型、最大長、およびエラーを決定するために +定義されたスキーマとバリデーターを使用します。 diff --git a/docs/ja/core-libraries/global-constants-and-functions.md b/docs/ja/core-libraries/global-constants-and-functions.md new file mode 100644 index 0000000000..245a55ed0e --- /dev/null +++ b/docs/ja/core-libraries/global-constants-and-functions.md @@ -0,0 +1,236 @@ +# 定数および関数 + +CakePHP を使った皆さんの日常のほとんどの業務ではコアクラスやメソッドを用いることになるでしょうが、 +ちょっとした役に立つ便利なグローバル関数も CakePHP にはたくさんあります。この関数のほとんどは +CakePHP のクラスと一緒に使うためのもの(モデルやコンポーネントクラスのローディングなど)ですが、 +他の多くは、配列や文字列の扱いを少し楽にしてくれるものです。 + +また、CakePHP のアプリケーションで利用可能な定数も同時におさえておきましょう。 +これらの定数を用いることはよりスムースなアップグレードの助けになるだけでなく、 +CakePHP アプリケーション内の特定のファイルやディレクトリーを指し示す便利なやり方でもあります。 + +## グローバル関数 + +以下、CakePHP の使用可能なグローバル関数です。その多くは、デバッグしたり内容を翻訳したりといった、 +機能的に他の CakePHP の単なる便利なラッパーになっています。 + +`function` **__(string $string_id, [$formatArgs])** + +この関数は CakePHP のアプリケーションでのローカライズを担います。 +`$string_id` で翻訳時の ID を定めます。 +その文字列内のプレースホルダーを置き換えるための、追加の引数を供給できます。 : + +``` text +__('You have {0} unread messages', $number); +``` + +また、置換する名前インデックス配列を指定できます。 : + +``` text +__('You have {unread} unread messages', ['unread' => $number]); +``` + +> [!NOTE] +> より詳しい情報は +> [国際化と地域化](../core-libraries/internationalization-and-localization) +> のセクションを確認して下さい。 + +`function` **__d(string $domain, string $msg, mixed $args = null)** + +メッセージを一つ取得するために、現在のドメインを変更することが可能です。 + +プラグインを国際化するときに便利です: +`echo __d('PluginName', 'This is my plugin');` + +`function` **__dn(string $domain, string $singular, string $plural, integer $count, mixed $args = null)** + +複数形のメッセージを一つ取得するために、現在のドメインを変更することが可能です。 +`$domain` でドメインを指定し、 `$count` の数を数え、 `$singular` と +`$plural` に基いて複数形を正しく処理したメッセージを返します。 + +`function` **__dx(string $domain, string $context, string $msg, mixed $args = null)** + +メッセージを一つ取得するために、現在のドメインを変更することが可能です。 +また、あなたがコンテキストを指定することができます。 + +コンテキストは、同じドメイン内で、 +それがユニークな翻訳文字列の一意の識別子です。 + +`function` **__dxn(string $domain, string $context, string $singular, string $plural, integer $count, mixed $args = null)** + +複数形のメッセージを一つ取得するために、現在のドメインを変更することが可能です。 +また、あなたがコンテキストを指定することができます。 +`$domain` でドメインを指定し、 `$count` の数を数え、 `$singular` と +`$plural` に基いて複数形を正しく処理したメッセージを返します。 +幾つかの言語が、数に応じた複数形の形式を一つ以上持っています。 + +コンテキストは、同じドメイン内で、それがユニークな翻訳文字列の一意の識別子です。 + +`function` **__n(string $singular, string $plural, integer $count, mixed $args = null)** + +`$count` の数を数え、 `$singular` と `$plural` に基いて複数形を正しく処理した +メッセージを返します。幾つかの言語が、数に応じた複数形の形式を一つ以上持っています。 + +`function` **__x(string $context, string $msg, mixed $args = null)** + +コンテキストは、同じドメイン内で、それがユニークな翻訳文字列の一意の識別子です。 + +`function` **__xn(string $context, string $singular, string $plural, integer $count, mixed $args = null)** + +`$count` の数を数え、 `$singular` と `$plural` +に基いて複数形を正しく処理したメッセージを返します。 +また、あなたがコンテキストを指定することができます。 +幾つかの言語が、数に応じた複数形の形式を一つ以上持っています。 + +コンテキストは、同じドメイン内で、それがユニークな翻訳文字列の一意の識別子です。 + +`function` **collection(mixed $items)** + +渡された引数をラップする、新しい `Cake\Collection\Collection` +オブジェクトをインスタンス化するための簡易ラッパー。 `$items` パラメーターは +`Traversable` オブジェクトまたは配列のいずれかを取ります。 + +`function` **debug(mixed $var, boolean $showHtml = null, $showFrom = true)** + +コア `$debug` 変数が `true` であれば、 `$var` が出力されます。 +`$showHTML` が `true` あるいは `null` のままであればデータはブラウザー表示に +相応しいように描画されます。 `$showFrom` が `false` にセットされない場合、 +それがコールされた行の情報を伴ってデバッグ情報の出力が始まります。 +[デバッグ](../development/debugging) もご覧ください。 + +`function` **dd(mixed $var, boolean $showHtml = null)** + +`debug()` のように動作しますが、実行を終了します。 +コア `$debug` 変数が `true` であれば、 `$var` が出力されます。 +`$showHTML` が `true` あるいは `null` のままであればデータはブラウザー表示に +相応しいように描画されます。 [デバッグ](../development/debugging) もご覧ください + +`function` **pr(mixed $var)** + +出力を `
    ` タグで周りを囲む機能を追加した `print_r()` の便利なラッパー。
    +
    +`function` **pj(mixed $var)**
    +
    +出力を `
    ` タグで周りを囲む機能を追加した JSON 整形表示の便利な関数。
    +
    +それは、オブジェクトと配列のJSON 表現をデバッグために意図されています。
    +
    +`function` **env(string $key, string $default = null)**
    +
    +可能な限りの環境変数を取得します。仮に `$_SERVER` か `$_ENV` が使用不可の場合には
    +バックアップとして用いられます。
    +
    +この関数はまた、 `PHP_SELF` と `DOCUMENT_ROOT` を、非サポートのサーバー上で
    +エミュレートします。これは完全なエミュレーションラッパーなので、 `$_SERVER` や
    +`getenv()` の代わりに `env()` を常に用いることは、
    +(とりわけあなたがコードを配布する予定なら)とても良い考えです。
    +
    +`function` **h(string $text, boolean $double = true, string $charset = null)**
    +
    +`htmlspecialchars()` の便利なラッパー。
    +
    +`function` **pluginSplit(string $name, boolean $dotAppend = false, string $plugin = null)**
    +
    +ドット記法されたプラグイン名をプラグインとクラス名に分離します。
    +`$name` にドットが含まれない場合、インデックスが 0 の箇所は `null` になります。
    +
    +一般に `list($plugin, $name) = pluginSplit('Users.User');` のように使われます。
    +
    +`function` **namespaceSplit(string $class)**
    +
    +ネームスペースをクラス名から分離します。
    +
    +一般に `list($namespace, $className) = namespaceSplit('Cake\Core\App');`
    +のように使われます。
    +
    +## コア定義定数
    +
    +以下のほとんどの定数はあなたのアプリケーション内部のパスへの参照です。
    +
    +`constant` **APP**
    +
    +アプリケーションディレクトリーへの絶対パス。末尾にスラッシュが付きます。
    +
    +`constant` **APP_DIR**
    +
    +あなたのアプリケーションのディレクトリー名。`app` かも知れません。
    +
    +`constant` **CACHE**
    +
    +キャッシュファイルディレクトリーへのパス。
    +複数サーバーをセットアップした際のホスト間で共有できます。
    +
    +`constant` **CAKE**
    +
    +cake ディレクトリーへのパス。
    +
    +`constant` **CAKE_CORE_INCLUDE_PATH**
    +
    +ルートの lib ディレクトリーへのパス。
    +
    +`constant` **CONFIG**
    +
    +config ディレクトリーへのパス。
    +
    +`constant` **CORE_PATH**
    +
    +ルートディレクトリーへの、末尾にディレクトリースラッシュを付加したパス。
    +
    +`constant` **DS**
    +
    +PHP の `DIRECTORY_SEPARATOR` (Linux の場合は `/` Windows の場合は `\`)
    +のショートカット。
    +
    +`constant` **LOGS**
    +
    +ログディレクトリーへのパス。
    +
    +`constant` **ROOT**
    +
    +ルートディレクトリーへのパス。
    +
    +`constant` **TESTS**
    +
    +テストディレクトリーへのパス。
    +
    +`constant` **TMP**
    +
    +一時ファイルディレクトリーへのパス。
    +
    +`constant` **WWW_ROOT**
    +
    +ウェブルートへのフルパス。
    +
    +## 時間定義定数
    +
    +`constant` **TIME_START**
    +
    +アプリケーションが開始された時点の、浮動小数点マイクロ秒での UNIX タイムスタンプ。
    +
    +`constant` **SECOND**
    +
    +1 と等しい
    +
    +`constant` **MINUTE**
    +
    +60 と等しい
    +
    +`constant` **HOUR**
    +
    +3600 と等しい
    +
    +`constant` **DAY**
    +
    +86400 と等しい
    +
    +`constant` **WEEK**
    +
    +604800 と等しい
    +
    +`constant` **MONTH**
    +
    +2592000 と等しい
    +
    +`constant` **YEAR**
    +
    +31536000 と等しい
    diff --git a/docs/ja/core-libraries/hash.md b/docs/ja/core-libraries/hash.md
    new file mode 100644
    index 0000000000..26d3897017
    --- /dev/null
    +++ b/docs/ja/core-libraries/hash.md
    @@ -0,0 +1,888 @@
    +# Hash
    +
    +`class` Cake\\Utility\\**Hash**
    +
    +配列マネジメントはとても強力かつ便利なツールであり、適切に使いさえすれば、
    +よりスマートでより最適化されたコードを書くことができるようになるものです。
    +CakePHP ではとても便利なユーティリティ群を Hash クラスの中に
    +static で用意しており、まさにこれをするのに使えます。
    +
    +CakePHP の Hash クラスは Inflector クラスと同様で、どのモデルや
    +コントローラーからでも呼ぶことができます。 例: `Hash::combine()` 。
    +
    +## Hash パス構文
    +
    +下記のパス構文は `Hash` が持つすべてのメソッドで使われるものです。
    +ただし、すべてのパス構文が、すべてのメソッドで使用可能であるとは限りません。
    +パスの式はいくつものトークンで構成されます。トークンは、配列データの移動に使う『式』と、
    +要素を絞り込む『マッチャー』の2つのグループに大きく分けられます。
    +マッチャーは要素の式に対して適用することができます。
    +
    +### 式の種類
    +
    +| 式    | 説明                                                                |
    +|-------|---------------------------------------------------------------------|
    +| `{n}` | 数値キーを意味する。どんな文字列キーでも 数値型のキーでも一致する。 |
    +| `{s}` | 文字列キーを意味する。数値文字列を含め、 どんな文字列でも一致する。 |
    +| `{*}` | 任意の値と一致する。                                                |
    +| `Foo` | 完全に同じ値だった場合のみ一致する。                                |
    +
    +要素の式はいずれも、すべてのメソッドで使うことができます。特定のメソッドでは、
    +要素の式に加え、 属性で絞り込むこともできます。該当するメソッドは、
    +`extract()`, `combine()`, `format()`, `check()`, `map()`, `reduce()`,
    +`apply()`, `sort()`, `insert()`, `remove()` と `nest()` です。
    +
    +### 属性の絞り込み種別
    +
    +| マッチャー     | 説明                                                     |
    +|----------------|----------------------------------------------------------|
    +| `[id]`         | 記述されたキーと一致する要素に絞り込む。                 |
    +| `[id=2]`       | id が 2 となっている要素に絞り込む。                     |
    +| `[id!=2]`      | id が 2 ではない要素に絞り込む。                         |
    +| `[id>2]`       | id が 2 より大きい要素に絞り込む。                       |
    +| `[id>=2]`      | id が 2 以上の要素に絞り込む。                           |
    +| `[id<2]`       | id が 2 より小さい要素に絞り込む。                       |
    +| `[id<=2]`      | id が 2 以下の要素に絞り込む。                           |
    +| `[text=/.../]` | 正規表現 `...` と合致する値を持っている 要素に絞り込む。 |
    +
    +`static` Cake\\Utility\\Hash::**get**(array|ArrayAccess $data, $path, $default = null)
    +
    +`get()` は `extract()` のシンプル版で、直接的に指定するパス式のみがサポートされます。
    +`{n}` 、 `{s}` 、 `{*}` 、または、マッチャーを使ったパスはサポートされません。
    +配列から1つの値だけを取り出したい場合に `get()` を使ってください。
    +もしマッチするパスが見つからない場合、デフォルト値が返ります。
    +
    +`static` Cake\\Utility\\Hash::**extract**(array|ArrayAccess $data, $path)
    +
    +`Hash::extract()` は [Hash Path Syntax](#hash-path-syntax) にあるすべての式とマッチャーを
    +サポートします。extract を使うことで、配列もしくは `ArrayAccess` インターフェイスを
    +実装したオブジェクトから好きなパスに沿ったデータを手早く取り出すことができます。
    +もはやデータ構造をループする必要はありません。その代わりに欲しい要素を絞り込むパス式を
    +使うのです。 :
    +
    +``` php
    +// 普通の使い方:
    +$users = [
    +    ['id' => 1, 'name' => 'mark'],
    +    ['id' => 2, 'name' => 'jane'],
    +    ['id' => 3, 'name' => 'sally'],
    +    ['id' => 4, 'name' => 'jose'],
    +];
    +$results = Hash::extract($users, '{n}.id');
    +// $results は以下のとおり:
    +// [1,2,3,4];
    +```
    +
    +`static` Hash::**insert**(array $data, $path, $values = null)
    +
    +`$values` を `$path` の定義に従って配列の中に挿入します。 :
    +
    +``` php
    +$a = [
    +    'pages' => ['name' => 'page']
    +];
    +$result = Hash::insert($a, 'files', ['name' => 'files']);
    +// $result は以下のようになります:
    +[
    +    [pages] => [
    +        [name] => page
    +    ]
    +    [files] => [
    +        [name] => files
    +    ]
    +]
    +```
    +
    +`{n}` 、 `{s}` そして `{*}` を使ったパスを使うことで、
    +複数のポイントにデータを挿入することができます。 :
    +
    +``` php
    +$users = Hash::insert($users, '{n}.new', 'value');
    +```
    +
    +`insert()` では属性のマッチャーも動きます。 :
    +
    +``` php
    +$data = [
    +    0 => ['up' => true, 'Item' => ['id' => 1, 'title' => 'first']],
    +    1 => ['Item' => ['id' => 2, 'title' => 'second']],
    +    2 => ['Item' => ['id' => 3, 'title' => 'third']],
    +    3 => ['up' => true, 'Item' => ['id' => 4, 'title' => 'fourth']],
    +    4 => ['Item' => ['id' => 5, 'title' => 'fifth']],
    +];
    +$result = Hash::insert($data, '{n}[up].Item[id=4].new', 9);
    +/* $result は以下のようになります:
    +    [
    +        ['up' => true, 'Item' => ['id' => 1, 'title' => 'first']],
    +        ['Item' => ['id' => 2, 'title' => 'second']],
    +        ['Item' => ['id' => 3, 'title' => 'third']],
    +        ['up' => true, 'Item' => ['id' => 4, 'title' => 'fourth', 'new' => 9]],
    +        ['Item' => ['id' => 5, 'title' => 'fifth']],
    +    ]
    +*/
    +```
    +
    +`static` Cake\\Utility\\Hash::**remove**(array $data, $path)
    +
    +`$path` に合致するすべての要素を配列から削除します。 :
    +
    +``` php
    +$a = [
    +    'pages' => ['name' => 'page'],
    +    'files' => ['name' => 'files']
    +];
    +$result = Hash::remove($a, 'files');
    +/* $result は以下のようになります:
    +    [
    +        [pages] => [
    +            [name] => page
    +        ]
    +
    +    ]
    +*/
    +```
    +
    +`{n}` 、 `{s}` そして `{*}` を使うことで、複数の値を一度に削除することができます。
    +また、`remove()` では属性のマッチャーを使用することもできます。 :
    +
    +``` php
    +$data = [
    +    0 => ['clear' => true, 'Item' => ['id' => 1, 'title' => 'first']],
    +    1 => ['Item' => ['id' => 2, 'title' => 'second']],
    +    2 => ['Item' => ['id' => 3, 'title' => 'third']],
    +    3 => ['clear' => true, 'Item' => ['id' => 4, 'title' => 'fourth']],
    +    4 => ['Item' => ['id' => 5, 'title' => 'fifth']],
    +];
    +$result = Hash::remove($data, '{n}[clear].Item[id=4]');
    +/* $result は以下のようになります:
    +    [
    +        ['clear' => true, 'Item' => ['id' => 1, 'title' => 'first']],
    +        ['Item' => ['id' => 2, 'title' => 'second']],
    +        ['Item' => ['id' => 3, 'title' => 'third']],
    +        ['clear' => true],
    +        ['Item' => ['id' => 5, 'title' => 'fifth']],
    +    ]
    +*/
    +```
    +
    +`static` Cake\\Utility\\Hash::**combine**(array $data, $keyPath, $valuePath = null, $groupPath = null)
    +
    +`$keyPath` のパスをキー、`$valuePath` (省略可) のパスを値として使って連想配列を作ります。
    +`$valuePath` が省略された場合や、`$valuePath` に合致するものが無かった場合は、値は null で初期化されます。
    +`$groupPath` が指定された場合は、そのパスにしたがって生成したものをグルーピングします。 :
    +
    +``` php
    +$a = [
    +    [
    +        'User' => [
    +            'id' => 2,
    +            'group_id' => 1,
    +            'Data' => [
    +                'user' => 'mariano.iglesias',
    +                'name' => 'Mariano Iglesias'
    +            ]
    +        ]
    +    ],
    +    [
    +        'User' => [
    +            'id' => 14,
    +            'group_id' => 2,
    +            'Data' => [
    +                'user' => 'phpnut',
    +                'name' => 'Larry E. Masters'
    +            ]
    +        ]
    +    ],
    +];
    +
    +$result = Hash::combine($a, '{n}.User.id');
    +/* $result は以下のようになります:
    +    [
    +        [2] =>
    +        [14] =>
    +    ]
    +*/
    +
    +$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.user');
    +/* $result は以下のようになります:
    +    [
    +        [2] => 'mariano.iglesias'
    +        [14] => 'phpnut'
    +    ]
    +*/
    +
    +$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data');
    +/* $result は以下のようになります:
    +    [
    +        [2] => [
    +                [user] => mariano.iglesias
    +                [name] => Mariano Iglesias
    +        ]
    +        [14] => [
    +                [user] => phpnut
    +                [name] => Larry E. Masters
    +        ]
    +    ]
    +*/
    +
    +$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.name');
    +/* $result は以下のようになります:
    +    [
    +        [2] => Mariano Iglesias
    +        [14] => Larry E. Masters
    +    ]
    +*/
    +
    +$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data', '{n}.User.group_id');
    +/* $result は以下のようになります:
    +    [
    +        [1] => [
    +                [2] => [
    +                        [user] => mariano.iglesias
    +                        [name] => Mariano Iglesias
    +                ]
    +        ]
    +        [2] => [
    +                [14] => [
    +                        [user] => phpnut
    +                        [name] => Larry E. Masters
    +                ]
    +        ]
    +    ]
    +*/
    +
    +$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.name', '{n}.User.group_id');
    +/* $result は以下のようになります:
    +    [
    +        [1] => [
    +                [2] => Mariano Iglesias
    +        ]
    +        [2] => [
    +                [14] => Larry E. Masters
    +        ]
    +    ]
    +*/
    +
    +// As of 3.9.0 $keyPath can be null
    +$result = Hash::combine($a, null, '{n}.User.Data.name');
    +/* $result now looks like:
    +    [
    +        [0] => Mariano Iglesias
    +        [1] => Larry E. Masters
    +    ]
    +*/
    +```
    +
    +`$keyPath` および `$valuePath` で配列を指定することができます。これにより、
    +最初の要素で指定した形式に合わせて、その他のパスで指定した値がフォーマットされます。 :
    +
    +``` php
    +$result = Hash::combine(
    +    $a,
    +    '{n}.User.id',
    +    ['%s: %s', '{n}.User.Data.user', '{n}.User.Data.name'],
    +    '{n}.User.group_id'
    +);
    +/* $result は以下のようになります:
    +    [
    +        [1] => [
    +                [2] => mariano.iglesias: Mariano Iglesias
    +        ]
    +        [2] => [
    +                [14] => phpnut: Larry E. Masters
    +        ]
    +    ]
    +*/
    +
    +$result = Hash::combine(
    +    $a,
    +    ['%s: %s', '{n}.User.Data.user', '{n}.User.Data.name'],
    +    '{n}.User.id'
    +);
    +/* $result は以下のようになります:
    +    [
    +        [mariano.iglesias: Mariano Iglesias] => 2
    +        [phpnut: Larry E. Masters] => 14
    +    ]
    +*/
    +```
    +
    +`static` Cake\\Utility\\Hash::**format**(array $data, array $paths, $format)
    +
    +配列から取り出し、フォーマット文字列でフォーマットされた文字列の配列を返します。 :
    +
    +``` php
    +$data = [
    +    [
    +        'Person' => [
    +            'first_name' => 'Nate',
    +            'last_name' => 'Abele',
    +            'city' => 'Boston',
    +            'state' => 'MA',
    +            'something' => '42'
    +        ]
    +    ],
    +    [
    +        'Person' => [
    +            'first_name' => 'Larry',
    +            'last_name' => 'Masters',
    +            'city' => 'Boondock',
    +            'state' => 'TN',
    +            'something' => '{0}'
    +        ]
    +    ],
    +    [
    +        'Person' => [
    +            'first_name' => 'Garrett',
    +            'last_name' => 'Woodworth',
    +            'city' => 'Venice Beach',
    +            'state' => 'CA',
    +            'something' => '{1}'
    +        ]
    +    ]
    +];
    +
    +$res = Hash::format($data, ['{n}.Person.first_name', '{n}.Person.something'], '%2$d, %1$s');
    +/*
    +[
    +    [0] => 42, Nate
    +    [1] => 0, Larry
    +    [2] => 0, Garrett
    +]
    +*/
    +
    +$res = Hash::format($data, ['{n}.Person.first_name', '{n}.Person.something'], '%1$s, %2$d');
    +/*
    +[
    +    [0] => Nate, 42
    +    [1] => Larry, 0
    +    [2] => Garrett, 0
    +]
    +*/
    +```
    +
    +`static` Cake\\Utility\\Hash::**contains**(array $data, array $needle)
    +
    +一方のハッシュや配列の中に、もう一方のキーと値が厳密に見てすべて存在しているかを判定します。 :
    +
    +``` php
    +$a = [
    +    0 => ['name' => 'main'],
    +    1 => ['name' => 'about']
    +];
    +$b = [
    +    0 => ['name' => 'main'],
    +    1 => ['name' => 'about'],
    +    2 => ['name' => 'contact'],
    +    'a' => 'b'
    +];
    +
    +$result = Hash::contains($a, $a);
    +// true
    +$result = Hash::contains($a, $b);
    +// false
    +$result = Hash::contains($b, $a);
    +// true
    +```
    +
    +`static` Cake\\Utility\\Hash::**check**(array $data, string $path = null)
    +
    +配列の中に特定のパスがセットされているかをチェックします。 :
    +
    +``` php
    +$set = [
    +    'My Index 1' => ['First' => 'The first item']
    +];
    +$result = Hash::check($set, 'My Index 1.First');
    +// $result == true
    +
    +$result = Hash::check($set, 'My Index 1');
    +// $result == true
    +
    +$set = [
    +    'My Index 1' => [
    +        'First' => [
    +            'Second' => [
    +                'Third' => [
    +                    'Fourth' => 'Heavy. Nesting.'
    +                ]
    +            ]
    +        ]
    +    ]
    +];
    +$result = Hash::check($set, 'My Index 1.First.Second');
    +// $result == true
    +
    +$result = Hash::check($set, 'My Index 1.First.Second.Third');
    +// $result == true
    +
    +$result = Hash::check($set, 'My Index 1.First.Second.Third.Fourth');
    +// $result == true
    +
    +$result = Hash::check($set, 'My Index 1.First.Seconds.Third.Fourth');
    +// $result == false
    +```
    +
    +`static` Cake\\Utility\\Hash::**filter**(array $data, $callback = ['Hash', 'filter'])
    +
    +配列から空の要素(ただし '0' 以外)を取り除きます。
    +また、カスタム引数 `$callback` を指定することで配列の要素を抽出することができます。
    +コールバック関数が `false` を返した場合、その要素は配列から取り除かれます。 :
    +
    +``` php
    +$data = [
    +    '0',
    +    false,
    +    true,
    +    0,
    +    ['one thing', 'I can tell you', 'is you got to be', false]
    +];
    +$res = Hash::filter($data);
    +
    +/* $res は以下のようになります:
    +    [
    +        [0] => 0
    +        [2] => true
    +        [3] => 0
    +        [4] => [
    +                [0] => one thing
    +                [1] => I can tell you
    +                [2] => is you got to be
    +        ]
    +    ]
    +*/
    +```
    +
    +`static` Cake\\Utility\\Hash::**flatten**(array $data, string $separator = '.')
    +
    +多次元配列を1次元配列へと平坦化します。 :
    +
    +``` php
    +$arr = [
    +    [
    +        'Post' => ['id' => '1', 'title' => 'First Post'],
    +        'Author' => ['id' => '1', 'user' => 'Kyle'],
    +    ],
    +    [
    +        'Post' => ['id' => '2', 'title' => 'Second Post'],
    +        'Author' => ['id' => '3', 'user' => 'Crystal'],
    +    ],
    +];
    +$res = Hash::flatten($arr);
    +/* $res は以下のようになります:
    +    [
    +        [0.Post.id] => 1
    +        [0.Post.title] => First Post
    +        [0.Author.id] => 1
    +        [0.Author.user] => Kyle
    +        [1.Post.id] => 2
    +        [1.Post.title] => Second Post
    +        [1.Author.id] => 3
    +        [1.Author.user] => Crystal
    +    ]
    +*/
    +```
    +
    +`static` Cake\\Utility\\Hash::**expand**(array $data, string $separator = '.')
    +
    +`Hash::flatten()` によって前もって平坦化された配列を再構築します。 :
    +
    +``` php
    +$data = [
    +    '0.Post.id' => 1,
    +    '0.Post.title' => First Post,
    +    '0.Author.id' => 1,
    +    '0.Author.user' => Kyle,
    +    '1.Post.id' => 2,
    +    '1.Post.title' => Second Post,
    +    '1.Author.id' => 3,
    +    '1.Author.user' => Crystal,
    +];
    +$res = Hash::expand($data);
    +/* $res は以下のようになります:
    +[
    +    [
    +        'Post' => ['id' => '1', 'title' => 'First Post'],
    +        'Author' => ['id' => '1', 'user' => 'Kyle'],
    +    ],
    +    [
    +        'Post' => ['id' => '2', 'title' => 'Second Post'],
    +        'Author' => ['id' => '3', 'user' => 'Crystal'],
    +    ],
    +];
    +*/
    +```
    +
    +`static` Cake\\Utility\\Hash::**merge**(array $data, array $merge[, array $n])
    +
    +この関数は PHP の `array_merge` と `array_merge_recursive` の
    +両方の機能を持っていると考えることができます。この2つの関数との違いは、一方の配列キーが
    +もう一方に含まれていた場合には (`array_merge` と違って) 再帰的に動きますが、
    +含まれていなかった場合には (`array_merge_recursive` と違って) 再帰的には動きません。
    +
    +> [!NOTE]
    +> この関数の引数の個数に制限はありません。また、配列以外が引数に指定された場合は
    +> 配列へとキャストされます。
    +
    +``` php
    +$array = [
    +    [
    +        'id' => '48c2570e-dfa8-4c32-a35e-0d71cbdd56cb',
    +        'name' => 'mysql raleigh-workshop-08 < 2008-09-05.sql ',
    +        'description' => 'Importing an sql dump'
    +    ],
    +    [
    +        'id' => '48c257a8-cf7c-4af2-ac2f-114ecbdd56cb',
    +        'name' => 'pbpaste | grep -i Unpaid | pbcopy',
    +        'description' => 'Remove all lines that say "Unpaid".',
    +    ]
    +];
    +$arrayB = 4;
    +$arrayC = [0 => "test array", "cats" => "dogs", "people" => 1267];
    +$arrayD = ["cats" => "felines", "dog" => "angry"];
    +$res = Hash::merge($array, $arrayB, $arrayC, $arrayD);
    +
    +/* $res は以下のようになります:
    +[
    +    [0] => [
    +            [id] => 48c2570e-dfa8-4c32-a35e-0d71cbdd56cb
    +            [name] => mysql raleigh-workshop-08 < 2008-09-05.sql
    +            [description] => Importing an sql dump
    +    ]
    +    [1] => [
    +            [id] => 48c257a8-cf7c-4af2-ac2f-114ecbdd56cb
    +            [name] => pbpaste | grep -i Unpaid | pbcopy
    +            [description] => Remove all lines that say "Unpaid".
    +    ]
    +    [2] => 4
    +    [3] => test array
    +    [cats] => felines
    +    [people] => 1267
    +    [dog] => angry
    +]
    +*/
    +```
    +
    +`static` Cake\\Utility\\Hash::**numeric**(array $data)
    +
    +配列内のすべての値が数値であるかをチェックします。 :
    +
    +``` php
    +$data = ['one'];
    +$res = Hash::numeric(array_keys($data));
    +// $res は true
    +
    +$data = [1 => 'one'];
    +$res = Hash::numeric($data);
    +// $res は false
    +```
    +
    +`static` Cake\\Utility\\Hash::**dimensions **(array $data)
    +
    +配列の次元数を数えます。このメソッドは配列の1つ目の要素だけを見て次元を判定します。 :
    +
    +``` php
    +$data = ['one', '2', 'three'];
    +$result = Hash::dimensions($data);
    +// $result == 1
    +
    +$data = ['1' => '1.1', '2', '3'];
    +$result = Hash::dimensions($data);
    +// $result == 1
    +
    +$data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => '3.1.1']];
    +$result = Hash::dimensions($data);
    +// $result == 2
    +
    +$data = ['1' => '1.1', '2', '3' => ['3.1' => '3.1.1']];
    +$result = Hash::dimensions($data);
    +// $result == 1
    +
    +$data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => ['3.1.1' => '3.1.1.1']]];
    +$result = Hash::dimensions($data);
    +// $result == 2
    +```
    +
    +`static` Cake\\Utility\\Hash::**maxDimensions**(array $data)
    +
    +`~Hash::dimensions()` に似ていますが、このメソッドは配列内にある
    +もっとも大きな次元数を返します。 :
    +
    +``` php
    +$data = ['1' => '1.1', '2', '3' => ['3.1' => '3.1.1']];
    +$result = Hash::maxDimensions($data);
    +// $result == 2
    +
    +$data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => ['3.1.1' => '3.1.1.1']]];
    +$result = Hash::maxDimensions($data);
    +// $result == 3
    +```
    +
    +`static` Cake\\Utility\\Hash::**map**(array $data, $path, $function)
    +
    +`$path` で抽出し、各要素に `$function` を割り当て(map)ることで新たな配列を作ります。
    +このメソッドでは式とマッチャーの両方を使うことができます。 :
    +
    +``` php
    +// $data のすべての要素に対して noop 関数 $this->noop() を呼びます。
    +$result = Hash::map($data, "{n}", [$this, 'noop']);
    +
    +public function noop(array $array)
    +{
    +    // 配列に詰めて、結果を返してください。
    +    return $array;
    +}
    +```
    +
    +`static` Cake\\Utility\\Hash::**reduce**(array $data, $path, $function)
    +
    +`$path` で抽出し、抽出結果を `$function` で縮小(reduce)することでを単一の値を作ります。
    +このメソッドでは式とマッチャーの両方を使うことができます。
    +
    +`static` Cake\\Utility\\Hash::**apply**(array $data, $path, $function)
    +
    +`$function` を使用して、抽出された値のセットにコールバックを適用します。
    +この関数は第一引数として抽出された値を取得します。 :
    +
    +``` php
    +$data = [
    +    ['date' => '01-01-2016', 'booked' => true],
    +    ['date' => '01-01-2016', 'booked' => false],
    +    ['date' => '02-01-2016', 'booked' => true]
    +];
    +$result = Hash::apply($data, '{n}[booked=true].date', 'array_count_values');
    +/* $result は以下のようになります:
    +    [
    +        '01-01-2016' => 1,
    +        '02-01-2016' => 1,
    +    ]
    +*/
    +```
    +
    +`static` Cake\\Utility\\Hash::**sort**(array $data, $path, $dir, $type = 'regular')
    +
    +[Hash Path Syntax](#hash-path-syntax) によって、どの次元のどの値によってでもソートすることができます。
    +このメソッドでは式のみがサポートされます。 :
    +
    +``` php
    +$a = [
    +    0 => ['Person' => ['name' => 'Jeff']],
    +    1 => ['Shirt' => ['color' => 'black']]
    +];
    +$result = Hash::sort($a, '{n}.Person.name', 'asc');
    +/* $result は以下のようになります:
    +    [
    +        [0] => [
    +                [Shirt] => [
    +                        [color] => black
    +                ]
    +        ]
    +        [1] => [
    +                [Person] => [
    +                        [name] => Jeff
    +                ]
    +        ]
    +    ]
    +*/
    +```
    +
    +`$dir` には `asc` もしくは `desc` を指定することができます。
    +`$type` には次のいずれかを指定することができます。
    +
    +- `regular` : 通常のソート。
    +- `numeric` : 数値とみなしてソート。
    +- `string` : 文字列としてソート。
    +- `natural` : ヒューマン・フレンドリー・ソート。例えば、 `foo10` が `foo2`
    +  の下に配置される。
    +
    +`static` Cake\\Utility\\Hash::**diff**(array $data, array $compare)
    +
    +2つの配列の差分を計算します:
    +
    +``` php
    +$a = [
    +    0 => ['name' => 'main'],
    +    1 => ['name' => 'about']
    +];
    +$b = [
    +    0 => ['name' => 'main'],
    +    1 => ['name' => 'about'],
    +    2 => ['name' => 'contact']
    +];
    +
    +$result = Hash::diff($a, $b);
    +/* $result は以下のようになります:
    +    [
    +        [2] => [
    +                [name] => contact
    +        ]
    +    ]
    +*/
    +```
    +
    +`static` Cake\\Utility\\Hash::**mergeDiff**(array $data, array $compare)
    +
    +この関数は2つの配列をマージし、差分は、その結果の配列の下部に push します。
    +
    +**例1**
    +:
    +
    +``` php
    +$array1 = ['ModelOne' => ['id' => 1001, 'field_one' => 'a1.m1.f1', 'field_two' => 'a1.m1.f2']];
    +$array2 = ['ModelOne' => ['id' => 1003, 'field_one' => 'a3.m1.f1', 'field_two' => 'a3.m1.f2', 'field_three' => 'a3.m1.f3']];
    +$res = Hash::mergeDiff($array1, $array2);
    +
    +/* $res は以下のようになります:
    +    [
    +        [ModelOne] => [
    +                [id] => 1001
    +                [field_one] => a1.m1.f1
    +                [field_two] => a1.m1.f2
    +                [field_three] => a3.m1.f3
    +            ]
    +    ]
    +*/
    +```
    +
    +**例2**
    +:
    +
    +``` php
    +$array1 = ["a" => "b", 1 => 20938, "c" => "string"];
    +$array2 = ["b" => "b", 3 => 238, "c" => "string", ["extra_field"]];
    +$res = Hash::mergeDiff($array1, $array2);
    +/* $res は以下のようになります:
    +    [
    +        [a] => b
    +        [1] => 20938
    +        [c] => string
    +        [b] => b
    +        [3] => 238
    +        [4] => [
    +                [0] => extra_field
    +        ]
    +    ]
    +*/
    +```
    +
    +`static` Cake\\Utility\\Hash::**normalize**(array $data, $assoc = true)
    +
    +配列を正規化します。 `$assoc` が `true` なら、連想配列へと正規化された配列が
    +返ります。値を持つ数値キーは null を持つ文字列キーへと変換されます。
    +配列を正規化すると、 `Hash::merge()` で扱いやすくなります。 :
    +
    +``` php
    +$a = ['Tree', 'CounterCache',
    +    'Upload' => [
    +        'folder' => 'products',
    +        'fields' => ['image_1_id', 'image_2_id']
    +    ]
    +];
    +$result = Hash::normalize($a);
    +/* $result は以下のようになります:
    +    [
    +        [Tree] => null
    +        [CounterCache] => null
    +        [Upload] => [
    +                [folder] => products
    +                [fields] => [
    +                        [0] => image_1_id
    +                        [1] => image_2_id
    +                ]
    +        ]
    +    ]
    +*/
    +
    +$b = [
    +    'Cacheable' => ['enabled' => false],
    +    'Limit',
    +    'Bindable',
    +    'Validator',
    +    'Transactional'
    +];
    +$result = Hash::normalize($b);
    +/* $result は以下のようになります:
    +    [
    +        [Cacheable] => [
    +                [enabled] => false
    +        ]
    +
    +        [Limit] => null
    +        [Bindable] => null
    +        [Validator] => null
    +        [Transactional] => null
    +    ]
    +*/
    +```
    +
    +`static` Cake\\Utility\\Hash::**nest**(array $data, array $options = [])
    +
    +平坦な配列から、多次元配列もしくはスレッド状(threaded)の構造化データを生成します。
    +
    +**オプション:**
    +
    +- `children` : 子の配列のために使われる戻り値のキー名。デフォルトは 'children'。
    +- `idPath` : 各要素を識別するためのキーを指すパス。
    +  `Hash::extract()` と同様に指定する。デフォルトは `{n}.$alias.id`
    +- `parentPath` : 各要素の親を識別するためのキーを指すパス。
    +  `Hash::extract()` と同様に指定する。デフォルトは `{n}.$alias.parent_id`
    +- `root` : 最上位となる要素の id 。
    +
    +次の配列データを使用した例:
    +
    +``` php
    +$data = [
    +    ['ThreadPost' => ['id' => 1, 'parent_id' => null]],
    +    ['ThreadPost' => ['id' => 2, 'parent_id' => 1]],
    +    ['ThreadPost' => ['id' => 3, 'parent_id' => 1]],
    +    ['ThreadPost' => ['id' => 4, 'parent_id' => 1]],
    +    ['ThreadPost' => ['id' => 5, 'parent_id' => 1]],
    +    ['ThreadPost' => ['id' => 6, 'parent_id' => null]],
    +    ['ThreadPost' => ['id' => 7, 'parent_id' => 6]],
    +    ['ThreadPost' => ['id' => 8, 'parent_id' => 6]],
    +    ['ThreadPost' => ['id' => 9, 'parent_id' => 6]],
    +    ['ThreadPost' => ['id' => 10, 'parent_id' => 6]]
    +];
    +
    +$result = Hash::nest($data, ['root' => 6]);
    +/* $result は以下のようになります:
    +    [
    +        (int) 0 => [
    +            'ThreadPost' => [
    +                'id' => (int) 6,
    +                'parent_id' => null
    +            ],
    +            'children' => [
    +                (int) 0 => [
    +                    'ThreadPost' => [
    +                        'id' => (int) 7,
    +                        'parent_id' => (int) 6
    +                    ],
    +                    'children' => []
    +                ],
    +                (int) 1 => [
    +                    'ThreadPost' => [
    +                        'id' => (int) 8,
    +                        'parent_id' => (int) 6
    +                    ],
    +                    'children' => []
    +                ],
    +                (int) 2 => [
    +                    'ThreadPost' => [
    +                        'id' => (int) 9,
    +                        'parent_id' => (int) 6
    +                    ],
    +                    'children' => []
    +                ],
    +                (int) 3 => [
    +                    'ThreadPost' => [
    +                        'id' => (int) 10,
    +                        'parent_id' => (int) 6
    +                    ],
    +                    'children' => []
    +                ]
    +            ]
    +        ]
    +    ]
    +    */
    +```
    diff --git a/docs/ja/core-libraries/httpclient.md b/docs/ja/core-libraries/httpclient.md
    new file mode 100644
    index 0000000000..aebf27e6cb
    --- /dev/null
    +++ b/docs/ja/core-libraries/httpclient.md
    @@ -0,0 +1,544 @@
    +# Http Client
    +
    +`class` Cake\\Http\\**Client**(mixed $config = [])
    +
    +CakePHP includes a PSR-18 compliant HTTP client which can be used for
    +making requests. It is a great way to communicate with webservices, and
    +remote APIs.
    +
    +CakePHP には、リクエストの実行に使用できる基本的ながら強力な HTTP クライアントが含まれています。
    +これは、ウェブサービスや、リモート API と通信するための素晴らしい方法です。
    +
    +## リクエストの実行
    +
    +リクエストの実行は、シンプルで簡単です。
    +GET リクエストは、次のようになります。 :
    +
    +``` php
    +use Cake\Http\Client;
    +
    +$http = new Client();
    +
    +// 単純な GET
    +$response = $http->get('http://example.com/test.html');
    +
    +// クエリー文字列を使用した単純な GET
    +$response = $http->get('http://example.com/search', ['q' => 'widget']);
    +
    +// クエリー文字列と追加ヘッダーを使用した単純な GET
    +$response = $http->get('http://example.com/search', ['q' => 'widget'], [
    +  'headers' => ['X-Requested-With' => 'XMLHttpRequest']
    +]);
    +```
    +
    +POST や PUT のリクエストを実行することは、同様に簡単です。 :
    +
    +``` php
    +// application/x-www-form-urlencoded エンコードデータを POST リクエストで送信
    +$http = new Client();
    +$response = $http->post('http://example.com/posts/add', [
    +  'title' => 'testing',
    +  'body' => 'content in the post'
    +]);
    +
    +// application/x-www-form-urlencoded エンコードデータを PUT リクエストで送信
    +$response = $http->put('http://example.com/posts/add', [
    +  'title' => 'testing',
    +  'body' => 'content in the post'
    +]);
    +
    +// 他のメソッドも同様に、
    +$http->delete(/* ... */);
    +$http->head(/* ... */);
    +$http->patch(/* ... */);
    +```
    +
    +If you have created a PSR-7 request object you can send it using
    +`sendRequest()`:
    +
    +``` php
    +use Cake\Http\Client;
    +use Cake\Http\Client\Request as ClientRequest;
    +
    +$request = new ClientRequest(
    +    'http://example.com/search',
    +    ClientRequest::METHOD_GET
    +);
    +$client = new Client();
    +$response = $client->sendRequest($request);
    +```
    +
    +## ファイルを使用したマルチパートリクエストの作成
    +
    +配列の中にファイルハンドルを含めることで、リクエストのボディー内にファイルを含めることができます。 :
    +
    +``` php
    +$http = new Client();
    +$response = $http->post('http://example.com/api', [
    +  'image' => fopen('/path/to/a/file', 'r'),
    +]);
    +```
    +
    +ファイルハンドルは、最後まで読まれます。それが読まれる前に巻き戻されることはありません。
    +
    +> [!WARNING]
    +> 互換性の理由から、 `@` で始まる文字列は、ローカルまたはリモートのファイルパスとして評価されます。
    +
    +この機能は、CakePHP 3.0.5 からは非推奨で、将来のバージョンで削除されます。
    +それまでは、HTTP クライアントに渡されるユーザーデータは、次のようにサニタイズする必要があります。 :
    +
    +``` php
    +$response = $http->post('http://example.com/api', [
    +    'search' => ltrim($this->request->getData('search'), '@'),
    +]);
    +```
    +
    +クエリー文字列の先頭の `@` 文字を維持する必要がある場合は、
    +`http_build_query()` で予めエンコードされたクエリー文字列を渡すことができます。 :
    +
    +``` php
    +$response = $http->post('http://example.com/api', http_build_query([
    +    'search' => $this->request->getData('search'),
    +]));
    +```
    +
    +### 手動でマルチパートリクエストのボディーを構築
    +
    +非常に特殊な方法でリクエストボディーを構築しなければならない場合もあるかもしれません。
    +このような状況では、多くの場合、あなたが望んだ特殊なマルチパートの HTTP リクエストを作るために
    +`Cake\Http\Client\FormData` を使用することができます。 :
    +
    +``` php
    +use Cake\Http\Client\FormData;
    +
    +$data = new FormData();
    +
    +// XML 部分を作成
    +$xml = $data->newPart('xml', $xmlString);
    +// コンテンツタイプを設定
    +$xml->type('application/xml');
    +$data->add($xml);
    +
    +// addFile() でファイルアップロードの作成
    +// 同様にフォームデータにファイルを追加します。
    +$file = $data->addFile('upload', fopen('/some/file.txt', 'r'));
    +$file->contentId('abc123');
    +$file->disposition('attachment');
    +
    +// リクエストの送信
    +$response = $http->post(
    +    'http://example.com/api',
    +    (string)$data,
    +    ['headers' => ['Content-Type' => $data->contentType()]]
    +);
    +```
    +
    +## リクエストボディーを送信
    +
    +REST API を扱うとき、多くの場合、フォームエンコードされていないリクエストボディーを送信する必要があります。
    +Http\Client は、type オプションを介してこれを公開します。 :
    +
    +``` php
    +// JSON リクエストボディーの送信
    +$http = new Client();
    +$response = $http->post(
    +  'http://example.com/tasks',
    +  json_encode($data),
    +  ['type' => 'json']
    +);
    +```
    +
    +`type` キーは「json」、「xml」または完全な MIME タイプのいずれかになります。
    +`type` オプションを使用するときは、文字列としてデータを提供してください。
    +クエリー文字列パラメーターとリクエストボディーの両方を必要とする GET リクエストを行う場合は、
    +次の操作で行うことができます。 :
    +
    +``` php
    +// クエリー文字列パラメーター付きの GET リクエストで JSON ボディーを送信
    +$http = new Client();
    +$response = $http->get(
    +  'http://example.com/tasks',
    +  ['q' => 'test', '_content' => json_encode($data)],
    +  ['type' => 'json']
    +);
    +```
    +
    +## リクエストメソッドのオプション
    +
    +各 HTTP メソッドは、追加のリクエスト情報を提供するための `$options` パラメーターを受け取ります。
    +以下のキーが `$options` で使用することができます。
    +
    +- `headers` - 追加ヘッダーの配列。
    +- `cookie` - 使用するクッキーの配列。
    +- `proxy` - プロキシー情報の配列。
    +- `auth` - 認証データの配列、 `type` キーが認証ストラテジーに委譲するために使用されます。
    +  デフォルトでは、Basic 認証が使用されます。
    +- `ssl_verify_peer` - デフォルトは `true` 。SSL 証明書の検証を無効にするには
    +  `false` を設定します(推奨されません)。
    +- `ssl_verify_peer_name` - デフォルトは `true` 。SSL 証明書を検証する場合、
    +  ホスト名検証を無効にするには `false` を設定します(推奨されません)。
    +- `ssl_verify_depth` - デフォルトは 5 。CA チェーンを通過する深さ。
    +- `ssl_verify_host` - デフォルトは `true` 。ホスト名に対して SSL 証明書を検証します。
    +- `ssl_cafile` - デフォルトは組み込みの cafile 。カスタム CA バンドルを使用するためには
    +  上書きしてください。
    +- `timeout` - 秒単位でタイムアウトするまで待つ時間。
    +- `type` - 独自のコンテンツタイプでリクエストボディーを送信します。 `$data` を文字列にするか、
    +  GET リクエストで `_content` オプションを指定する必要があります。
    +- `redirect` - フォローするリダイレクトの数。デフォルトは `false` です。
    +
    +オプションのパラメーターは、いつも HTTP メソッドの 3 番目のパラメーターです。
    +[スコープ指定クライアント](#http_client_scoped_client) を作成するために
    +`Client` を構築する場合にも使用できます。
    +
    +## 認証
    +
    +`Cake\Http\Client` は、いくつかの異なる認証システムをサポートしています。
    +異なる認証ストラテジーを、開発者によって追加することができます。
    +認証ストラテジーは、リクエストが送信される前に呼び出され、
    +リクエストの文脈にヘッダーを追加することができます。
    +
    +### Basic 認証の使用
    +
    +Basic 認証の例:
    +
    +``` php
    +$http = new Client();
    +$response = $http->get('http://example.com/profile/1', [], [
    +  'auth' => ['username' => 'mark', 'password' => 'secret']
    +]);
    +```
    +
    +デフォルトでは、 auth オプションに `'type'` キーが存在しない場合、
    +`Cake\Http\Client` は Basic 認証を使用します。
    +
    +### ダイジェスト認証の使用
    +
    +Basic 認証の例:
    +
    +``` php
    +$http = new Client();
    +$response = $http->get('http://example.com/profile/1', [], [
    +  'auth' => [
    +    'type' => 'digest',
    +    'username' => 'mark',
    +    'password' => 'secret',
    +    'realm' => 'myrealm',
    +    'nonce' => 'onetimevalue',
    +    'qop' => 1,
    +    'opaque' => 'someval'
    +  ]
    +]);
    +```
    +
    +'type' キーに 'digest' を設定することによって、
    +認証サブシステムにダイジェスト認証を使用することを伝えます。
    +
    +### OAuth 1 認証
    +
    +多くのモダンなウェブサービスは、API にアクセスするために OAuth 認証を必要とします。
    +含まれる OAuth 認証は、すでにコンシューマキーとコンシューマシークレットがあることを前提としています。 :
    +
    +``` php
    +$http = new Client();
    +$response = $http->get('http://example.com/profile/1', [], [
    +  'auth' => [
    +    'type' => 'oauth',
    +    'consumerKey' => 'bigkey',
    +    'consumerSecret' => 'secret',
    +    'token' => '...',
    +    'tokenSecret' => '...',
    +    'realm' => 'tickets',
    +  ]
    +]);
    +```
    +
    +### OAuth 2 認証
    +
    +OAuth2 は、多くの場合、単一のヘッダーであるため、特殊な認証アダプターがありません。
    +代わりに、アクセストークンを使用してクライアントを作成することができます。 :
    +
    +``` php
    +$http = new Client([
    +    'headers' => ['Authorization' => 'Bearer ' . $accessToken]
    +]);
    +$response = $http->get('https://example.com/api/profile/1');
    +```
    +
    +### プロキシー認証
    +
    +いくつかのプロキシーは使用するために認証を必要とします。
    +一般に、この認証は Basic ですが、任意の認証アダプターによって実装することができます。
    +デフォルトでは、 type キーが設定されていない限り、 Http\Client は Basic 認証を前提としています。 :
    +
    +``` php
    +$http = new Client();
    +$response = $http->get('http://example.com/test.php', [], [
    +  'proxy' => [
    +    'username' => 'mark',
    +    'password' => 'testing',
    +    'proxy' => '127.0.0.1:8080',
    +  ]
    +]);
    +```
    +
    +2番目のプロキシーパラメーターは、プロトコルのない IP またはドメインの文字列でなければなりません。
    +ユーザー名とパスワードは、ヘッダー通じて渡されますが、プロキシー文字列は [stream_context_create()](https://php.net/manual/ja/function.stream-context-create.php) を通じて渡されます。
    +
    +## スコープ指定クライアントの作成
    +
    +ドメイン名を再入力すると、認証とプロキシーの設定が面倒になり、エラーが発生しやすくなります。
    +間違いの可能性を減らし、いくつかの退屈さを緩和するために、
    +スコープ指定クライアントを作成することができます。 :
    +
    +``` php
    +// スコープ指定クライアントの作成
    +$http = new Client([
    +  'host' => 'api.example.com',
    +  'scheme' => 'https',
    +  'auth' => ['username' => 'mark', 'password' => 'testing']
    +]);
    +
    +// api.example.com にリクエストします
    +$response = $http->get('/test.php');
    +```
    +
    +スコープ指定クライアントを作成する場合、以下の情報を使用することができます。
    +
    +- host
    +- basePath
    +- scheme
    +- proxy
    +- auth
    +- port
    +- cookies
    +- timeout
    +- ssl_verify_peer
    +- ssl_verify_depth
    +- ssl_verify_host
    +
    +リクエスト実行時に、これらのオプションのいずれかを指定することで上書きすることができます。
    +リクエスト URL 中のホスト、スキーム、プロキシー、ポートが上書きされます。 :
    +
    +``` php
    +// 先ほど作成したスコープ指定クライアントの使用
    +$response = $http->get('http://foo.com/test.php');
    +```
    +
    +上記は、ドメインやスキーム、ポートが置き換えられます。ただし、このリクエストは、
    +スコープ指定クライアントの作成時に定義された他のすべてのオプションを使用して引き続き行われます。
    +対応するオプションの詳細については [Http Client Request Options](#http_client_request_options) をご覧ください。
    +
    +## クッキーの設定と管理
    +
    +Http\Client はまた、リクエストを行うときクッキーを受け入れることができます。
    +クッキーを受け入れることに加えて、レスポンス中に自動的に設定された有効なクッキーを格納します。
    +クッキーを持つ任意のレスポンスが、元の Http\Client のインスタンスに格納されています。
    +Client インスタンスに格納されているクッキーは、それ以後のドメイン+パスの組み合わせが一致する
    +リクエストに自動的に含まれます。 :
    +
    +``` php
    +$http = new Client([
    +    'host' => 'cakephp.org'
    +]);
    +
    +// いくつかのクッキーをセットしたリクエストを実行
    +$response = $http->get('/');
    +
    +// デフォルトで、初めのリクエストのクッキーが
    +// 含まれます。
    +$response2 = $http->get('/changelogs');
    +```
    +
    +リクエストの `$options` パラメーターの中に設定することにより、
    +自動で含まれるクッキーをいつでも上書きすることができます。 :
    +
    +``` php
    +// 格納されたクッキーを独自の値に置き換えます。
    +$response = $http->get('/changelogs', [], [
    +    'cookies' => ['sessionid' => '123abc']
    +]);
    +```
    +
    +`addCookie()` メソッドを使って、作成された後のクライアントにクッキーオブジェクトを
    +追加することができます。 :
    +
    +``` php
    +use Cake\Http\Cookie\Cookie;
    +
    +$http = new Client([
    +    'host' => 'cakephp.org'
    +]);
    +$http->addCookie(new Cookie('session', 'abc123'));
    +```
    +
    +## レスポンスオブジェクト
    +
    +`class` Cake\\Http\\Client\\**Response**
    +
    +Response オブジェクトは、レスポンスデータを検査するための多くのメソッドを持ちます。
    +
    +### レスポンスボディーの読み込み
    +
    +文字列としてレスポンスボディー全体を読み込みます。 :
    +
    +``` php
    +// 文字列としてレスポンス全体を読み込み
    +$response->getStringBody();
    +```
    +
    +また、レスポンスのストリームオブジェクトにアクセスし、そのメソッドを使用することができます。 :
    +
    +``` php
    +// レスポンスボディーを含む Psr\Http\Message\StreamInterface を取得
    +$stream = $response->getBody();
    +
    +// ストリームを一度に 100 バイト読み込み
    +while (!$stream->eof()) {
    +    echo $stream->read(100);
    +}
    +```
    +
    +### JSON や XML レスポンスボディーの読み込み
    +
    +JSON や XML のレスポンスが一般的に使用されているので、レスポンスオブジェクトは、
    +デコードされたデータを読み取るためにアクセサーを簡単に使用することができます。
    +JSON は配列にデコードされ、XML データは、 `SimpleXMLElement` ツリーにデコードされます。 :
    +
    +``` php
    +// 何らかの XML を取得
    +$http = new Client();
    +$response = $http->get('http://example.com/test.xml');
    +$xml = $response->getXml();
    +
    +// 何らかの JSON を取得
    +$http = new Client();
    +$response = $http->get('http://example.com/test.json');
    +$json = $response->getJson();
    +```
    +
    +デコードされたレスポンスデータはそれを複数回アクセスし、レスポンスオブジェクトに格納されても、
    +何も追加コストはかかりません。
    +
    +### レスポンスヘッダーへのアクセス
    +
    +いくつかの異なるメソッドを介してヘッダーにアクセスすることができます。
    +メソッドを介してアクセスする際に、ヘッダー名は常に大文字と小文字を区別しない値として扱われます。 :
    +
    +``` php
    +// 連想配列として全てのヘッダーを取得
    +$response->getHeaders();
    +
    +// 配列として単一のヘッダーを取得
    +$response->getHeader('content-type');
    +
    +// 文字列としてヘッダーを取得
    +$response->getHeaderLine('content-type');
    +
    +// レスポンスのエンコーディングを取得
    +$response->getEncoding();
    +
    +// 全てのヘッダーの key=>value の配列を取得
    +$response->headers;
    +```
    +
    +### クッキーデータへのアクセス
    +
    +クッキーについて必要なデータ量に応じて、いくつかの異なる方法でクッキーを読むことができます。 :
    +
    +``` php
    +// 全てのクッキー (全データ) を取得
    +$response->getCookies();
    +
    +// 単一のクッキーの値を取得
    +$response->getCookie('session_id');
    +
    +// 単一のクッキーの完全なデータを取得
    +// value, expires, path, httponly, secure キーを含みます
    +$response->getCookieData('session_id');
    +
    +// 全てのクッキーの完全なデータにアクセス
    +$response->cookies;
    +```
    +
    +### ステータスコードの確認
    +
    +レスポンスオブジェクトは、ステータスコードを確認するためのいくつかのメソッドを提供します。 :
    +
    +``` php
    +// レスポンスが 20x だった
    +$response->isOk();
    +
    +// レスポンスが 30x だった
    +$response->isRedirect();
    +
    +// ステータスコードの取得
    +$response->getStatusCode();
    +
    +// __get() ヘルパー
    +$response->code;
    +```
    +
    +## Changing Transport Adapters
    +
    +By default `Http\Client` will prefer using a `curl` based transport adapter.
    +If the curl extension is not available a stream based adapter will be used
    +instead. You can force select a transport adapter using a constructor option:
    +
    +``` php
    +use Cake\Http\Client\Adapter\Stream;
    +
    +$client = new Client(['adapter' => Stream::class]);
    +```
    +
    +
    +
    +## Testing
    +
    +In tests you will often want to create mock responses to external APIs. You can
    +use the `HttpClientTrait` to define responses to the requests your application
    +is making:
    +
    +``` php
    +use Cake\Http\TestSuite\HttpClientTrait;
    +use Cake\TestSuite\TestCase;
    +
    +class CartControllerTests extends TestCase
    +{
    +    use HttpClientTrait;
    +
    +    public function testCheckout()
    +    {
    +        // Mock a POST request that will be made.
    +        $this->mockClientPost(
    +            'https://example.com/process-payment',
    +            $this->newClientResponse(200, [], json_encode(['ok' => true]))
    +        );
    +        $this->post("/cart/checkout");
    +        // Do assertions.
    +    }
    +}
    +```
    +
    +There are methods to mock the most commonly used HTTP methods:
    +
    +``` php
    +$this->mockClientGet(/* ... */);
    +$this->mockClientPatch(/* ... */);
    +$this->mockClientPost(/* ... */);
    +$this->mockClientPut(/* ... */);
    +$this->mockClientDelete(/* ... */);
    +```
    +
    +`method` Cake\\Http\\TestSuite\\Response::**newClientResponse**(int $code = 200, array $headers = [], string $body = '')
    +
    +As seen above you can use the `newClientResponse()` method to create responses
    +for the requests your application will make. The headers need to be a list of
    +strings:
    +
    +``` php
    +$headers = [
    +    'Content-Type: application/json',
    +    'Connection: close',
    +];
    +$response = $this->newClientResponse(200, $headers, $body)
    +```
    diff --git a/docs/ja/core-libraries/inflector.md b/docs/ja/core-libraries/inflector.md
    new file mode 100644
    index 0000000000..0c7ea2f9ff
    --- /dev/null
    +++ b/docs/ja/core-libraries/inflector.md
    @@ -0,0 +1,232 @@
    +# Inflector
    +
    +`class` **Inflector**
    +
    +Inflector は文字列の複数形やキャメルケースへの変換を取り扱うクラスです。
    +Inflector のメソッドは通常では静的にアクセスします。
    +例: `Inflector::pluralize('example')` は "examples" を返します。
    +
    +[inflector.cakephp.org](https://inflector.cakephp.org/)
    +にてオンライン上で変換を試すことができます。
    +
    +## Inflector メソッドの概要と出力
    +
    +Inflector の組み込みメソッドの簡単な概要と、複数単語の引数を指定したときに出力される結果:
    +
    +
    +++++
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    メソッド引数出力
    pluralize()BigAppleBigApples
    big_applebig_apples
    singularize()BigApplesBigApple
    big_applesbig_apple
    camelize()big_applesBigApples
    big appleBigApple
    underscore()BigApplesbig_apples
    Big Applesbig apples
    humanize()big_applesBig Apples
    bigAppleBigApple
    classify()big_applesBigApple
    big appleBigApple
    dasherize()BigApplesbig-apples
    big applebig apple
    tableize()BigApplebig_apples
    Big Applebig apples
    variable()big_applebigApple
    big applesbigApples
    + +## 複数形と単数形の作成 + +`static` Inflector::**singularize**($singular) + +`static` Inflector::**pluralize**($singular) + +`pluralize` や `singularize()` の両方は、多くの英語名詞に作用します。 +もし、他の言語の対応が必要な場合、 使用するルールをカスタマイズするために +[Inflection Configuration](#inflection-configuration) を使用することができます。 : + +``` php +// Apples +echo Inflector::pluralize('Apple'); +``` + +> [!NOTE] +> `pluralize()` は、すでに複数形の名詞をいつも正しく変換できるわけではありません。 + +``` php +// Person +echo Inflector::singularize('People'); +``` + +> [!NOTE] +> `singularize()` は、すでに単数形の名詞をいつも正しく変換できるわけではありません。 + +## キャメルケースやアンダースコアーの作成 + +`static` Inflector::**camelize**($underscored) + +`static` Inflector::**underscore**($camelCase) + +これらのメソッドは、クラス名やプロパティー名を作成する時便利です。 : + +``` php +// ApplePie +Inflector::camelize('Apple_pie') + +// apple_pie +Inflector::underscore('ApplePie'); +``` + +underscore メソッドは、 キャメルケース形式の単語のみ変換することに注意してください。 +スペースを含む単語は、小文字になりますが、アンダースコアーは含まれません。 + +## 人間が読みやすい形式の作成 + +`static` Inflector::**humanize**($underscored) + +このメソッドは、アンダースコアー形式を人間が読みやすい値 +「タイトルケース」形式に変換する時に便利です。 : + +``` php +// Apple Pie +Inflector::humanize('apple_pie'); +``` + +## テーブル名やクラス名の作成 + +`static` Inflector::**classify**($underscored) + +`static` Inflector::**dasherize**($dashed) + +`static` Inflector::**tableize**($camelCase) + +コードの生成や CakePHP の規約を使用する時、テーブル名やクラス名に加工するために +必要になります。 : + +``` php +// UserProfileSetting +Inflector::classify('user_profile_settings'); + +// user-profile-setting +Inflector::dasherize('UserProfileSetting'); + +// user_profile_settings +Inflector::tableize('UserProfileSetting'); +``` + +## 変数名の作成 + +`static` Inflector::**variable**($underscored) + +規約をもとにしたコード生成や仕事をするのに必要なメタプログログラミングの作業を行う時に、 +変数名はしばしば役に立ちます。 : + +``` php +// applePie +Inflector::variable('apple_pie'); +``` + +## Inflection の設定 + +CakePHP の命名規則は、本当に良くなります。あなたはデータベーステーブル `big_boxes` と +名付け、モデルを `BigBoxes` 、コントローラーを `BigBoxesController` 、 +そして、すべては、ただ自動的に一緒に動作します。CakePHP は、 +単数形と複数形の間で単語を加工することにより、お互いを紐付ける方法を知っています。 + +(特に英語を話さない友人にとって) CakePHP の inflector (複数形、単数形、 +キャメルケース、アンダースコアーに変換するクラス) があなたの希望通りの動作をしないような、 +状況に陥るかもしれません。もし、CakePHP に Foci や Fish を認識させたくない場合、 +CakePHP に特別なケースを伝えることができます。 + +### カスタム Inflection のロード + +`static` Inflector::**rules**($type, $rules, $reset = false) + +Inflector で使用する新しい変換・翻訳の規則を定義します。しばしば、 +このメソッドは、 **config/bootstrap.php** 内で使用されます。 : + +``` php +Inflector::rules('singular', ['/^(bil)er$/i' => '\1', '/^(inflec|contribu)tors$/i' => '\1ta']); +Inflector::rules('uninflected', ['singulars']); +Inflector::rules('irregular', ['phylum' => 'phyla']); // キーは単数形、値は複数形 +``` + +与えられたルールは、 `Cake/Utility/Inflector` で定義された各変換セットの中に +マージされます。コアのルールよりも追加されたルールが優先されます。ルールをクリアして +Inflector の元の状態に戻すために `Inflector::reset()` が使用できます。 diff --git a/docs/ja/core-libraries/internationalization-and-localization.md b/docs/ja/core-libraries/internationalization-and-localization.md new file mode 100644 index 0000000000..1bb33bd93f --- /dev/null +++ b/docs/ja/core-libraries/internationalization-and-localization.md @@ -0,0 +1,698 @@ +# 国際化と地域化 + +アプリケーションをより多くのユーザーに届けるのに最も良い方法の一つは、 +複数の言語に対応することです。これは、しばしば気が遠くなるような作業になります。 +しかし、CakePHP の国際化と地域化の機能は、これを容易にします。 + +まずは、いくつかの専門用語について理解しましょう。 +国際化 *(internationalization)* とは、あるアプリケーションを地域化できるようにすることです。 +地域化 *(localization)* とは、あるアプリケーションを特定の言語や文化での表現( +すなわちロケール *(locale)*) に適応させることです。国際化と地域化は、それぞれ「i18n」と +「l10n」というように省略されます。「internationalization (国際化)」の最初と最後の文字の間に +18文字あるから「i18n」となり、「localization (地域化)」も同様の理由で「l10n」となります。 + +## 翻訳の準備 + +単一言語のアプリケーションから、複数言語のアプリケーションに移行するためには、 +数ステップを踏むだけです。最初のステップは、 `__()` 関数をあなたのコードで +使用することです。以下が単一言語アプリケーションのとあるコードの例です。 : + +``` html +

    Popular Articles

    +``` + +あなたのコードを国際化するためには、以下のように `__()` +で文字列を囲んでください。 : + +``` html +

    +``` + +これ以上何もしなければ、上記の2つのコードの例は、機能的に同じです。それらは、 +両方ともブラウザーに同じ内容を送信します。 `__()` 関数は、 +与えられた文字列を翻訳がある場合は翻訳し、そうでなければ何も変更せずに返します。 + +### 言語ファイル + +翻訳はアプリケーションの中にある言語ファイルを使って有効になります。 +CakePHP 翻訳ファイルのデフォルトの形式は、 [Gettext](https://en.wikipedia.org/wiki/Gettext) +です。ファイルは **resources/locales/** 以下に置かれる必要があります。各言語用のサブフォルダーは +以下のようになっている必要があります。 : + + /resources + /locales + /en_US + default.po + /en_GB + default.po + validation.po + /es + default.po + +デフォルトのドメインは 'default' です。ロケールフォルダーは上記のように少なくとも **default.po** +ファイルを含まなくてはなりません。ドメインは翻訳メッセージの任意のグルーピングを参照します。 +グループが使われていない場合、デフォルトのグループが選択されます。 + +CakePHP のライブラリーから抜き出されたコアの文字列は **resources/locales/** 内の **cake.po** という名前の +ファイルに分けて置かれます。 [CakePHP localized library](https://github.com/cakephp/localized) +は、コア (Cake のドメイン) の中にクライエント・フェイシングな翻訳文字列を置いています。 +これらのファイルを利用するには、期待された場所 **resources/locales/\/cake.po** にリンク +またはコピーをしてください。もし不完全または正しくない場合は、修正するためにこのリポジトリーに +PR を送ってください。 + +プラグインは、翻訳ファイルをも含みます。プラグインの名前が `under_scored` なバージョンのものを、 +翻訳メッセージのドメインとして利用する方法は以下の通りです。 : + + MyPlugin + /resources + /locales + /fr + my_plugin.po + /de + my_plugin.po + +翻訳フォルダーは、2文字または3文字の言語 ISO コード、または、言語及び話されている国を含む +`fr_FR`, `es_AR`, `da_DK` のような完全なロケールの名称にしてください。 + + (参考) + +翻訳ファイルの具体例は以下のようになります。 + +``` pot +msgid "My name is {0}" +msgstr "Je m'appelle {0}" + +msgid "I'm {0,number} years old" +msgstr "J'ai {0,number} ans" +``` + +> [!NOTE] +> 翻訳はキャッシュされています。翻訳を変更した後は、必ずキャッシュをクリアしてください。 +> [キャッシュツール](../console-commands/cache) を使って、例えば +> `bin/cake cache clear _cake_core_` を実行するか、手動で `tmp/cache/persistent` +> フォルダをクリアすることができます (ファイルベースのキャッシュを使用している場合)。 + +### I18n を利用して Pot ファイルを生成する + +アプリケーション内の、 \_\_() や他の国際化されたメッセージから pot ファイルを生成するためには、 +i18n シェルを利用できます。より知りたい場合は、 [次の章](../console-commands/i18n) +を読んでください。 + +### デフォルトのロケールを設定する + +デフォルトのロケールは **config/app.php** ファイルの `App.defaultLocale` +を以下のようにすることで設定できます。 : + +``` text +'App' => [ + ... + 'defaultLocale' => env('APP_DEFAULT_LOCALE', 'en_US'), + ... +] +``` + +これは、CakePHP が提供している地域化のライブラリーを使うと示されている場合いつでも、 +デフォルトの翻訳言語、日付のフォーマット、番号のフォーマットおよび通貨を含む、 +アプリケーションのいくつかの様相をコントロールします。 + +### 実行時にロケールを変更する + +翻訳文字列の言語を変更する場合はこのメソッドを呼び出せます。 : + +``` php +use Cake\I18n\I18n; + +I18n::setLocale('de_DE'); +``` + +地域化のツールを使うと、これは数字や日付がどのようにフォーマットされているかについても変更します。 + +## 翻訳の機能を利用する + +CakePHP はアプリケーションを国際化する手助けになるさまざまな機能を提供しています。 +最も頻繁に使われているものとして `__()` があります。 +この機能は一つの翻訳メッセージを引き出すか、見つからなかった場合は同じ文字列を返します。 : + +``` text +echo __('Popular Articles'); +``` + +もし、プラグインの中などで、メッセージをまとめる必要がある場合は、 +別のドメインからメッセージを取ってくるのに `__d()` が利用できます。 : + +``` text +echo __d('my_plugin', 'Trending right now'); +``` + +> [!NOTE] +> もし、名前空間付きのプラグインを翻訳したい場合、ドメイン文字列には `Namespace/PluginName` +> と名前を付けなければなりません。しかし、関連する言語ファイルは、プラグインのフォルダーの中の +> `plugins/Namespace/PluginName/resources/locales/plugin_name.po` になります。 + +翻訳の際に、翻訳すべき文字列が曖昧であることがあります。 +これは、2つの文字列がまったく同じであるのに異なることがらを指し示している場合に起こりえます。 +例えば、英語では 'letter' という単語は複数の意味を持ちます。この問題を解決するために +`__x()` を利用することができます。 : + +``` text +echo __x('written communication', 'He read the first letter'); + +echo __x('alphabet learning', 'He read the first letter'); +``` + +第1引数はメッセージの文脈を示し、第2引数は翻訳されるべきメッセージです。 + +``` pot +msgctxt "written communication" +msgid "He read the first letter" +msgstr "彼は最初の手紙を読みました" +``` + +### 翻訳メッセージで変数を利用する + +翻訳関数を利用すると、メッセージの中あるいは翻訳された文字列の中で定義された特別なマーカーを +用いているメッセージの中で変数を補完することができます。 : + +``` text +echo __("Hello, my name is {0}, I'm {1} years old", ['Sara', 12]); +``` + +マーカーは数値で、渡された配列のキーに対応します。関数に独立した引数として変数を渡すことも可能です。 : + +``` text +echo __("Small step for {0}, Big leap for {1}", 'Man', 'Humanity'); +``` + +あらゆる翻訳関数はプレースホルダーの置き換えに対応しています。 : + +``` text +__d('validation', 'The field {0} cannot be left empty', 'Name'); + +__x('alphabet', 'He read the letter {0}', 'Z'); +``` + +`'` (シングルクオーテーション) は、翻訳メッセージの中ではエスケープコードとして扱われます。 +シングルクオーテーションの間の変数は、置き換えられませんし、文字通りのテキストとして扱われます。 +例えば、 : + +``` text +__("This variable '{0}' be replaced.", 'will not'); +``` + +変数の中で2つ連続してクオーテーションを用いると適切に置き換えられます。 : + +``` text +__("This variable ''{0}'' be replaced.", 'will'); +``` + +これらの関数は [ICU MessageFormatter](https://php.net/manual/ja/messageformatter.format.php) +を活用しています。そのためメッセージと地域化された日付や番号、通貨とを同時に翻訳することが可能です。 : + +``` text +echo __( + 'Hi {0}, your balance on the {1,date} is {2,number,currency}', + ['Charles', new FrozenTime('2014-01-13 11:12:00'), 1354.37] +); + +// 結果 +Hi Charles, your balance on the Jan 13, 2014, 11:12 AM is $ 1,354.37 +``` + +プレースホルダーの中の数字は、出力のきめ細やかなコントロールによって、同様にフォーマットされます。 : + +``` text +echo __( + 'You have traveled {0,number} kilometers in {1,number,integer} weeks', + [5423.344, 5.1] +); + +// 結果 +You have traveled 5,423.34 kilometers in 5 weeks + +echo __('There are {0,number,#,###} people on earth', 6.1 * pow(10, 8)); + +// 結果 +There are 6,100,000,000 people on earth +``` + +以下は、 `number` という言葉の後に続けられるフォーマット修飾子のリストです: + +- `integer`: 小数の部分を取り除く +- `currency`: 地域の通貨、を利用し、小数点以下を丸めます +- `percent`: パーセントとして数をフォーマットします + +日付は、プレースホルダーの数値の後に `date` という語を利用することによってフォーマットされます。 +以下は特別なオプションのリストです: + +- `short` +- `medium` +- `long` +- `full` + +プレースホルダーの数値の後に `time` という語も使用でき、 `date` と同じオプションとして認識されます。 + +> [!NOTE] +> named プレースホルダーは PHP 5.5 以上でサポートされており、 `{name}` として +> フォーマットされます。named プレースホルダーを用いたい場合は、key/value ペアを用いた配列として +> 変数を渡してください。たとえば、 `['name' => 'Sara', 'age' => 12]` というようにです。 +> +> CakePHP で国際化の機能を活用する場合は PHP 5.5 以上を利用することが推奨されています。 +> `php5-intl` エクステンションがインストールされていなくてはなりませんし、ICU のバージョンは +> 48.x.y よりも上であるべきです ( `Intl::getIcuVersion()` で ICU のバージョンを確認してください)。 + +### 複数形 + +見せる言語によって、メッセージを正しく複数形にすることは、アプリケーションの国際化において +重要な部分のひとつです。CakePHP はメッセージの中の複数形を正しく選択するいつかの方法を提供しています。 + +#### ICU の複数形選択を利用する + +一つ目は、翻訳関数のデフォルトである `ICU` のメッセージフォーマットを活用する方法です。 +翻訳ファイルにおいて、以下の文字列があるかもしれません。 + +``` pot +msgid "{0,plural,=0{No records found} =1{Found 1 record} other{Found # records}}" +msgstr "{0,plural,=0{Ningún resultado} =1{1 resultado} other{# resultados}}" + +msgid "{placeholder,plural,=0{No records found} =1{Found 1 record} other{Found {1} records}}" +msgstr "{placeholder,plural,=0{Ningún resultado} =1{1 resultado} other{{1} resultados}}" +``` + +そしてアプリケーション内では、このような文字列の翻訳のどちらかを出力するために、以下のようなコードを +使ってください。 : + +``` text +__('{0,plural,=0{No records found }=1{Found 1 record} other{Found # records}}', [0]); + +// 引数 {0} を 0 として "Ningún resultado" を返します。 + +__('{0,plural,=0{No records found} =1{Found 1 record} other{Found # records}}', [1]); + +// 引数 {0} は 1 なので "1 resultado" を返します。 + +__('{placeholder,plural,=0{No records found} =1{Found 1 record} other{Found {1} records}}', [0, 'many', 'placeholder' => 2]) + +// 引数 {placeholder} は 2 で、引数 {1} は 'many' なので +// "many resultados" を返します。 +``` + +いま利用したフォーマットをよくみると、どのようにメッセージが構築されているのかがはっきりするでしょう。 : + +``` text +{ [count placeholder],plural, case1{message} case2{message} case3{...} ... } +``` + +この `[count placeholder]` は翻訳関数にわたす変数の配列の key の番号です。 +正しい複数形を選択するのに使われます。 + +`{message}` の中の `[count placeholder]` を参照するためには `#` を +利用しなくてはならないことに注意してください。 + +もちろん、コードの中で完全な複数形を求めていない場合は、メッセージ ID をよりシンプルにすることができます。 + +``` pot +msgid "search.results" +msgstr "{0,plural,=0{Ningún resultado} =1{1 resultado} other{{1} resultados}}" +``` + +この場合は新しい文字列を使います。 : + +``` text +__('search.results', [2, 2]); + +// 戻り値: "2 resultados" +``` + +後者のバージョンでは、デフォルトの言語でさえも翻訳ファイルが必要になるという欠点がありますが、 +コードの可読性が上がり、複雑な複数形の選択文字列が翻訳ファイルに入らないという利点もあります。 + +複数形において、直接数値を指定するやり方は実用的でないことがあります。例えば、アラビア語のような言語では、 +少ないものの複数形と多いものの複数形が異なります。 +このような場合は ICU のマッチングエイリアスを利用できます。以下のように書く代わりに: + +``` text +=0{No results} =1{...} other{...} +``` + +以下のようにすることができます。 : + +``` css +zero{No Results} one{One result} few{...} many{...} other{...} +``` + +各言語のエイリアスの完全な概要を知りたい場合は +[Language Plural Rules Guide](https://unicode-org.github.io/cldr-staging/charts/37/supplemental/language_plural_rules.html) +をご参照ください。 + +#### Gettext の複数形選択を使用する + +二番目の複数形のフォーマットは、Gettext のビルトイン機能を用いたものです。 +この場合、複数形ごとに分かれた翻訳メッセージの行を作成した `.po` ファイルに複数形が置かれます。: + +``` pot +# One message identifier for singular +msgid "One file removed" +# Another one for plural +msgid_plural "{0} files removed" +# Translation in singular +msgstr[0] "Un fichero eliminado" +# Translation in plural +msgstr[1] "{0} ficheros eliminados" +``` + +これを別のフォーマットで利用するとき、別の翻訳機能を利用する必要があります。 : + +``` php +// 戻り値: "10 ficheros eliminados" +$count = 10; +__n('One file removed', '{0} files removed', $count, $count); + +// ドメインの中でそれを使うことが可能です。 +__dn('my_plugin', 'One file removed', '{0} files removed', $count, $count); +``` + +`msgstr[]` 内の数値は、言語の複数形のために Gettext によって割り当てられた数値です。 +言語によっては、例えばクロアチア語では、2つ以上の複数形が存在します。 + +``` pot +msgid "One file removed" +msgid_plural "{0} files removed" +msgstr[0] "{0} datoteka je uklonjena" +msgstr[1] "{0} datoteke su uklonjene" +msgstr[2] "{0} datoteka je uklonjeno" +``` + +各言語の数値の複数形についてより詳細な説明は +[Launchpad languages page](https://translations.launchpad.net/+languages) をご覧ください。 + +## 独自の翻訳機構を作成する + +翻訳のメッセージが置かれている場所や方法についての CakePHP の慣習を拡張する必要がもしあるのなら、 +独自の翻訳メッセージローダーを作成することができます。独自の翻訳機構を作成する最も簡単な方法は、 +1つのドメインのローダーを指定して、以下を設置します。 : + +``` php +use Aura\Intl\Package; + +I18n::setTranslator('animals', function () { + $package = new Package( + 'default', // フォーマット戦略 (ICU) + 'default' // フォールバックドメイン + ); + $package->setMessages([ + 'Dog' => 'Chien', + 'Cat' => 'Chat', + 'Bird' => 'Oiseau' + ... + ]); + + return $package; +}, 'fr_FR'); +``` + +上記のコードは **config/bootstrap.php** に追加してください。そうすれば翻訳の機能が使われる前に +見つかります。翻訳機構を作成するのに最低限必要なのは、ローダー機能が `Aura\Intl\Package` +オブジェクトを返すことです。一旦コードを置けば、翻訳機能は以下のように利用できるでしょう。 : + +``` php +I18n::setLocale('fr_FR'); +__d('animals', 'Dog'); // "Chien" を返す +``` + +見てお分かりの通り、 `Package` オブジェクトは配列として翻訳メッセージを受け取ります。 +インラインコードや、他のファイルの読み込み、別の機能の呼び出しなどのときに、いつでも +`setMessages()` メソッドを渡すことができます。CakePHP はメッセージが読み込まれる場所を +変える必要がある場合に、使いまわせるいくつかのローダー機能を提供しています。例えば、 +**.po** ファイルを利用しているのに、他の場所から読み込みたい場合は、 : + +``` php +use Cake\I18n\MessagesFileLoader as Loader; + +// Load messages from resources/locales/folder/sub_folder/filename.po からメッセージをロード +I18n::setTranslator( + 'animals', + new Loader('filename', 'folder/sub_folder', 'po'), + 'fr_FR' +); +``` + +のようになります。 + +### メッセージのパーサーを作成する + +CakePHP が利用しているものと同じやり方を使い続けることもできますが、 `PoFileParser` +以外のメッセージパーサーを利用してみてください。たとえば、 `YAML` を用いた翻訳メッセージを +読み込みたい場合、まずはじめにパーサークラスを作成する必要があります。 : + +``` php +namespace App\I18n\Parser; + +class YamlFileParser +{ + public function parse($file) + { + return yaml_parse_file($file); + } +} +``` + +アプリケーションの **src/I18n/Parser** ディレクトリー内にこのファイルを作成してください。 +続いて、 **resources/locales/fr_FR/animals.yaml** として翻訳ファイルを作ります。 + +``` yaml +Dog: Chien +Cat: Chat +Bird: Oiseau +``` + +最後に、翻訳を読み込むドメインと場所を設定します。 : + +``` php +use Cake\I18n\MessagesFileLoader as Loader; + +I18n::setTranslator( + 'animals', + new Loader('animals', 'fr_FR', 'yaml'), + 'fr_FR' +); +``` + +### 包括的な翻訳機構を作成する + +対応が必要なドメインおよび場所ごとに、 `I18n::translator()` を呼び出して翻訳機構を設定するのは、 +非常に面倒です。わずかな違いで対応が必要な場合は特にです。この問題を避けるために、CakePHP では +ドメインごとに包括的な翻訳機構のローダーを定義することができます。 + +デフォルトのドメインとあらゆる言語のすべての翻訳を、外部のサービス読み込みたいときのことを +想像してみてください。 : + +``` php +use Cake\I18n\Package; +// Prior to 4.2 you need to use Aura\Intl\Package + +I18n::config('default', function ($domain, $locale) { + $locale = Locale::parseLocale($locale); + $lang = $locale['language']; + $messages = file_get_contents("http://example.com/translations/$lang.json"); + + return new Package( + 'default', // フォーマット機構 + null, // フォールバック (デフォルトドメインにはありません) + json_decode($messages, true) + ) +}); +``` + +上記の例は、翻訳を含む JSON ファイルを読み込む外部のサービスの例です。 アプリケーション内で +リクエストされたどの場所でも `Package` オブジェクトをビルドします。 + +特定のローダーが設定されていない全てのパッケージで、パッケージをロードする方法を変更したい場合、 +`_fallback` パッケージを使用することによって、代替パッケージローダーに置き換えることができます。 : + +``` php +I18n::config('_fallback', function ($domain, $locale) { + // パッケージを生成するカスタムコードはこちら。 +}); +``` + +### 独自の翻訳機構における複数形と文脈について + +`setMessages()` に用いられている配列は、異なるドメイン配下にメッセージを翻訳機構が置くために +指示をだす、または、Gettext の複数形選択のきっかけとなるために作成されます。 +以下は、異なる文脈において同じキーを翻訳に設置する例です。 : + +``` php +[ + 'He reads the letter {0}' => [ + 'alphabet' => 'Él lee la letra {0}', + 'written communication' => 'Él lee la carta {0}' + ] +] +``` + +同様にして、メッセージの配列で用いられているGettextの複数形を、複数形ごとのキーを用いて +ネストされた配列で表現することもできます。 : + +``` php +[ + 'I have read one book' => 'He leído un libro', + 'I have read {0} books' => [ + 'He leído un libro', + 'He leído {0} libros' + ] +] +``` + +### 異なるフォーマット機構を使う + +前の例では最初の引数として `default` を用いるようにパッケージが作成されていました。そして、 +これは使用されているフォーマット機構と対応するコメントを示します。 +フォーマット機構は、翻訳メッセージに変数を渡す、そして正しい複数形を選択するクラスです。 + +もし、レガシーなアプリケーションを扱っている、あるいは ICU メッセージフォーマットが提供している機能が +必要ない場合、CakePHP は `sprinf` フォーマット機構も提供しています。 : + +``` text +return Package('sprintf', 'fallback_domain', $messages); +``` + +翻訳されるメッセージは `sprintf()` 関数に引数を入れて引き渡されます。 : + +``` text +__('Hello, my name is %s and I am %d years old', 'José', 29); +``` + +デフォルトのフォーマット機構を最初に使われる以前の CakePHP に作成されたすべての翻訳機構に +設置することができます。 + +これは、 `translator()` や `config()` メソッドを使って手で作成された翻訳機構を含みません。 : + +``` php +I18n::defaultFormatter('sprintf'); +``` + +## 日付や数値を地域化する + +アプリケーションで日付や数値を出力する際に、ページが表示される国や地域の適切なフォーマットに沿って +フォーマットされる必要があることがあります。 + +日付や数値を表示する方法を変えるためには、現在のロケールの設定を変更し、正しいクラスを使用する +必要があります。 : + +``` php +use Cake\I18n\I18n; +use Cake\I18n\Time; +use Cake\I18n\Number; + +I18n::setLocale('fr-FR'); + +$date = new Time('2015-04-05 23:00:00'); + +echo $date; // 05/04/2015 23:00 と表示 + +echo Number::format(524.23); // 524,23 と表示 +``` + +フォーマットのオプションをより知りたい場合は、 [日付と時刻](../core-libraries/time) や +[Number](../core-libraries/number) を読んでください。 + +ORM で返されるデフォルトの日付では結果は `Cake\I18n\Time` クラスを利用しています。そのため、 +アプリケーションで直接表示することは、現在のロケールの変更に影響されます。 + +### 地域化された日時データをパースする + +リクエストから地域化されたデータを受け取る場合、ユーザーが地域化したフォーマットから日時の情報を +取得するのが良いでしょう。コントローラー、あるいは [ミドルウェア](../controllers/middleware) では、 +日付、時刻、そして日時の型が地域化のフォーマットをパースするために定義できます。 : + +``` php +use Cake\Database\TypeFactory; + +// デフォルトのロケールフォーマットのパースを有効化 +TypeFactory::build('datetime')->useLocaleParser(); + +// カスタム datetime フォーマットパース書式の設定 +TypeFactory::build('datetime')->useLocaleParser()->setLocaleFormat('dd-M-y'); + +// IntlDateFormatter 定数を使用することもできます。 +TypeFactory::build('datetime')->useLocaleParser() + ->setLocaleFormat([IntlDateFormatter::SHORT, -1]); +``` + +デフォルトでパースするフォーマットは、デフォルトの文字列のフォーマットと同じです。 + +### リクエストデータをユーザーのタイムゾーンから変換する + +様々なタイムゾーンのユーザーからのデータを扱う場合には、 +リクエストデータにおける日時をアプリケーションのタイムゾーンへ変換する必要が出てきます。 +この処理を簡単にするために、 +コントローラーもしくは [ミドルウェア](../controllers/middleware) の `setUserTimezone()` を使うことができます: + +``` php +// ユーザーのタイムゾーンを設定する +TypeFactory::build('datetime')->setUserTimezone($user->timezone); +``` + +いったん設定をすると、アプリケーションがリクエストデータからエンティティーを作成もしくは更新をする時に、 +ORM が日時の値をユーザーのタイムゾーンからアプリケーションのタイムゾーンへ自動で変換します。 +これは、常にアプリケーションが `App.defaultTimezone` で定義されたタイムゾーンで動作することを保証します。 + +あなたのアプリケーションが様々なアクションにおける日時の情報を扱う場合、 +ミドルウェアを使ってタイムゾーンの変換とロケールのパースの両方を設定することができます: + +``` php +namespace App\Middleware; + +use Cake\Database\TypeFactory; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; + +class DatetimeMiddleare implements MiddlewareInterface +{ + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface { + // リクエストからユーザーを取得 + // この例では、ユーザーエンティティーにタイムゾーン属性があるものと仮定 + $user = $request->getAttribute('identity'); + if ($user) { + TypeFactory::build('datetime') + ->useLocaleParser() + ->setUserTimezone($user->timezone); + } + + return $handler->handle($request); + } +} +``` + +## 自動でリクエストデータに基づいたロケールを選択する + +`LocaleSelectorMiddleware` をアプリケーション内で使用すると、CakePHP は自動で現在のユーザーに基づいた +ロケールを設定します。 : + +``` php +// src/Application.php の中で +use Cake\I18n\Middleware\LocaleSelectorMiddleware; + +// 新しいミドルウェアを追加するために middleware 関数を更新してください。 +public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue +{ + // ミドルウェアの追加し、有効なロケールの設定 + $middlewareQueue->add(new LocaleSelectorMiddleware(['en_US', 'fr_FR'])); + // 全てののロケールヘッダー値を受け入れる + $middlewareQueue->add(new LocaleSelectorMiddleware(['*'])); +} +``` + +`LocaleSelectorMiddleware` は `Accept-Language` ヘッダーを用いて、ユーザーの選択したロケールを +自動的に設定します。どのロケールが自動で使われるかを制限するロケールリストオプションを使用することが +できます。 + +## コンテンツ/エンティティーの翻訳 + +コンテンツ/エンティティーを翻訳したい場合には、 +[Translate Behavior](../orm/behaviors/translate) をご覧ください。 diff --git a/docs/ja/core-libraries/logging.md b/docs/ja/core-libraries/logging.md new file mode 100644 index 0000000000..5e646f9914 --- /dev/null +++ b/docs/ja/core-libraries/logging.md @@ -0,0 +1,466 @@ +# ロギング + +CakePHP コアクラスの Configure 設定は、内部で何が起きているかを知るための有益な手段です。 +そこで、何が起きているかをディスクにログデータとして保存する必要が出てくるでしょう。 +SOAP や AJAX、REST API のような技術を一緒に使うとデバッグはむしろ難しくなるでしょう。 + +ロギングは、時系列でアプリケーションで何が起きているかを知るための手段です。 +何の検索ワードが使われましたか?何のエラーがユーザーに表示されましたか? +どのくらいの頻度で特定のクエリーが実行されましたか? + +CakePHP でデータのロギングは簡単です。 log() 関数は、多くの CakePHP クラスの共通の祖先である +`LogTrait` により提供されています。もし、環境が CakePHP のクラス (Controller や Component, +View 等) であれば、あなたはデータを記録することができます。直接 `Log::write()` を同様に使うことも +出来ます。 [Writing To Logs](#writing-to-logs) を参照してください。 + +## ロギング設定 + +アプリケーションの起動処理の間に、 `Log` の設定は完了しているはずで **config/app.php** は、 +これのためです。多かれ少なかれアプリケーションが望むロガーを定義できます。 +ロガーは、 `Cake\Log\Log` を使い設定する必要があります。一例として: + +``` php +use Cake\Log\Engine\FileLog; +use Cake\Log\Log; + +// Classname using logger 'class' constant +Log::setConfig('info', [ + 'className' => FileLog::class, + 'path' => LOGS, + 'levels' => ['info'], + 'file' => 'info', +]); + +// 短いクラス名 +Log::setConfig('debug', [ + 'className' => 'File', + 'path' => LOGS, + 'levels' => ['notice', 'debug'], + 'file' => 'debug', +]); + +// 完全な名前空間の名前 +Log::setConfig('error', [ + 'className' => 'Cake\Log\Engine\FileLog', + 'path' => LOGS, + 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'], + 'file' => 'error', +]); +``` + +上記では二つのロガーを作っています。一つは `debug` で、その他は `error` です。 +それぞれが異なるレベルのメッセージを処理するために構成されています。 +また、それらは別々のファイルにメッセージを格納しますので、 +debug/notice/info のログをより深刻なエラーから分離するのが簡単です。 + +色々なレベルに応じた情報と意味の一覧は [Logging Levels](#logging-levels) を参照してください。 + +一度設定を作成した後、それを変更することはできません。 `Cake\Log\Log::drop()` と +`Cake\Log\Log::setConfig()` を使って、設定を削除、再作成すべきです。 + +それらもまた、クロージャーを与えることによりロガーを作成することが可能です。 +クロージャーこれはロガーオブジェクトがどのように構築されるかを完全に制御する必要がある時に役立ちます。 +クロージャーは構築されたロガーのインスタンスを返さなければなりません。例えば: + +``` php +Log::setConfig('special', function () { + return new \Cake\Log\Engine\FileLog(['path' => LOGS, 'file' => 'log']); +}); +``` + +設定オプションは `DSN` の文字列で渡すことも可能です。これは環境変数や `PaaS` +プロバイダーを扱っている時に役立ちます。 : + +``` php +Log::setConfig('error', [ + 'url' => 'file:///full/path/to/logs/?levels[]=warning&levels[]=error&file=error', +]); +``` + +> [!WARNING] +> If you do not configure logging engines, log messages will not be stored. + +## ログエンジンの作成 + +ログエンジンはアプリケーションの一部やプラグインの一部になりえます。 +例えば `DatabaseLog` という名前のデータベースロガーがあったとします。 +アプリケーションの一部として **src/Log/Engine/DatabaseLog.php** に置かれます。 +プラグインの一部として **plugins/LoggingPack/src/Log/Engine/DatabaseLog.php** に置かれます。 +また、ログエンジンの設定は `Cake\Log\Log::setConfig()` を使う必要があります。 +例えば DatabaseLog の設定はこのようになります。 : + +``` php +// src/Log 用 +Log::setConfig('otherFile', [ + 'className' => 'Database', + 'model' => 'LogEntry', + // ... +]); + +// LoggingPack というプラグイン用 +Log::setConfig('otherFile', [ + 'className' => 'LoggingPack.Database', + 'model' => 'LogEntry', + // ... +]); +``` + +ログエンジンを設定する時、 `className` パラメーターは、ログハンドラーを配置しロードするために使用されます。 +その他の設定プロパティーの全ては、ログエンジンのコンストラクターに配列として渡されます。 : + +``` php +namespace App\Log\Engine; +use Cake\Log\Engine\BaseLog; + +class DatabaseLog extends BaseLog +{ + public function __construct($options = []) + { + parent::__construct($options); + // ... + } + + public function log($level, $message, array $context = []) + { + // データベースに書き込みます。 + } +} +``` + +CakePHP では 全てのログエンジンにおいて `Psr\Log\LoggerInterface` を実装する必要があります。 +`Cake\Log\Engine\BaseLog` クラスは、 `log()` メソッドを実装することだけを要求しますので、 +そのインターフェイスを満たすための簡単な方法です。 + +### ロギングフォーマッタ + +ロギングフォーマッタは、ログメッセージがストレージエンジンに依存せずにフォーマットされる方法を制御することができます。 +各コア提供のログエンジンは、後方互換性のある出力を維持するように設定されたフォーマッタが付属しています。 +しかし、あなたの要件に合うようにフォーマッタを調整することができます。 +フォーマッターはロギングエンジンと一緒に設定されます。 : + +``` php +use Cake\Log\Engine\SyslogLog; +use App\Log\Formatter\CustomFormatter; + +// オプションのない単純なフォーマット構成 +Log::setConfig('error', [ + 'className' => SyslogLog::class, + 'formatter' => CustomFormatter::class, +]); + +// 追加オプションを使用してフォーマッターを構成します +Log::setConfig('error', [ + 'className' => SyslogLog::class, + 'formatter' => [ + 'className' => CustomFormatter::class, + 'key' => 'value', + ], +]); +``` + +独自のロギングフォーマッターを実装するには `Cake\Log\Format\AbstractFormatter` またはそのサブクラスのいずれかを継承する必要があります。 +実装する必要がある主なメソッドは `format($level, $message, $context)` で、これはログメッセージの書式設定を担当します。 + +
    + +`FileLog` エンジンは次のオプションを受け取ります。 + +
    + +- `size` 基本的なログファイルローテーションの実装に使われます。もしログファイルサイズが + 特定のファイルサイズに到達した場合、既存のファイルはファイル名にタイムスタンプを付け加えることで + 名前が変更され、新しいログファイルが作成されます。整数バイト値か '10MB' や '100KB' などの + 人間が読みやすい文字列にすることができます。デフォルトは 10MB です。 +- `rotate` ログファイルが削除される前に指定された回数ローテートされます。もし値が 0 の場合は、 + ログローテーションされずに削除されます。デフォルトは 10 です。 +- `mask` 作成されるファイルのパーミッションを設定します。 + もし空のままであればデフォルトのパーミッションが使われます。 + +> [!WARNING] +> エンジンは接尾辞 `Log` を持っています。 +> クラス名が `SomeLogLog` のような接尾辞が二重になった名前は避けるべきです。 + +> [!NOTE] +> 起動処理でロガーの設定をすべきです。 **config/app.php** はログアダプターの設定の慣習的な場所です。 +> +> デバッグモード中では、FileEngine 使用時に無用なエラーの発生を避けるため、 +> ディレクトリーが存在しない時には自動的に作成されるようになりました。 + +## エラーと例外のロギング + +エラーと例外も記録できます。 app.php ファイル内に関連する値を設定することで +ログに記録することができます。debug が `true` のときにエラーが表示され、debug が `false` のときに +ログに記録されます。捕捉されなかった例外をログに記録するときは `log` オプションを +`true` に設定してください。詳しくは、 [構成設定](../development/configuration) を参照ください。 + +## ログストリームの相互作用 + +`Cake\Log\Log::configured()` で一連の設定を確認することができます。 +`configured()` の戻り値は、現在設定されている全てを配列で返します。 +`Cake\Log\Log::drop()` を使って、ストリームを削除することができます。 +一度、ログの設定が削除されると、ロガーはメッセージを受信しなくなります。 + +## FileLog アダプターの利用 + +その名前が示すように、 FileLog は、ログメッセージをファイルに書き込みます。 +書かれたログメッセージのレベルは、メッセージが書き込まれたファイルの名前で決まります。 +もしレベルが指定されなければ、エラーログを書き込むための `LOG_ERR` が使われます。 +デフォルトのログの場所は `logs/$level.log` です。 : + +``` php +// CakePHP クラスの中でこれを実行 +$this->log("何かがうまくいかなかった!"); + +// logs/error.log に追記された結果 +// 2007-11-02 10:22:02 Error: 何かがうまくいかなかった! +``` + +設定されたディレクトリーは、ウェブサーバーユーザー権限で正しくロギングできるように +書き込み可能にしなければなりません。 + +ロガーの設定により、追加/代替の FileLog の場所を設定できます。FileLog は、独自のパスを使用するために +`path` を設定できます。 : + +``` php +Log::setConfig('custom_path', [ + 'className' => 'File', + 'path' => '/path/to/custom/place/' +]); +``` + +> [!WARNING] +> もしロギングアダプターを設定していなければ、ログメッセージは保存されません。 + +## Syslog へのロギング + +本番環境では、ファイルロガーの代わりに syslog を使用するようにシステムをセットアップすることを +強く勧めます。これは、(大部分は)ノンブロッキング方式で全て書き込むため、よりよく動作し、 +そしてあなたのオペレーティングシステムのロガーは、独立してファイルのローテーションの設定ができ、 +前処理を記述したり、ログを完全に別のストレージを使うことができます。 + +syslog を使うためには、デフォルトの FileLog エンジンを使うのとよく似ています。 +ロギングに使用するエンジンとして Syslog を指定する必要があります。下記の設定は、デフォルトのロガーを +`Syslog` に置き換えるものです。これは、 **bootstrap.php** ファイルで設定します。 : + +``` php +Log::setConfig('default', [ + 'engine' => 'Syslog' +]); +``` + +Syslog ロギングエンジンのための設定配列は、以下のキーを認識します。 + +- `format`: 2つのプレースホルダーを持つ sprintf テンプレート文字列で1つ目は、 + エラーレベルで、2つ目はメッセージのためのものです。このキーは、ロギングメッセージ内の + サーバーやプロセスに関する追加の情報を付加するのに便利です。例えば、 + `%s - Web Server 1 - %s` は、プレースホルダーが置き換えられると、 + `error - Web Server 1 - An error occurred in this request` のようになります。 + このオプションは非推奨です。代わりに [Logging Formatters](#logging-formatters) を使用する必要があります。 +- `prefix`: 全てのログメッセージの先頭につく文字列です。 +- `flag`: ロガーへの接続を開くために使用される整数値のフラグで、デフォルトは、 + `LOG_ODELAY` が使用されます。 詳しくは、 `openlog` のドキュメントをご覧ください。 +- `facility`: syslog で使用するロギングスロット。デフォルトでは、 `LOG_USER` が使用されます。 + 詳しくは、 ドキュメントの `syslog` をご覧ください。 + +## ログへの書き込み + +ログファイルへの書き込みは、2つの方法があります。1つは、 +静的な `Cake\Log\Log::write()` メソッドを使用することです。 : + +``` php +Log::write('debug', '何かがうまくいかなかった'); +``` + +2つ目は、 `LogTrait` を使用しているクラスに用意された `log()` ショートカット関数を使用することです。 +log() を呼ぶと、内部的に `Log::write()` が呼ばれます。 : + +``` php +// LogTrait を使用した クラス内でこれを実行 +$this->log("何かがうまくいかなかった!", 'debug'); +``` + +全ての設定されたログストリームは、 `Cake\Log\Log::write()` が呼ばれるたびに +順次書き込まれます。もし設定されていないログアダプターを持っているならば、 +`log()` は `false` を返し何も書き込みません。 + +### レベルを使う + +CakePHP は、標準 POSIX のロギングレベルをサポートします。 +各レベルは、増加する重要度を表します。 + +- Emergency: システムは使用出来ません +- Alert: 今すぐ行動する必要がある +- Critical: 致命的な状態 +- Error: エラー状態 +- Warning: 警告状態 +- Notice: 正常であるが、重大な状態 +- Info: インフォメーションメッセージ +- Debug: デバッグレベルメッセージ + +ロガー設定時やログメッセージの書き出し中に、名前からこれらのレベルを引くことができます。 +あるいは、 `Cake\Log\Log::error()` のような便利メソッドを使うと +ログレベルを明確に示すことができます。上記のレベルにないレベルを使っていると例外が発生します。 + +> [!NOTE] +> ロガー設定の中で `levels` が空の値をセットされたとき、任意のレベルのメッセージを受け取ります。 + +### ロギングスコープ + +しばしば、異なるサブシステムやアプリケーションの一部で異なるロギングの振る舞いを設定したく +なるでしょう。ある E コマースショップの例を挙げます。注文と支払いのロギングをその他の +重大ではないログとは分けておきたい場合です。 + +CakePHP は、このコンセプトをロギングスコープで実現します。ログメッセージが書かれた時、 +スコープ名を指定できます。そのスコープとして設定されたロガーがある場合、ログメッセージは +これらのロガーに向けられます。例: + +``` php +// すべてのレベルを受け取るように、 logs/shops.log を設定。 +// スコープは `orders` と `payments` のみ +Log::setConfig('shops', [ + 'className' => 'File', + 'path' => LOGS, + 'levels' => [], + 'scopes' => ['orders', 'payments'], + 'file' => 'shops.log', +]); + +// すべてのレベルを受け取るように、 logs/payments.log を設定。 +// スコープは `payments` のみ +Log::setConfig('payments', [ + 'className' => 'File', + 'path' => LOGS, + 'levels' => [], + 'scopes' => ['payments'], + 'file' => 'payments.log', +]); + +Log::warning('これは、 shops.log のみに書かれます', ['scope' => ['orders']]); +Log::warning('これは、 shops.log と payments.log の両方に書かれます', ['scope' => ['payments']]); +``` + +スコープは単一の文字列もしくは数値インデックス配列として渡すことができます。 +コンテキストとしてより多くのデータを渡す機能が、この形式を使用すると制限されることに注意してください。 : + +``` php +Log::warning('これは警告です', ['orders']); +Log::warning('これは警告です', 'payments'); +``` + +> [!NOTE] +> ロガー設定の中で `scopes` に空の配列や `null` がセットされたとき、 +> 任意のメッセージを受け取ります。それに `false` をセットすると、 +> スコープのないメッセージにしかマッチしません。 + +## Log API + +`class` Cake\\Log\\**Log** + +`static` Cake\\Log\\Log::**setConfig**($key, $config) + +param string \$name +接続されるロガーの名前で、後でロガーを削除するために使用されます。 + +param array \$config +ロガーの設定情報とコンストラクター引数の配列です。 + +ロガーの設定を取得したり、セットしたりします。詳細は [Log Configuration](#log-configuration) を参照してください。 + +`static` Cake\\Log\\Log::**configured**() + +returns +設定されたロガーの配列です。 + +設定された複数のロガーの名前を取得します。 + +`static` Cake\\Log\\Log::**drop**($name) + +param string \$name +今後メッセージを受信させたくないロガーの名前です。 + +`static` Cake\\Log\\Log::**write**($level, $message, $scope = []) + +全ての設定されたロガーにメッセージを書き込みます。 +`$level` は、作成されたログメッセージのレベルを表します。 +`$message` は、書き込みたいログのメッセージです。 +`$scope` は、スコープ(一つもしくは複数)でログメッセージが作成されます。 + +`static` Cake\\Log\\Log::**levels**() + +引数なしでメソッドを呼び出します。例えば、 Log::levels() は、現在のレベルの設定を取得します。 + +### 便利なメソッド + +以下の便利メソッドは、適切なログレベルで \$message を記録するために追加されました。 + +`static` Cake\\Log\\Log::**emergency**($message, $scope = []) + +`static` Cake\\Log\\Log::**alert**($message, $scope = []) + +`static` Cake\\Log\\Log::**critical**($message, $scope = []) + +`static` Cake\\Log\\Log::**error**($message, $scope = []) + +`static` Cake\\Log\\Log::**warning**($message, $scope = []) + +`static` Cake\\Log\\Log::**notice**($message, $scope = []) + +`static` Cake\\Log\\Log::**info**($message, $scope = []) + +`static` Cake\\Log\\Log::**debug**($message, $scope = []) + +## ロギングトレイト + +> トレイトはロギングへのショートカットを提供します。 + +`method` Cake\\Log\\Log::**log**($msg, $level = LOG_ERR) + +## Monolog を使用する + +Monolog は、PHP で人気のロガーです。CakePHP のロガーと同じインターフェイスを実装しています。 +なので、アプリケーションでデフォルトのロガーとして使うことが簡単です。 + +Composer を使って Monolog をインストールしたら、 +`Log::setConfig()` メソッドを使ってロガーを設定してください。 : + +``` php +// config/bootstrap.php + +use Monolog\Logger; +use Monolog\Handler\StreamHandler; + +Log::setConfig('default', function () { + $log = new Logger('app'); + $log->pushHandler(new StreamHandler('path/to/your/combined.log')); + + return $log; +}); + +// オプションで、今使っていない不要なデフォルトのロガーを止めてください +Log::drop('debug'); +Log::drop('error'); +``` + +もし異なるロガーをコンソールで設定したいのであれば、同じ方法を使ってください。 : + +``` php +// config/bootstrap_cli.php + +use Monolog\Logger; +use Monolog\Handler\StreamHandler; + +Log::setConfig('default', function () { + $log = new Logger('cli'); + $log->pushHandler(new StreamHandler('path/to/your/combined-cli.log')); + + return $log; +}); + +// オプションで、今使っていない不要なデフォルトの CLI ロガーを止めてください +Configure::delete('Log.debug'); +Configure::delete('Log.error'); +``` + +> [!NOTE] +> コンソールで固有なロガーを使用する場合は、アプリケーションロガーを条件付きで設定してください。 +> これは複数のログが重複することを防ぎます。 diff --git a/docs/ja/core-libraries/number.md b/docs/ja/core-libraries/number.md new file mode 100644 index 0000000000..9af6c74a15 --- /dev/null +++ b/docs/ja/core-libraries/number.md @@ -0,0 +1,300 @@ +# Number + +`class` Cake\\I18n\\**Number** + +あなたが `View` の外で `NumberHelper` 機能が必要な場合、 +`Number` クラスを次のように使います。 : + +``` php +namespace App\Controller; + +use Cake\I18n\Number; + +class UsersController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('Auth'); + } + + public function afterLogin() + { + $storageUsed = $this->Auth->user('storage_used'); + if ($storageUsed > 5000000) { + // 割り当てられた users に通知 + $this->Flash->success(__('あなたは {0} ストレージを使用しています', Number::toReadableSize($storageUsed))); + } + } +} +``` + + + +以下の全ての機能は、整形された数値を返します。これらは自動的にビューに出力を表示しません。 + +## 通貨フォーマット + +`method` Cake\\I18n\\Number::**currency**(mixed $value, string $currency = null, array $options = []) + +このメソッドは、共通通貨フォーマット(ユーロ、英ポンド、米ドル)で数値を表示するために使用されます。 +ビュー内で次のように使います。 : + +``` php +// NumberHelper としてコール +echo $this->Number->currency($value, $currency); + +// Number としてコール +echo Number::currency($value, $currency); +``` + +1つ目のパラメーター `$value` は、合計金額をあらわす浮動小数点数でなければいけません。 +2つ目のパラメーターは、あらかじめ定義された通貨フォーマット方式を選択するための文字列です。 + +| \$currency | 通貨の種類によってフォーマットされた 1234.56 | +|------------|----------------------------------------------| +| EUR | €1.234,56 | +| GBP | £1,234.56 | +| USD | \$1,234.56 | + +3つ目のパラメーターは、出力を定義するためのオプションの配列です。 +次のオプションが用意されています。 + +| オプション | 説明 | +|----|----| +| before | レンダリングされた数値の前に表示されるテキスト。 | +| after | レンダリングされた数値の後に表示されるテキスト。 | +| zero | ゼロ値の場合に使用するテキストで、 文字列か数値で指定できます。例. 0, '無料!' | +| places | 小数点以下の桁数を指定します。例. 2 | +| precision | 小数点以下の最大桁数を指定します。例. 2 | +| locale | 数値フォーマットに使用するロケール名。 例. "fr_FR". | +| fractionSymbol | 小数に使用する文字列。例. ' cents' | +| fractionPosition | fractionSymbol で指定した文字列を、小数の 'before' または 'after' のどちらに配置するか。 | +| pattern | 数値のフォーマットに使用する ICU 数値パターン。 例. \#,###.00 | +| useIntlCode | 通貨記号を国際通貨コードに置き換えるために `true` を指定する。 | + +\$currency の値が `null` の場合、デフォルト通貨は `Cake\I18n\Number::defaultCurrency()` によって設定されます。 + +## デフォルト通貨の設定 + +`method` Cake\\I18n\\Number::**defaultCurrency**($currency) + +デフォルト通貨のための setter/getter です。これによって、常に `Cake\I18n\Number::currency()` に通貨を渡したり、 +他のデフォルトを設定することによって全ての通貨の出力を変更したりする必要がなくなります。 +`$currency` に `false` が設定された場合、現在格納されている値をクリアします。 +デフォルトでは、設定されていれば `intl.default_locale` を取得し、そうでない場合は 'en_US' を設定します。 + +## 浮動小数点数フォーマット + +`method` Cake\\I18n\\Number::**precision**(float $value, int $precision = 3, array $options = []) + +このメソッドは指定された精度(小数点以下)で数値を表示します。 +定義された精度のレベルを維持するために丸めます。 : + +``` php +// NumberHelper としてコール +echo $this->Number->precision(456.91873645, 2); + +// 出力 +456.92 + +// Number としてコール +echo Number::precision(456.91873645, 2); +``` + +## パーセンテージフォーマット + +`method` Cake\\I18n\\Number::**toPercentage**(mixed $value, int $precision = 2, array $options = []) + +| オプション | 説明 | +|----|----| +| multiply | 値を 100 で乗算しなければならないかどうかを示す Boolean 値です。少数のパーセンテージに便利です。 | + +このメソッドは `Cake\I18n\Number::precision()` のように、 +与えられた精度に応じて(精度を満たすように丸めて)数値をフォーマットします。 +このメソッドはパーセンテージとして数値を表現し、パーセント記号を追加して出力します。 : + +``` php +// NumberHelper としてコール。 出力: 45.69% +echo $this->Number->toPercentage(45.691873645); + +// Number としてコール。 出力: 45.69% +echo Number::toPercentage(45.691873645); + +// multiply オプションとともにコール。 出力: 45.7% +echo Number::toPercentage(0.45691, 1, [ + 'multiply' => true +]); +``` + +## 人が読める形式の値との相互作用 + +`method` Cake\\I18n\\Number::**toReadableSize**(string $size) + +このメソッドはデータサイズを人が読める形式に整形します。 +これは、バイト数を KB、MB、GB、および TB へ変換するための近道を提供します。 +サイズは、データのサイズに応じて小数点以下二桁の精度で表示されます。(例 大きいサイズの表現): + +``` php +// NumberHelper としてコール +echo $this->Number->toReadableSize(0); // 0 Byte +echo $this->Number->toReadableSize(1024); // 1 KB +echo $this->Number->toReadableSize(1321205.76); // 1.26 MB +echo $this->Number->toReadableSize(5368709120); // 5 GB + +// Number としてコール +echo Number::toReadableSize(0); // 0 Byte +echo Number::toReadableSize(1024); // 1 KB +echo Number::toReadableSize(1321205.76); // 1.26 MB +echo Number::toReadableSize(5368709120); // 5 GB +``` + +## 数字の整形 + +`method` Cake\\I18n\\Number::**format**(mixed $value, array $options = []) + +このメソッドは、ビューで使うための数値の整形をより制御しやすくします。 +(および、メインのメソッドとして、NumberHelper のその他のほとんどのメソッドから使用されます。) +このメソッドは以下のように使用します。 : + +``` php +// NumberHelper としてコール +$this->Number->format($value, $options); + +// Number としてコール +Number::format($value, $options); +``` + +`$value` パラメーターは、出力のために整形しようとしている数値です。 +`$options` が未指定の場合、1236.334 という数値は 1,236 として出力されるでしょう。 +デフォルトの制度は1の位であることに注意してください。 + +`$options` パラメーターはこのメソッドに存在している手品のタネの在りかです。 + +- もし整数を渡した場合、精度もしくは小数点以下の桁数になります。 +- もし連想配列を渡した場合、以下のキーが使用できます。 + +| オプション | 説明 | +|------------|---------------------------------------------------------------| +| places | 小数点以下の桁数を指定します。例. 2 | +| precision | 小数点以下の最大桁数を指定します。例. 2 | +| pattern | 数値のフォーマットに使用する ICU 数値パターン。 例. \#,###.00 | +| locale | 数値フォーマットに使用するロケール名。 例. "fr_FR". | +| before | レンダリングされた数値の前に表示されるテキスト。 | +| after | レンダリングされた数値の後に表示されるテキスト。 | + +例: + +``` php +// NumberHelper としてコール +echo $this->Number->format('123456.7890', [ + 'places' => 2, + 'before' => '¥ ', + 'after' => ' !' +]); +// 出力 '¥ 123,456.79 !' + +echo $this->Number->format('123456.7890', [ + 'locale' => 'fr_FR' +]); +// 出力 '123 456,79 !' + +// Number としてコール +echo Number::format('123456.7890', [ + 'places' => 2, + 'before' => '¥ ', + 'after' => ' !' +]); +// 出力 '¥ 123,456.79 !' + +echo Number::format('123456.7890', [ + 'locale' => 'fr_FR' +]); +// 出力 '123 456,79 !' +``` + +`method` Cake\\I18n\\Number::**ordinal**(mixed $value, array $options = []) + +このメソッドは序数を出力します。 + +例: + +``` php +echo Number::ordinal(1); +// 出力 '1st' + +echo Number::ordinal(2); +// 出力 '2nd' + +echo Number::ordinal(2, [ + 'locale' => 'fr_FR' +]); +// 出力 '2e' + +echo Number::ordinal(410); +// 出力 '410th' +``` + +## 差分フォーマット + +`method` Cake\\I18n\\Number::**formatDelta**(mixed $value, array $options = []) + +このメソッドは、符号付きの数として値の差分を表示します。 : + +``` php +// NumberHelper としてコール +$this->Number->formatDelta($value, $options); + +// Number としてコール +Number::formatDelta($value, $options); +``` + +`$value` パラメーターは、出力のために整形しようとしている数値です。 +`$options` が未指定の場合、1236.334 という数値は 1,236 として出力されるでしょう。 +デフォルトの制度は1の位であることに注意してください。 + +`$options` パラメーターは `Number::format()` と同じキーを取ります。 + +| オプション | 説明 | +|------------|-----------------------------------------------------| +| places | 小数点以下の桁数を指定します。例. 2 | +| precision | 小数点以下の最大桁数を指定します。例. 2 | +| locale | 数値フォーマットに使用するロケール名。 例. "fr_FR". | +| before | レンダリングされた数値の前に表示されるテキスト。 | +| after | レンダリングされた数値の後に表示されるテキスト。 | + +例: + +``` php +// NumberHelper としてコール +echo $this->Number->formatDelta('123456.7890', [ + 'places' => 2, + 'before' => '[', + 'after' => ']' +]); +// 出力 '[+123,456.79]' + +// Number としてコール +echo Number::formatDelta('123456.7890', [ + 'places' => 2, + 'before' => '[', + 'after' => ']' +]); +// 出力 '[+123,456.79]' +``` + + + +## フォーマッター設定 + +`method` Cake\\I18n\\Number::**config**(string $locale, int $type = NumberFormatter::DECIMAL, array $options = []) + +このメソッドを使用すると、様々なメソッドの呼び出し間で持続的なフォーマッターのデフォルトを設定することができます。 + +例: + +``` php +Number::config('en_IN', \NumberFormatter::CURRENCY, [ + 'pattern' => '#,##,##0' +]); +``` diff --git a/docs/ja/core-libraries/plugin.md b/docs/ja/core-libraries/plugin.md new file mode 100644 index 0000000000..ca321b8716 --- /dev/null +++ b/docs/ja/core-libraries/plugin.md @@ -0,0 +1,52 @@ +# Plugin Class + +`class` Cake\\Core\\**Plugin** + +The Plugin class is responsible for resource location and path management of plugins. + +## Locating Plugins + +`static` Cake\\Core\\Plugin::**path**(string $plugin) + +Plugins can be located with Plugin. Using `Plugin::path('DebugKit');` +for example, will give you the full path to the DebugKit plugin: + +``` php +$path = Plugin::path('DebugKit'); +``` + +## Check if a Plugin is Loaded + +You can check dynamically inside your code if a specific plugin has been loaded: + +``` php +$isLoaded = Plugin::isLoaded('DebugKit'); +``` + +Use `Plugin::loaded()` if you want to get a list of all currently loaded plugins. + +## Finding Paths to Namespaces + +`static` Cake\\Core\\Plugin::**classPath**(string $plugin) + +Used to get the location of the plugin's class files: + +``` php +$path = App::classPath('DebugKit'); +``` + +## Finding Paths to Resources + +`static` Cake\\Core\\Plugin::**templatePath**(string $plugin) + +The method returns the path to the plugins' templates: + +``` php +$path = Plugin::templatePath('DebugKit'); +``` + +The same goes for the config path: + +``` php +$path = Plugin::configPath('DebugKit'); +``` diff --git a/docs/ja/core-libraries/registry-objects.md b/docs/ja/core-libraries/registry-objects.md new file mode 100644 index 0000000000..06dc99ecd0 --- /dev/null +++ b/docs/ja/core-libraries/registry-objects.md @@ -0,0 +1,56 @@ +# レジストリーオブジェクト + +レジストリークラスは、指定されたオブジェクト型のロードされたインスタンスの、 +作成および取得する簡単な方法を提供します。 +コンポーネント、ヘルパー、タスク、およびビヘイビアーのレジストリークラスがあります。 + +以下の例ではコンポーネントを使用しますが、コンポーネントに加えて、ヘルパー、ビヘイビアー、 +およびタスクに同じ動作を期待することができます。 + +## オブジェクトのロード + +オブジェクトはその場で add\<レジストリーオブジェクト\>() でロードすることができます。 +例: + +``` php +$this->loadComponent('Acl.Acl'); +$this->addHelper('Flash') +``` + +これは、 `Acl` プロパティーと `Flash` ヘルパーをロードしています。 +設定も同様に、その場で行なうことができます。例: + +``` php +$this->loadComponent('Cookie', ['name' => 'sweet']); +``` + +提供される任意のキーと値は、コンポーネントのコンストラクターに渡されます。このルールの唯一の例外は、 +`className` です。クラス名は、レジストリーのエイリアスオブジェクトに使用される特殊なキーです。 +これは、クラス名を反映していないコンポーネント名を持つことができ、コアコンポーネントを拡張する際に +役立ちます。 : + +``` php +$this->Auth = $this->loadComponent('Auth', ['className' => 'MyCustomAuth']); +$this->Auth->user(); // 実態は MyCustomAuth::user(); +``` + +## コールバックトリガー + +コールバックは、レジストリーオブジェクトによって提供されていません。 +アプリケーションのすべてのイベント/コールバックをディスパッチするために +[events system](../core-libraries/events) を使用する必要があります。 + +## コールバックの無効化 + +以前のバージョンでは、コレクションオブジェクトは、コールバックを受けるオブジェクトを無効にする +`disable()` メソッドを提供していました。 +現在、これを実現するためには、イベントシステムの機能を使用する必要があります。 +たとえば、次のようにコンポーネントのコールバックを無効にすることができます。 : + +``` php +// Auth コールバックを無効化 +$this->getEventManager()->off($this->Auth); + +// Auth コールバックを再度有効化 +$this->getEventManager()->on($this->Auth); +``` diff --git a/docs/ja/core-libraries/security.md b/docs/ja/core-libraries/security.md new file mode 100644 index 0000000000..8b81052db8 --- /dev/null +++ b/docs/ja/core-libraries/security.md @@ -0,0 +1,118 @@ +# セキュリティユーティリティ + +`class` Cake\\Utility\\**Security** + +[security library](https://api.cakephp.org/4.x/class-Cake.Utility.Security.html) は、 +データのハッシュ化や暗号化などのメソッドなどの基本的なセキュリティ分野を取り扱います。 + +## データの暗号化と復号 + +`static` Cake\\Utility\\Security::**encrypt**($text, $key, $hmacSalt = null) + +`static` Cake\\Utility\\Security::**decrypt**($cipher, $key, $hmacSalt = null) + +`$text` の暗号化には AES-256 を利用します。 `$key` は例えば「よいパスワード」のように、 +沢山の分散された値であるべきです。返却される結果は HMAC チェックサム(checksum)つきの +暗号化された値となります。 + +このメソッドは、 [openssl](https://php.net/openssl) か +[mcrypt](https://php.net/mcrypt) のいずれかを利用し、 +あなたのシステムで有効であることが基準になります。 +いずれかの実装で暗号化されたデータは、もう一方の実装に移植できます。 + +> [!WARNING] +> [mcrypt](https://php.net/mcrypt) 拡張は、PHP7.1 で非推奨になりました。 + +このメソッドは **決して** パスワードの保存に使ってはいけません。代わりに一方通行の +ハッシュ化メソッド `Cake\Utility\Security::hash()` を利用すべきです。 +以下に一例を挙げます。 : + +``` php +// キーがどこかに保存されたと仮定すれば、あとで復号のために再利用されることが可能 +$key = 'wt1U5MACWJFTXGenFoZoiLwQGrLgdbHA'; +$result = Security::encrypt($value, $key); +``` + +もしあなたが HMAC ソルトを提供しなければ、 `Security.salt` の値が利用されます。 +暗号化された値は `Cake\Utility\Security::decrypt()` を利用して復号できます。 + +すでに暗号化された値を復号します。 `$key` と `$hmacSalt` のパラメーターは、 +暗号化時に利用された各々の値と一致する必要があり、さもなければ復号は失敗します。 +以下に一例を挙げます。 : + +``` php +// キーがどこかに保存されたと仮定すれば、あとで復号のために再利用されることが可能 +$key = 'wt1U5MACWJFTXGenFoZoiLwQGrLgdbHA'; + +$cipher = $user->secrets; +$result = Security::decrypt($cipher, $key); +``` + +キーや HMAC ソルトの変更により値が復号できない場合、 `false` が返却されます。 + +### 特定の暗号化を実装を選択 + +もし CakePHP 2.x からアプリケーションをアップグレードするならば、 2.x で暗号化されたデータは +openssl では互換性がありません。これは、暗号化されたデータは AES に完全には準拠していないから +です。もしデータを再度暗号化するときにトラブルを避けたければ、 `engine()` メソッドを使用して +`mcrypt` の利用を CakePHP に強制できます。 : + +``` php +// config/bootstrap.php の中で +use Cake\Utility\Crypto\Mcrypt; + +Security::engine(new Mcrypt()); +``` + +上記は古いバージョンの CakePHP のデータのシームレスな読み込みを許可し、新しいデータを +暗号化すると OpenSSL 互換となります。 + +## データのハッシュ化 + +`static` Cake\\Utility\\Security::**hash**( $string, $type = NULL, $salt = false ) + +このメソッドで文字列からハッシュを作成します。次の有効なメソッドを頼ってください。 +もし `$salt` が `true` にセットされていた場合、アプリケーションのソルト値が +利用されます。 : + +``` php +// アプリケーションのソルト値を利用 +$sha1 = Security::hash('CakePHP Framework', 'sha1', true); + +// カスタムソルト値を利用 +$sha1 = Security::hash('CakePHP Framework', 'sha1', 'my-salt'); + +// デフォルトのハッシュアルゴリズムを利用 +$hash = Security::hash('CakePHP Framework'); +``` + +`hash()` メソッドは以下のハッシュ方法をサポートします。 + +- md5 +- sha1 +- sha256 + +そして、PHP の `hash()` 関数がサポートしている他のハッシュアルゴリズムもサポートします。 + +> [!WARNING] +> 新しいアプリケーションのパスワード用に `hash()` を利用すべきではありません。 +> 代わりにデフォルトで bcrypt を利用する `DefaultPasswordHasher` クラスを利用すべきです。 + +## セキュアなランダムデータの取得 + +`static` Cake\\Utility\\Security::**randomBytes**($length) + +セキュアなランダムソースから `$length` バイト数を取得します。この関数は、 +以下のソースの1つからデータを生成します。 + +- PHP の `random_bytes` 関数。 +- SSL 拡張の `openssl_random_pseudo_bytes` 。 + +どちらのソースも利用できない場合、警告が発せられ、 +後方互換のために安全ではない値が使用されます。 + +`static` Cake\\Utility\\Security::**randomString**($length) + +セキュアなランダムソースから長さ `$length` のランダムな文字列を取得します。 +このメソッドは、 `randomBytes()` と同じランダムソースから生成し、 +データを16進文字列としてエンコードします。 diff --git a/docs/ja/core-libraries/text.md b/docs/ja/core-libraries/text.md new file mode 100644 index 0000000000..7cd5fccdde --- /dev/null +++ b/docs/ja/core-libraries/text.md @@ -0,0 +1,393 @@ +# Text + +`class` Cake\\Utility\\**Text** + +Text クラスは文字列を作ったり操作したりする便利なメソッドを持っており、通常は static にアクセスします。例: `Text::uuid()` + +`View` の外側で `Cake\View\Helper\TextHelper` の機能が必要なときは `Text` クラスを使ってください。 : + +``` php +namespace App\Controller; + +use Cake\Utility\Text; + +class UsersController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('Auth') + }; + + public function afterLogin() + { + $message = $this->Users->find('new_message')->first(); + if (!empty($message)) { + // ユーザーに新しいメッセージを通知 + $this->Flash->success(__( + 'You have a new message: {0}', + Text::truncate($message['Message']['body'], 255, ['html' => true]) + )); + } + } +} +``` + +## ASCII 文字への変換 + +`static` Cake\\Utility\\Text::**transliterate**($string, $transliteratorId = null) + +transliterate はデフォルトで、与えられた文字列の文字すべてを同じ意味の ASCII 文字に置き換えます。 +このメソッドは UTF-8 エンコーディングであることが前提になっています。 +文字変換はトランスリテレーション識別子で制御することができます。 +この識別子は引数 `$transliteratorId` を使って渡すか、 +`Text::setTransliteratorId()` を使ってデフォルトの識別子文字列を変更することができます。 +ICU のトランスリテレーション識別子は基本的に `<元のスクリプト>:<変換後のスクリプト>` の形で、 +`;` で区切って複数の変換の組み合わせを指定することができます。 +トランスリテレーション識別子についての詳細は +[ここ](https://unicode-org.github.io/icu/userguide/transforms/general/#transliterator-identifiers) +を参照してください。 : + +``` php +// apple puree +Text::transliterate('apple purée'); + +// Ubermensch (ラテン文字だけを変換する) +Text::transliterate('Übérmensch', 'Latin-ASCII;'); +``` + +## URL に安全な文字列の作成 + +`static` Cake\\Utility\\Text::**slug**($string, $options = []) + +slug はすべての文字を ASCII バージョンにトランスリテレートし(別言語の文字に置き換え)、 +マッチしない文字や空白はダッシュに変換します。 +slug メソッドは UTF-8 エンコーディングであること前提にしています。 + +slug をコントロールするオプション配列を渡すことができます。 +`$options` は文字列で指定することもでき、その場合は置き換え文字列として使われます。 +利用可能なオプションは次の通りです: + +- `replacement` 置き換え文字列。デフォルトは '-' 。 + +- `transliteratorId` 有効なトランスリテレータIDの文字列。 + デフォルトの `null` なら、`Text::$_defaultTransliteratorId` が使われます。 + false なら、トランスリテレーションは実行されず、非単語のみが削除されます。 + +- `preserve` 保持したい特定の非単語文字。デフォルトは `null` 。 + たとえば、このオプションに '.' をセットすることでクリーンなファイル名を生成することができます。 : + + ``` php + // apple-puree + Text::slug('apple purée'); + + // apple_puree + Text::slug('apple purée', '_'); + + // foo-bar.tar.gz + Text::slug('foo bar.tar.gz', ['preserve' => '.']); + ``` + +## UUID の生成 + +`static` Cake\\Utility\\Text::**uuid**() + +UUID メソッドは `4122` 準拠のユニークな識別子を生成するのに使います。 +UUID は `485fc381-e790-47a3-9794-1337c0a8fe68` というフォーマットの 128 ビットの文字列です。 : + +``` php +Text::uuid(); // 485fc381-e790-47a3-9794-1337c0a8fe68 +``` + +## 単純な文字列のパース + +`static` Cake\\Utility\\Text::**tokenize**($data, $separator = ',', $leftBound = '(', $rightBound = ')') + +`$separator` を使って文字列をトークン化します。その際、 `$leftBound` と `$rightBound` の間にある `$separator` は無視されます。 + +このメソッドはタグリストのような標準フォーマットを持つデータを分割するのに役立ちます。 : + +``` php +$data = "cakephp 'great framework' php"; +$result = Text::tokenize($data, ' ', "'", "'"); +// 結果 +['cakephp', "'great framework'", 'php']; +``` + +`method` Cake\\Utility\\Text::**parseFileSize**(string $size, $default) + +このメソッドは人が読みやすいバイトのサイズのフォーマットから、バイトの整数値へと変換します。 : + +``` php +$int = Text::parseFileSize('2GB'); +``` + +## 文字列のフォーマット + +`static` Cake\\Utility\\Text::**insert**($string, $data, $options = []) + +insert メソッドは文字列テンプレートを作り、key/value で置き換えるのに使います。 : + +``` php +Text::insert( + 'My name is :name and I am :age years old.', + ['name' => 'Bob', 'age' => '65'] +); +// これを返す: "My name is Bob and I am 65 years old." +``` + +`static` Cake\\Utility\\Text::**cleanInsert**($string, $options = []) + +`$options` 内の 'clean' キーに従って、 `Text::insert` でフォーマットされた文字列を掃除します。 +デフォルトで method に使われるのは text ですが html も使えます。 +この機能の目的は、`Text::insert` で置き換えられなかった、プレースホルダ周辺のすべての空白と不要なマークアップを置き換えることにあります。 + +options 配列内で下記のオプションを使うことができます。 : + +``` php +$options = [ + 'clean' => [ + 'method' => 'text', // もしくは html + ], + 'before' => '', + 'after' => '' +]; +``` + +## テキストの改行 + +`static` Cake\\Utility\\Text::**wrap**($text, $options = []) + +テキストのブロックを幅やインデントを指定して改行させます。 +単語が別の行に分離されないように賢く改行してくれます。 : + +``` php +$text = 'This is the song that never ends.'; +$result = Text::wrap($text, 22); + +// 戻り値 +This is the song that +never ends. +``` + +オプション配列でどのように改行されるのかを制御できます。 +利用できるオプションは次の通りです。 + +- `width` 改行の幅。デフォルトは 72。 +- `wordWrap` 単語単位で改行するか。デフォルトは `true` 。 +- `indent` インデントに使う文字。デフォルトは '' 。 +- `indentAt` 何行目からテキストのインデントを開始するか。デフォルトは 0 。 + +`static` Cake\\Utility\\Text::**wrapBlock**($text, $options = []) + +生成されたブロックの合計幅が内部的なインデントと同じ幅を確実に超えないようにする必要があるなら、 +`wrap()` の代わりに `wrapBlock()` を使う必要があります。 +これは例えばコンソール向けのテキストを生成するのにとても便利です。 +`wrap()` と同じオプションが使えます。 : + +``` php +$text = 'This is the song that never ends. This is the song that never ends.'; +$result = Text::wrapBlock($text, [ + 'width' => 22, + 'indent' => ' → ', + 'indentAt' => 1 +]); + +// 戻り値 +This is the song that + → never ends. This + → is the song that + → never ends. +``` + + + +## 文字列の一部をハイライトする + +`method` Cake\\Utility\\Text::**highlight**(string $haystack, string $needle, array $options = [] ) + +`$options['format']` で指定された文字列か、デフォルトの文字列を使って `$haystack` 中の `$needle` をハイライトします。 + +オプション: + +- `format` string - ハイライトするフレーズに適用する HTML パーツ +- `html` bool - `true` ならすべての HTML タグを無視して、正確にテキストのみをハイライトするよう保証します。 + +例: + +``` php +// TextHelper として呼ぶ +echo $this->Text->highlight( + $lastSentence, + '使って', + ['format' => '\1'] +); + +// Text として呼ぶ +use Cake\Utility\Text; + +echo Text::highlight( + $lastSentence, + '使って', + ['format' => '\1'] +); +``` + +出力: + +``` text +$options['format'] で指定された文字列か、デフォルトの文字列を使って +$haystack 中の $needle をハイライトします。 +``` + +## リンク除去 + +`method` Cake\\Utility\\Text::**stripLinks**($text) + +渡された `$text` から HTML リンクを取り除きます。 + +## テキストの切り詰め + +`method` Cake\\Utility\\Text::**truncate**(string $text, int $length = 100, array $options) + +`$text` が `$length` より長い場合、このメソッドはそれを `$length` の長さに切り詰め、 +`'ellipsis'` が定義されているなら末尾にその文字列を追加します。 +もし `'exact'` に `false` が渡されたなら、 `$length` を超えた最初の空白で切り詰められます。 +もし `'html'` に `true` が渡されたなら、HTML タグは尊重され、削除されなくなります。 + +`$options` はすべての追加パラメーターを渡すのに使われ、下記のようなキーがデフォルトになっており、すべてが省略可能です。 : + +``` php +[ + 'ellipsis' => '...', + 'exact' => true, + 'html' => false +] +``` + +例: + +``` php +// TextHelper として呼ぶ +echo $this->Text->truncate( + 'The killer crept forward and tripped on the rug.', + 22, + [ + 'ellipsis' => '...', + 'exact' => false + ] +); + +// Text として呼ぶ +use Cake\Utility\Text; + +echo Text::truncate( + 'The killer crept forward and tripped on the rug.', + 22, + [ + 'ellipsis' => '...', + 'exact' => false + ] +); +``` + +出力: + + The killer crept... + +## 文字列の末尾を切り詰める + +`method` Cake\\Utility\\Text::**tail**(string $text, int $length = 100, array $options) + +`$text` が `$length` より長い場合、このメソッドは先頭から差となる長さの文字列を取り除き、 +`'ellipsis'` が定義されているなら先頭にその文字列を追加します。 +もし `'exact'` に `false` が渡されたなら、切り詰めが本来発生したであろう場所の前にある最初の空白で切り詰められます。 + +`$options` はすべての追加パラメーターを渡すのに使われ、下記のようなキーがデフォルトになっており、すべてが省略可能です。 : + +``` php +[ + 'ellipsis' => '...', + 'exact' => true +] +``` + +例: + +``` php +$sampleText = 'I packed my bag and in it I put a PSP, a PS3, a TV, ' . + 'a C# program that can divide by zero, death metal t-shirts' + +// TextHelper として呼ぶ +echo $this->Text->tail( + $sampleText, + 70, + [ + 'ellipsis' => '...', + 'exact' => false + ] +); + +// Text として呼ぶ +use Cake\Utility\Text; + +echo Text::tail( + $sampleText, + 70, + [ + 'ellipsis' => '...', + 'exact' => false + ] +); +``` + +出力: + + ...a TV, a C# program that can divide by zero, death metal t-shirts + +## 抜粋の抽出 + +`method` Cake\\Utility\\Text::**excerpt**(string $haystack, string $needle, integer $radius=100, string $ellipsis="...") + +`$haystack` から、 `$needle` の前後 `$radius` で指定された文字数分を含む文字列を抜粋として抽出し、 +その先頭と末尾に `$ellipsis` の文字列を追加します。 +このメソッドは検索結果には特に便利でしょう。クエリー文字列やキーワードを結果の文章中とともに表示することができます。 : + +``` php +// TextHelper として呼ぶ +echo $this->Text->excerpt($lastParagraph, 'method', 50, '...'); + +// Text として呼ぶ +use Cake\Utility\Text; + +echo Text::excerpt($lastParagraph, 'method', 50, '...'); +``` + +出力: + + ... by $radius, and prefix/suffix with $ellipsis. This method is especially + handy for search results. The query... + +## 配列を文章的なものに変換する + +`method` Cake\\Utility\\Text::**toList**(array $list, $and='and', $separator=', ') + +最後の2要素が 'and' で繋がっている、カンマ区切りのリストを生成します。 : + +``` php +$colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; + +// TextHelper として呼ぶ +echo $this->Text->toList($colors); + +// Text として呼ぶ +use Cake\Utility\Text; + +echo Text::toList($colors); +``` + +出力: + + red, orange, yellow, green, blue, indigo and violet + + diff --git a/docs/ja/core-libraries/time.md b/docs/ja/core-libraries/time.md new file mode 100644 index 0000000000..aa41e775c7 --- /dev/null +++ b/docs/ja/core-libraries/time.md @@ -0,0 +1,489 @@ +# 日付と時刻 + +`class` Cake\\I18n\\**FrozenTime** + +`TimeHelper` の機能を `View` の外で使いたい場合は、 +`FrozenTime` クラスを利用してください。 : + +``` php +use Cake\I18n\FrozenTime; + +class UsersController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('Auth'); + } + + public function afterLogin() + { + $time = new FrozenTime($this->Auth->user('date_of_birth')); + if ($time->isToday()) { + / 誕生日祝いのメッセージでユーザーに挨拶 + $this->Flash->success(__('Happy birthday to you...')); + } + } +} +``` + +内部的には、この `FrozenTime` ユーティリティを動かすために、CakePHP は +[Chronos](https://github.com/cakephp/chronos) を利用しています。 +`Chronos` と `DateTime` でできることはなんでも、 `FrozenTime` と `FrozenDate` ですることができます。 + +Chronos についてより詳しく知りたい場合は [API ドキュメント](https://api.cakephp.org/chronos/1.0/) をご覧ください。 + + + +## Time インスタンスを作成する + +`FrozenTime` インスタンスを作成するにはいくつかの方法があります。 : + +``` php +use Cake\I18n\FrozenTime; + +// 日時文字列から作成 +$time = FrozenTime::createFromFormat( + 'Y-m-d H:i:s', + '2021-01-31 22:11:30', + 'America/New_York' +); + +// タイムスタンプから作成 +$time = FrozenTime::createFromTimestamp(1612149090, 'Asia/Tokyo'); + +// 現在時刻を取得 +$time = FrozenTime::now(); + +// または 'new' を使用して +$time = new FrozenTime('2021-01-31 22:11:30', 'Asia/Tokyo'); + +$time = new FrozenTime('2 hours ago'); +``` + +`FrozenTime` クラスのコンストラクターは、内部の PHP クラスである `DateTimeImmutable` が受け取れる、 +あらゆるパラメーターを受け取ることができます。数値や数字文字列を渡したとき、 +UNIX タイムスタンプとして解釈されます。 + +テストケースでは、 `setTestNow()` を使うことで `now()` をモックアップできます。 : + +``` php +// 時間の固定 +$time = new FrozenTime('2021-01-31 22:11:30'); +FrozenTime::setTestNow($time); + +// 結果は '2021-01-31 22:11:30' +$now = FrozenTime::now(); +echo $now->i18nFormat('yyyy-MM-dd HH:mm:ss'); + +// 結果は '2021-01-31 22:11:30' +$now = FrozenTime::parse('now'); +echo $now->i18nFormat('yyyy-MM-dd HH:mm:ss'); +``` + +## 操作 + +`FrozenTime` インスタンスは、それ自体を変更するのではなく、常にセッターから新しいインスタンスを返すことを忘れないでください。 : + +``` php +$time = FrozenTime::now(); + +// Create and reassign a new instance +$newTime = $time->year(2013) + ->month(10) + ->day(31); +// Outputs '2013-10-31 22:11:30' +echo $newTime->i18nFormat('yyyy-MM-dd HH:mm:ss'); +``` + +PHP のビルトインの `DateTime` クラスで提供されているメソッドも使用できます。 : + +``` php +$time = $time->setDate(2013, 10, 31); +``` + +日付はコンポーネントの引き算や足し算で編集できます。 : + +``` php +$time->year(2013) + ->month(10) + ->day(31); +// Outputs '2021-01-31 22:11:30' +echo $time->i18nFormat('yyyy-MM-dd HH:mm:ss'); +``` + +コンポーネントの減算と加算により、日付が変更された別のインスタンスを作成できます。 : + +``` php +$time = FrozenTime::create(2021, 1, 31, 22, 11, 30); +$newTime = $time->subDays(5) + ->addHours(-2) + ->addMonth(1); +// Outputs '2/26/21, 8:11 PM' +echo $newTime; + +// Using strtotime strings. +$newTime = $time->modify('+1 month -5 days -2 hours'); +// Outputs '2/26/21, 8:11 PM' +echo $newTime; +``` + +プロパティにアクセスすることで、日付の内部コンポーネントを取得できます。 : + +``` php +$time = FrozenTime::create(2021, 1, 31, 22, 11, 30); +echo $time->year; // 2021 +echo $time->month; // 1 +echo $time->day; // 31 +echo $time->timezoneName; // America/New_York +``` + +## フォーマットする + +`static` Cake\\I18n\\FrozenTime::**setJsonEncodeFormat**($format) + +このメソッドは、オブジェクトを json 形式に変換するときに使われる +デフォルトのフォーマットをセットします。 : + +``` php +Time::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss'); // 可変の DataTime 用 +FrozenTime::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss'); // 不変の DateTime 用 +Date::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss'); // 可変の Date 用 +FrozenDate::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss'); // 不変の Date 用 + +$time = FrozenTime::parse('2021-01-31 22:11:30'); +echo json_encode($time); // Outputs '2021-01-31 22:11:30' + +// Added in 4.1.0 +FrozenDate::setJsonEncodeFormat(static function($time) { + return $time->format(DATE_ATOM); +}); +``` + +> [!NOTE] +> このメソッドは静的に呼び出されなくてはなりません。 + +`method` Cake\\I18n\\FrozenTime::**i18nFormat**($format = null, $timezone = null, $locale = null) + +`Time` インスタンスで行うごく一般的なことは、フォーマットされたデータを出力することです。 +CakePHP は snap を作成します。 : + +``` php +$now = Time::parse('2014-10-31'); + +// 地域化された日時のスタンプを出力します。 +echo $now; + +// en-US ロケールでは '10/31/14, 12:00 AM' を出力します。 +$now->i18nFormat(); + +// 日付と時刻のフルフォーマットを利用します。 +$now->i18nFormat(\IntlDateFormatter::FULL); + +// 日付のフルフォーマットと時刻のショートフォーマットを利用します。 +$now->i18nFormat([\IntlDateFormatter::FULL, \IntlDateFormatter::SHORT]); + +// '2014-10-31 00:00:00' と出力します。 +$now->i18nFormat('yyyy-MM-dd HH:mm:ss'); +``` + +文字列が表示される希望のフォーマットを特定することも可能です。 +この関数に第1引数として [IntlDateFormatter 定数](https://www.php.net/manual/ja/class.intldateformatter.php) を渡したり、 +あるいは以下のリソースで指定されている ICU の日付フルフォーマット文字列を渡すことができます: +. + +グレゴリオ暦以外の暦で日付をフォーマットすることも可能です。 : + +``` php +// 出力結果 'Friday, Aban 9, 1393 AP at 12:00:00 AM GMT' +$result = $now->i18nFormat(\IntlDateFormatter::FULL, null, 'en-IR@calendar=persian'); +``` + +以下の暦のタイプがサポートされています。 + +- japanese +- buddhist +- chinese +- persian +- indian +- islamic +- hebrew +- coptic +- ethiopic + +> [!NOTE] +> IntlDateFormatter::FULL のような文字列定数のために Intl は ICU ライブラリーを使用します。 +> そのライブラリーは、 CLDR () からデータを取り入れています。 +> ライブラリーのバージョンは、 PHP のインストールにとても依存し、バージョンにより異なる結果を返します。 + +`method` Cake\\I18n\\FrozenTime::**nice**() + +あらかじめ定義されている 'nice' フォーマットで出力します。 : + +``` php +$time = Time::parse('2014-10-31'); + +// en-USでは 'Oct 31, 2014 12:00 AM' と出力されます。 +echo $time->nice(); +``` + +`Time` オブジェクトそのものを変更することなく、出力される日付のタイムゾーンを変更することができます。 +一つのタイムゾーンでデータを保存しているけれども、ユーザーのそれぞれのタイムゾーンで表示したい場合に +便利です。 : + +``` php +$time->i18nFormat(\IntlDateFormatter::FULL, 'Europe/Paris'); +``` + +第1引数を `null` のままにしておくと、デフォルトのフォーマット文字列を使用します。 : + +``` php +$time->i18nFormat(null, 'Europe/Paris'); +``` + +最後に、日付を表示するのに異なるロケールを利用することができます。 : + +``` php +echo $time->i18nFormat(\IntlDateFormatter::FULL, 'Europe/Paris', 'fr-FR'); + +echo $time->nice('Europe/Paris', 'fr-FR'); +``` + +### デフォルトのロケールとフォーマット文字列を設定する + +`nice` や `i18nFormat` を利用している際に表示される日付のデフォルトのロケールは、 +[intl.default_locale](https://www.php.net/manual/en/intl.configuration.php#ini.intl.default-locale) の指令です。 +しかしながら、このデフォルト値は実行時にも変更できます。 : + +``` php +Time::setDefaultLocale('es-ES'); // 可変の DateTime 用 +FrozenTime::setDefaultLocale('es-ES'); // 不変の DateTime 用 +Date::setDefaultLocale('es-ES'); // 可変の Date 用 +FrozenDate::setDefaultLocale('es-ES'); // 不変の Date 用 +``` + +フォーマットメソッドの中で直接異なるローケルが指示されていない限り、今後、 +日時はスペインのフォーマットで表示されます。 + +同様に、 `i18nFormat` を利用することでデフォルトのフォーマット文字列を変更できます。 : + +``` php +Time::setToStringFormat(\IntlDateFormatter::SHORT); // 可変の DateTime 用 +FrozenTime::setToStringFormat(\IntlDateFormatter::SHORT); // 不変の DateTime 用 +Date::setToStringFormat(\IntlDateFormatter::SHORT); // 可変の Date 用 +FrozenDate::setToStringFormat(\IntlDateFormatter::SHORT); // 不変の Date 用 + +// Date, FrozenDate, FrozenTime にも同じメソッドがあります。 +Time::setToStringFormat([ + \IntlDateFormatter::FULL, + \IntlDateFormatter::SHORT +]); + +// Date, FrozenDate, FrozenTime にも同じメソッドがあります。 +Time::setToStringFormat('yyyy-MM-dd HH:mm:ss'); +``` + +日付のフォーマット文字列を直接渡すよりも、定数を常に利用することが推奨されています。 + +### 相対時間のフォーマットについて + +`method` Cake\\I18n\\FrozenTime::**timeAgoInWords**(array $options = []) + +現在との相対的な時間を出力することが有用なときがしばしばあります。 : + +``` php +$time = new FrozenTime('Jan 31, 2021'); +// On June 12, 2021, this would output '4 months, 1 week, 6 days ago' +echo $time->timeAgoInWords( + ['format' => 'MMM d, YYY', 'end' => '+1 year'] +); +``` + +`format` オプションを利用してフォーマットされた相対時間の位置は +`end` オプションによって定義されます。 +`accuracy` オプションは、それぞれの間隔幅に対してどのレベルまで詳細を出すかをコントロールします。 : + +``` php +// Outputs '4 months ago' +echo $time->timeAgoInWords([ + 'accuracy' => ['month' => 'month'], + 'end' => '1 year' +]); +``` + +`accuracy` を文字列で設定すると、出力をどのレベルまで詳細を出すかの最大値を指定できます。 : + +``` php +$time = new Time('+23 hours'); +// 出力結果 'in about a day' +$result = $time->timeAgoInWords([ + 'accuracy' => 'day' +]); +``` + +## 変換 + +`method` Cake\\I18n\\FrozenTime::**toQuarter**() + +一旦作成しても、 `Time` インスタンスを、タイムスタンプや四半期の値に変換することができます。 : + +``` php +$time = new FrozenTime('2021-01-31'); +echo $time->toQuarter(); // Outputs '1' +echo $time->toUnixString(); // Outputs '1612069200' +``` + +## 現在と比較する + +`method` Cake\\I18n\\FrozenTime::**isYesterday**() + +`method` Cake\\I18n\\FrozenTime::**isThisWeek**() + +`method` Cake\\I18n\\FrozenTime::**isThisMonth**() + +`method` Cake\\I18n\\FrozenTime::**isThisYear**() + +様々な方法で `Time` インスタンスと現在とを比較することができます。 : + +``` php +$time = new FrozenTime('+3 days'); + +debug($time->isYesterday()); +debug($time->isThisWeek()); +debug($time->isThisMonth()); +debug($time->isThisYear()); +``` + +上述のメソッドのいずれも、 `Time` インスタンスが現在と一致するかどうかによって、 +`true`/`false` を返します。 + +## 間隔を比較する + +`method` Cake\\I18n\\FrozenTime::**isWithinNext**($interval) + +`wasWithinLast()` および `isWithinNext()` を使用して `FrozenTime` インスタンスが特定の範囲内にあるかどうかを確認できます。 : + +``` php +$time = new FrozenTime('+3 days'); + +// Within 2 days. Outputs 'false' +debug($time->isWithinNext('2 days')); + +// Within 2 next weeks. Outputs 'true' +debug($time->isWithinNext('2 weeks')); +``` + +`method` Cake\\I18n\\FrozenTime::**wasWithinLast**($interval) + +過去の範囲内の `FrozenTime` インスタンスと比較することもできます。 : + +``` php +$time = new FrozenTime('-72 hours'); + +// Within past 2 days. Outputs 'false' +debug($time->wasWithinLast('2 days')); + +// Within past 3 days. Outputs 'true' +debug($time->wasWithinLast('3 days')); + +// Within past 2 weeks. Outputs 'true' +debug($time->wasWithinLast('2 weeks')); +``` + + + +## FrozenDate + +CakePHP の不変の `FrozenDate` クラスは `Cake\I18n\FrozenTime` と同じAPIとメソッドを実装しています。 +`FrozenTime` と `FrozenDate` の主な違いは、 `FrozenDate` が時間コンポーネントを追跡しないことです。 +以下のコードをご覧ください。 : + +``` php +use Cake\I18n\FrozenDate; +$date = new FrozenDate('2021-01-31'); + +$newDate = $date->modify('+2 hours'); +// Outputs '2021-01-31 00:00:00' +echo $newDate->format('Y-m-d H:i:s'); + +$newDate = $date->addHours(36); +// Outputs '2021-01-31 00:00:00' +echo $newDate->format('Y-m-d H:i:s'); + +$newDate = $date->addDays(10); +// Outputs '2021-02-10 00:00:00' +echo $newDate->format('Y-m-d H:i:s'); +``` + +`FrozenDate` インスタンスのタイムゾーンを変更する試みも無視されます。 : + +``` php +use Cake\I18n\FrozenDate; +$date = new FrozenDate('2021-01-31', new \DateTimeZone('America/New_York')); +$newDate = $date->setTimezone(new \DateTimeZone('Europe/Berlin')); + +// Outputs 'America/New_York' +echo $newDate->format('e'); +``` + + + +## Mutable Dates and Times + +`class` Cake\\I18n\\**Time** + +`class` Cake\\I18n\\**Date** + +CakePHP は、変更可能な仲間と同じインターフェイスを実装する、不変な日付と時刻のクラスを +提供しています。不変なオブジェクトは、偶発的にデータが変わってしまうのを防ぎたいときや、 +順番に依存する問題を避けたいときに、便利です。以下のコードをご覧ください。 : + +``` php +use Cake\I18n\Time; +$time = new Time('2015-06-15 08:23:45'); +$time->modify('+2 hours'); + +// このメソッドは $time インスタンスも変更します。 +$this->someOtherFunction($time); + +// ここでの出力結果は不明です。 +echo $time->format('Y-m-d H:i:s'); +``` + +メソッドの呼び出しの順番が変わった場合、あるいは `someOtherFunction` によって変更された場合、 +出力は予期できません。このオブジェクトの変更可能な性質によって、一時的結合が作成されます。 +不変のオブジェクトを用いれば、この問題を避けることができます。 : + +``` php +use Cake\I18n\FrozenTime; +$time = new FrozenTime('2015-06-15 08:23:45'); +$time = $time->modify('+2 hours'); + +// このメソッドの変更は $time を変更しません。 +$this->someOtherFunction($time); + +// ここでの出力結果は明らかです。 +echo $time->format('Y-m-d H:i:s'); +``` + +不変の日付と時刻は、エンティティー内での偶然的な更新を防ぎ、変更を明示するよう強制したいときに便利です。 +不変なオブジェクトを利用することで、ORM が変更を追跡したり、日付や日付と時刻のカラムを正しく保持する +ことが、より簡単になります。 : + +``` php +// 記事が保存されるとき、この変更は消去されます。 +$article->updated->modify('+1 hour'); + +// 時刻のオブジェクトを置き換えると、プロパティーが保存されます。 +$article->updated = $article->updated->modify('+1 hour'); +``` + +## 地域化されたリクエストデータの受け入れ + +日付を操作するテキストの入力を作成するとき、きっと地域化された日時の文字列を受け入れて +パースしたいはずです。 [Parsing Localized Dates](../core-libraries/internationalization-and-localization#parsing-localized-dates) をご覧ください。 + +## サポートされるタイムゾーン + +CakePHP はすべての有効な PHP タイムゾーンをサポートしています。サポートされるタイムゾーンの一覧は、 +[このページをご覧ください](https://php.net/manual/ja/timezones.php) 。 diff --git a/docs/ja/core-libraries/validation.md b/docs/ja/core-libraries/validation.md new file mode 100644 index 0000000000..8c214403c5 --- /dev/null +++ b/docs/ja/core-libraries/validation.md @@ -0,0 +1,642 @@ +# バリデーション + +CakePHP のバリデーションは、任意の配列データに対するバリデーションを簡単に行うための +バリデーター構築のパッケージを提供します。 [API 中の利用可能なバリデーションルールの一覧](https://api.cakephp.org/4.x/class-Cake.Validation.Validation.html) +をご覧ください。 + +## バリデーターを作成する + +`class` Cake\\Validation\\**Validator** + +バリデーターオブジェクトは1セットのフィールドに適用されるルールを定義します。 +バリデーターオブジェクトはフィールドとバリデーションセットとの間のマッピングを含みます。 +その引き換えに、バリデーションセットは、関係するフィールドに適用されるルールの集合体を +提供します。バリデーターを作成するのは簡単です。 : + +``` php +use Cake\Validation\Validator; + +$validator = new Validator(); +``` + +一度作成した後、バリデーションを適用したいフィールドに対して、実際にルールを設定していきます。 : + +``` php +$validator + ->requirePresence('title') + ->notEmptyString('title', 'このフィールドに入力してください') + ->add('title', [ + 'length' => [ + 'rule' => ['minLength', 10], + 'message' => 'タイトルは 10 文字以上必要です', + ] + ]) + ->allowEmptyDateTime('published') + ->add('published', 'boolean', [ + 'rule' => 'boolean' + ]) + ->requirePresence('body') + ->add('body', 'length', [ + 'rule' => ['minLength', 50], + 'message' => '記事は中身のある本文を持っていなければなりません。', + ]); +``` + +上記例に見られるように、バリデーションは、実際にバリデーションを実行したいフィールドに対して +ルールを適用するような形で、流暢なインターフェイスとともに構築されて行きます。 + +上記例には、いくつかのメソッドが呼ばれていたので、様々な特徴について見て行きましょう。 +`add()` メソッドを用いることにより、バリデーターに対して新しいルールを加えることができます。 +上記のように個別にルールを加えることもできますし、グループ単位でルールを加えることもできます。 + +### フィールドが実在することを求める + +`requirePresence()` メソッドは、バリデーションの対象となる配列について、 +フィールドが実在することを求めます。もし、フィールドが存在していなければ、 +バリデーションは失敗します。 `requirePresence()` には4つのモードがあります: + +- `true` この場合、フィールドが存在することが常に求められます。 +- `false` この場合、フィールドが存在することは必要なくなります。 +- `create` **create** 実行時にバリデーションを行う場合、このフィールドが存在することが求められます。 +- `update` **update** 実行時にバリデーションを行う場合、このフィールドが存在することが求められます。 + +デフォルトでは、 `true` が用いられます。キーの存在は、 `array_key_exists()` +を用いることによりチェックされるため、 null 値は、「存在する」としてカウントされます。 +モードについては、2番目のパラメーターを用いることにより設定できます。 : + +``` php +$validator->requirePresence('author_id', 'create'); +``` + +もし要求するフィールドが複数ある場合、それらをリストとして定義することができます。 : + +``` php +// create として複数フィールドを定義 +$validator->requirePresence(['author_id', 'title'], 'create'); + +// 混在したモードで複数フィールドを定義 +$validator->requirePresence([ + 'author_id' => [ + 'mode' => 'create', + 'message' => '著者は必須です。', + ], + 'published' => [ + 'mode' => 'update', + 'message' => '公開された状態が必要です。', + ] +]); +``` + +### 空のフィールドを認める + +バリデーターは、フィールドが空の値を許容するかどうかを制御するためのいくつかのメソッドを提供します。 +そして、許容された空の値は他の名前付きフィールドのバリデーションルールには転送されません。 +CakePHPは6つの異なる形状のデータに対して空の値のサポートを提供します。 + +1. `allowEmptyString()` 空文字列を許容する場合にのみ使用します +2. `allowEmptyArray()` 配列を許容する場合に使用します +3. `allowEmptyDate()` 空文字列か、日付フィールドに取り込まれる配列を許容する場合に使用します +4. `allowEmptyTime()` 空文字列か、時刻フィールドに取り込まれる配列を許容する場合に使用します +5. `allowEmptyDateTime()` 空文字列か、日付時刻フィールドまたはタイムスタンプフィールドに取り込まれる配列を許容する場合に使用します +6. `allowEmptyFile()` 空ファイルのアップロードが含まれた配列を許容する場合に使用します + +`notEmpty()` を使用して空のフィールドを無効にすることもできますが、 +推奨されるのは `notEmpty()` は使用せず、より具体的なバリデーターである +`notEmptyString()`, `notEmptyArray()`, `notEmptyFile()`, `notEmptyDate()`, `notEmptyTime()`, `notEmptyDateTime()` +を使用することです。 + +`allowEmpty*` メソッドは、空のフィールドを許容するかを制御する `when` パラメーターをサポートします。 + +- `false` フィールドは空にできません +- `create` **create** 操作のバリデーション時にフィールドを空にできます +- `update` **update** 操作のバリデーション時にフィールドを空にできます +- フィールドが空にできるかどうかを示す `true` または `false` を返すコールバック。このパラメーターの使用方法の例については、:ref:\`conditional-validation\` セクションを参照してください。 + +これらのメソッドの使用例は次のとおりです。: + +``` php +$validator->allowEmptyDateTime('published') + ->allowEmptyString('title', 'タイトルは空にできません', false) + ->allowEmptyString('body', '本文は空にできません', 'update') + ->allowEmptyFile('header_image', 'update'); + ->allowEmptyDateTime('posted', 'update'); +``` + +### バリデーションルールの追加 + +`Validator` クラスはバリデーターの構築をシンプルかつ表現力豊かにするメソッドを提供します。 +例えば、バリデーションルールを username フィールドに追加するには以下のようになります。 : + +``` php +$validator = new Validator(); +$validator + ->email('username') + ->ascii('username') + ->lengthBetween('username', [4, 8]); +``` + +バリデータメソッドの完全なセットについては、 [Validator API ドキュメント](https://api.cakephp.org/4.x/class-Cake.Validation.Validator.html) +をご覧ください。 + +### カスタムバリデーションルールの使用 + +`Validator` やプロバイダーから与えられるメソッドを使うことに加え、 +匿名関数を含むコールバック関数も、バリデーションルールとして用いることができます。 : + +``` php +// グローバル関数を利用する +$validator->add('title', 'custom', [ + 'rule' => 'validate_title', + 'message' => 'タイトルが正しくありません' +]); + +// プロバイダーではないコールバック関数を利用する +$validator->add('title', 'custom', [ + 'rule' => [$this, 'method'], + 'message' => 'タイトルが正しくありません' +]); + +// クロージャーを利用する +$extra = 'クロージャー内に必要な追加値'; +$validator->add('title', 'custom', [ + 'rule' => function ($value, $context) use ($extra) { + // true/falseを返すカスタムロジックを記入 + }, + 'message' => 'タイトルが正しくありません' +]); + +// カスタムプロバイダーからのルールを利用する +$validator->add('title', 'custom', [ + 'rule' => 'customRule', + 'provider' => 'custom', + 'message' => 'タイトルが十分にユニークではありません' +]); +``` + +クロージャーやコールバックメソッドは、呼び出された際に2つの設定を受けることとなります。 +最初は、バリデーションが行われるフィールド値であり、2番目はバリデーションプロセスに関連する +データを含む配列です。 + +- **data**: バリデーションメソッドに与えられた元々のデータのことです。 + 値を比較するようなルールを作る場合には、利用価値が高いといえます。 +- **providers**: プロバイダーオブジェクトについての完成されたリストのことです。 + 複数のプロバイダーを呼び出すことにより複雑なルールを作りたいときに、利用価値が高いといえます。 +- **newRecord**: バリデーションコールが新しいレコードのためのものか、 + すでにあるレコードのためのものかを示します。 + +既存ユーザーの ID のようにあなたのバリデーションメソッドに追加のデータを渡す必要がある場合、 +あなたのコントローラーからカスタム動的プロバイダー利用できます。 : + +``` php +$this->Examples->validator('default')->provider('passed', [ + 'count' => $countFromController, + 'userid' => $this->Auth->user('id') +]); +``` + +そのとき、あなたのバリデーションメソッドが、第2コンテキストパラメーターを持つことを保証します。 : + +``` php +public function customValidationMethod($check, array $context) +{ + $userid = $context['providers']['passed']['userid']; +} +``` + +もし、バリデーションに合格した場合、クロージャーはブーリアン型の true を返さなければなりません。 +もし、失敗した場合、ブーリアン型の false またはカスタムエラーメッセージとして文字列を返してください。 +詳しくは [条件付き/動的なエラーメッセージ](#dynamic_validation_error_messages) +をご覧ください。 + +### 条件付き/動的なエラーメッセージ + +バリデーションルールのメソッドは、 [カスタムコールバック](#custom-validation-rules) +または [プロバイダーによって提供されるメソッド](#adding-validation-providers) であり、 +検証が成功したかどうかを示すブーリアン型を返すか、検証が失敗したことを意味する文字列を返すことができ、 +返された文字列はエラーメッセージとして使用されます。 + +`message` オプションで定義された既存のエラーメッセージは、 +バリデーションルールメソッドから返されたエラーメッセージによって上書きされます。 : + +``` php +$validator->add('length', 'custom', [ + 'rule' => function ($value, $context) { + if (!$value) { + return false; + } + + if ($value < 10) { + return '値が 10 より小さい場合のエラーメッセージ'; + } + + if ($value > 20) { + return '値が 20 より大きい場合のエラーメッセージ'; + } + + return true; + }, + 'message' => '`false` が返されたときに使われる一般的なエラーメッセージ' +]); +``` + +### 条件付バリデーション + +バリデーションルールを定義する際、 `on` キーを用いることで、バリデーションルールが +適用されるべきか否かを定義することができます。未定義のままにすると、ルールは常に適用されます。 +他に有効な値は、 `create` 及び `update` です。これらの値を利用することにより、 +`create` や `update` 実行時にのみ、ルールが適用されることとなります。 + +加えて、特定なルールが適用されるべきか決めるためのコールバック関数を活用することもできます。 : + +``` php +$validator->add('picture', 'file', [ + 'rule' => ['mimeType', ['image/jpeg', 'image/png']], + 'on' => function ($context) { + return !empty($context['data']['show_profile_picture']); + } +]); +``` + +`$context['data']` 配列を用いることで、他の送信されたフィールドにアクセスすることが +できます。上記例では、 `show_profile_picture` の値が空かどうかで 'picture' +のルールを任意なものとします。また、 `uploadedFile` を用いることで、 +任意のファイルアップロードに関する入力を設定することができます。 : + +``` php +$validator->add('picture', 'file', [ + 'rule' => ['uploadedFile', ['optional' => true]], +]); +``` + +`allowEmpty*`, `notEmpty()` 及び `requirePresence()` メソッドは、 +最後に引数としてコールバック関数を受け付けることができます。もしこれがあれば、 +ルールが適用されるべきか否かをコールバック関数が決めます。例えば、以下のように、 +フィールド値が空のままでも許容される時もあります。 : + +``` php +$validator->allowEmptyString('tax', function ($context) { + return !$context['data']['is_taxable']; +}); +``` + +一方で、以下のように、一定の条件が満たされた場合にのみ、フィールド値が求められる +(空欄が許容されない)場合もあります。 : + +``` php +$validator->notEmpty('email_frequency', 'このフィールドは必須です', function ($context) { + return !empty($context['data']['wants_newsletter']); +}); +``` + +上記の例は、ユーザーがニュースレターを受領したい場合には、 `email_frequency` +フィールドが空欄のまま残されてはいけない、という例です。 + +さらに、一定の条件の下でのみフィールドが存在することを求めることも可能です。 : + +``` php +$validator->requirePresence('full_name', function ($context) { + if (isset($context['data']['action'])) { + return $context['data']['action'] === 'subscribe'; + } + + return false; +}); +$validator->requirePresence('email'); +``` + +これは、申し込みを作成したいユーザーの場合のみ `full_name` フィールドの存在を求め、 +`email` フィールドは常に要求されます。申し込みをキャンセルした時にも必要とされます。 + +条件付きのカスタムコールバックに渡される `$context` パラメータには、以下のキーが含まれます。 + +- `data` バリデートされるデータ +- `newRecord` 新規または既存のレコードが存在しているかどうかを示すブール値 +- `field` バリデートされるフィールド +- `providers` バリデーターに付与されるバリデーションプロバイダー + +### 最後に適用されるルールとして設定する + +フィールドに複数のルールが存在する場合は、前回のバリデーションが上手く機能しなかった場合でも、 +個々のバリデーションルールは適用されます。このことにより、一回のパスにより、好きなだけ +バリデーションエラーを設定することが可能となります。ただし、あるルールが上手くいかなかった後に +その後のバリデーションを適用したくない場合は、 `last` オプションを `true` +に設定することができます。 : + +``` php +$validator = new Validator(); +$validator + ->add('body', [ + 'minLength' => [ + 'rule' => ['minLength', 10], + 'last' => true, + 'message' => 'コメントには中身のある本文が必要です。', + ], + 'maxLength' => [ + 'rule' => ['maxLength', 250], + 'message' => 'コメントが長すぎることはできません。' + ] + ]); +``` + +上記例にて、minLength ルール適用によりエラーとなった場合は、maxLength ルールは適用されません。 + +### バリデーションプロバイダーを加える + +`Validator`, `ValidationSet`, `ValidationRule` の各クラスは、 +自らのバリデーションメソッドを提供するわけではありません。バリデーションルールは +'プロバイダー' からもたらされるのです。バリデーターオブジェクトに対しては、 +いくつでもプロバイダーを設定することができます。バリデーターインスタンスには、 +自動的にデフォルトのプロバイダー設定が付随しています。デフォルトのプロバイダーは、 +`Cake\Validation\Validation` のクラスにマッピングされております。 +このことが、このクラスにおけるメソッドをバリデーションルールとして使用することを容易にします。 +バリデーターと ORM をともに用いる場合は、テーブル及びエンティティーのオブジェクトのために +追加のプロバーダーが設定されます。アプリケーションの用途に応じてプロバイダーを追加したい場合は、 +`setProvider()` メソッドを用います。 : + +``` php +$validator = new Validator(); + +// オブジェクトインスタンスを使います。 +$validator->setProvider('custom', $myObject); + +// クラス名を使います。メソッドは静的なものでなければなりません。 +$validator->setProvider('custom', 'App\Model\Validation'); +``` + +バリデーションプロバイダーは、オブジェクトか、あるいはクラス名で設定されます。 +クラス名が使用されるのであれば、メソッドは静的でなければなりません。 +デフォルト以外のプロバイダーを使うには、ルールの中に `provider` +キーを挿入することを忘れないこと。 : + +``` php +// テーブルプロバイダーからのルールを使用する +$validator->add('title', 'custom', [ + 'rule' => 'customTableMethod', + 'provider' => 'table' +]); +``` + +今後作成される全ての `Validator` オブジェクトに `provider` を追加したい場合、 +以下のように `addDefaultProvider()` メソッドを使用できます。 : + +``` php +use Cake\Validation\Validator; + +// オブジェクトインスタンスを使います。 +Validator::addDefaultProvider('custom', $myObject); + +// クラス名を使います。メソッドは静的なものでなければなりません。 +Validator::addDefaultProvider('custom', 'App\Model\Validation'); +``` + +> [!NOTE] +> デフォルトプロバイダーは、 `Validator` オブジェクトが作成される前に追加されなければなりません。 +> そのため **config/bootstrap.php** がデフォルトプロバイダーの設定に最適な場所です。 + +国に基いて提供するための [Localized プラグイン](https://github.com/cakephp/localized) +が利用できます。このプラグインで、国に依存するモデルのフィールドをバリデートできます。 +例: + +``` php +namespace App\Model\Table; + +use Cake\ORM\Table; +use Cake\Validation\Validator; + +class PostsTable extends Table +{ + public function validationDefault(Validator $validator): Validator + { + // バリデーターにプロバイダーを追加 + $validator->setProvider('fr', 'Cake\Localized\Validation\FrValidation'); + // フィールドのバリデーションルールの中にプロバイダーを利用 + $validator->add('phoneField', 'myCustomRuleNameForPhone', [ + 'rule' => 'phone', + 'provider' => 'fr' + ]); + + return $validator; + } +} +``` + +Localized プラグインは、バリデーションのための国の2文字の ISO コード +(例えば en, fr, de) を使用します。 + +[ValidationInterface インターフェイス](https://github.com/cakephp/localized/blob/master/src/Validation/ValidationInterface.php) +によって定義されたすべてのクラスに共通する幾つかのメソッドがあります。 : + +``` text +電話番号のチェックのための phone() +郵便番号のチェックのための postal() +国が定めた個人 ID のチェックのための personId() +``` + +### バリデーターをネストする + +ネストされたデータで [モデルのないフォーム](../core-libraries/form) をバリデートする場合、 +また配列データを含むモデルを使用する場合、保有するネストされたデータをバリデートすることが +必要となります。CakePHP では、簡単に特定の属性に対してバリデーターを加えることが可能となります。 +例えば、非リレーショナルデータベースを用いて作業しており、とある記事とそれに対するコメントを +保存したいとします。 : + +``` php +$data = [ + 'title' => 'Best article', + 'comments' => [ + ['comment' => ''] + ] +]; +``` + +コメントに対してバリデーションをかけたい場合は、ネストされたバリデーターを使用します。 : + +``` php +$validator = new Validator(); +$validator->add('title', 'not-blank', ['rule' => 'notBlank']); + +$commentValidator = new Validator(); +$commentValidator->add('comment', 'not-blank', ['rule' => 'notBlank']); + +// ネストされたバリデーターをつなげる +$validator->addNestedMany('comments', $commentValidator); + +// ネストされたバリデーターからのエラーを含むすべてのエラーを取得する +$validator->validate($data); +``` + +`addNested()` を用いることで、1:1 の関係を構築することができ、 `addNestedMany()` +を用いることで 1:N の関係を築くことができます。両方のメソッドを用いることにより、 +ネストされたバリデーターのエラーは親バリデーターのエラーに貢献し、最終結果に影響を与えます。 +他のバリデーター機能と同様に、ネストされたバリデーターは、エラーメッセージと +条件付きアプリケーションをサポートします。 : + +``` php +$validator->addNestedMany( + 'comments', + $commentValidator, + 'Invalid comment', + 'create' +); +``` + +ネストされたバリデーターのエラーメッセージは、 `_nested` キーにあります。 + +### 再利用可能なバリデーターを作成する + +バリデーターを、使用されている場所で定義するのは、良いサンプルコードにはなりますが、 +簡単にメンテナンス可能なアプリケーションには結びつきません。実際には、 +再利用可能なバリデーションのロジックを使用する際、 +`Validator` サブクラスを使うべきです。 : + +``` php +// src/Model/Validation/ContactValidator.php にて +namespace App\Model\Validation; + +use Cake\Validation\Validator; + +class ContactValidator extends Validator +{ + public function __construct() + { + parent::__construct(); + // バリデーションのルールを加える + } +} +``` + +## データをバリデートする + +バリデーターを作成し、適用したいルールを加えたので、実際にデータを用いてバリデーションを +実施して行きましょう。バリデーターを用いることにより、配列ベースのデータのバリデーションが +可能となります。例えば、 email を作成し、送る前にコンタクト先のバリデーションを行いたい場合は、 +以下のようにするとよいでしょう。 : + +``` php +use Cake\Validation\Validator; + +$validator = new Validator(); +$validator + ->requirePresence('email') + ->add('email', 'validFormat', [ + 'rule' => 'email', + 'message' => 'Eメールは有効でなければなりません。' + ]) + ->requirePresence('name') + ->notEmpty('name', '名前が必要です。') + ->requirePresence('comment') + ->notEmpty('comment', 'コメントが必要です。'); + +$errors = $validator->validate($this->request->getData()); +if (empty($errors)) { + // email を送る。 +} +``` + +`errors()` メソッドは、バリデーションエラーがあった場合に、空でない配列を返します。 +返されたエラー配列は、以下のような構造となっております。 : + +``` php +$errors = [ + 'email' => ['Eメールは有効でなければなりません。'] +]; +``` + +もし単一のフィールドに複数のエラーがあった場合は、エラーメッセージの配列はフィールドごとに +返されます。デフォルトでは `errors()` メソッドは、 'create' を実行する際のルールが +適用されますが、 'update' を実行する際のルールを適用したい場合は、 +以下のことが可能となります。 : + +``` php +$errors = $validator->validate($this->request->getData(), false); +if (empty($errors)) { + // email を送る。 +} +``` + +> [!NOTE] +> もし、エンティティーをバリデーションしたい場合は、エンティティーのバリデーションのために +> 用意された次のようなメソッドを利用するべきです。 +> `Cake\ORM\Table::newEntity()`, +> `Cake\ORM\Table::newEntities()`, +> `Cake\ORM\Table::patchEntity()`, +> `Cake\ORM\Table::patchEntities()` +> as they are designed for that. + +## エンティティーをバリデーションする + +エンティティーは保存される際にバリデーションが実行されますが、保存を試みる前にエンティティーの +バリデーションを行いたいようなケースがあるかもしれません。 `newEntity()`, +`newEntities()`, `patchEntity()` または `patchEntities()` を使った場合、 +保存前のエンティティーのバリデーションは自動的に実行されます。 : + +``` php +// ArticlesController クラスにおいて +$article = $this->Articles->newEntity($this->request->getData()); +if ($article->errors()) { + // エラーメッセージが表示されるためのコードを書く +} +``` + +同様に、いくつかのエンティティーに対して同時に事前のバリデーションを実行したい場合は、 +`newEntities()` メソッドを用いることができます。 : + +``` php +// ArticlesController クラスにおいて +$entities = $this->Articles->newEntities($this->request->getData()); +foreach ($entities as $entity) { + if (!$entity->errors()) { + $this->Articles->save($entity); + } +} +``` + +`newEntity()`, `patchEntity()`, `newEntities()` 及び `patchEntities()` +メソッドを用いることによりどのアソシエーションがバリデーションされたか、 +`options` パラメーターを用いることによりどのバリデーションセットを適用させるかを +特定することができます。 : + +``` php +$valid = $this->Articles->newEntity($article, [ + 'associated' => [ + 'Comments' => [ + 'associated' => ['User'], + 'validate' => 'special', + ] + ] +]); +``` + +バリデーションは、ユーザーフォームやインターフェイスに主に利用され、その用途はテーブル内の +コラムをバリデーションすることに限られません。しかしながら、データ元がどこであったとしても、 +データの統一性を維持することは重要です。この問題を解決するために、CakePHP は +"アプリケーションルール" と呼ばれる2段階目のバリデーションを提供します。 +本件については、 [アプリケーションルールの適用](../orm/validation#application-rules) +セクションにて詳述します。 + +## コアバリデーションルール + +CakePHP は `Validation` クラス内にバリデーションメソッドに関する基本的な構文を提供します。 +バリデーションクラスには、色々な一般的なバリデーションのシチュエーションに対する、 +様々な静的なメソッドが含まれます。 + +`Validation` クラスにおける [API ドキュメント](https://api.cakephp.org/4.x/class-Cake.Validation.Validation.html) では、 +利用可能なバリデーションのルールについてのリスト及び基本的な使い方が案内されております。 + +いくつかのバリデーションメソッドは、上限下限に関する条件や有効なオプションを設定することができます。 +このような上限下限に関する条件や有効なオプションは、以下のように提供可能です。 : + +``` php +$validator = new Validator(); +$validator + ->add('title', 'minLength', [ + 'rule' => ['minLength', 10] + ]) + ->add('rating', 'validValue', [ + 'rule' => ['range', 1, 5] + ]); +``` + +追加のパラメーターが設定できるコアなルールには、 `rule` キーの中に、最初の要素として +ルールそのものを含むような配列が設定されるべきであり、その後のパラメーターには、 +残りのパラメーターを含ませるべきです。 diff --git a/docs/ja/core-libraries/xml.md b/docs/ja/core-libraries/xml.md new file mode 100644 index 0000000000..60bc8e09b8 --- /dev/null +++ b/docs/ja/core-libraries/xml.md @@ -0,0 +1,187 @@ +# Xml + +`class` Cake\\Utility\\**Xml** + +Xml クラスを利用して、 配列から SimpleXMLElement もしくは DOMDocument +オブジェクトに変換を、また配列に戻すことを可能にします。 + +## データを Xml クラスにインポートする + +`static` Cake\\Utility\\Xml::**build**($input, array $options = []) + +`Xml::build()` を利用することで、XML 形式のデータの読み込みが可能となります。 +`$options` により、このメソッドは SimpleXMLElement (デフォルト) もしくは +DOMDocument を返却します。 +様々なソースから XML オブジェクトのビルドに `Xml::build()` が利用できます。 +例えば、文字列から XML をロードできます。 : + +``` php +$text = ' + + 1 + Best post + ... +'; +$xml = Xml::build($text); +``` + +ローカルファイルから Xml オブジェクトにビルドも可能です。 : + +``` php +// ローカルファイル +$xml = Xml::build('/home/awesome/unicorns.xml'); +``` + +配列を利用して Xml オブジェクトのビルドも可能です。 : + +``` php +$data = [ + 'post' => [ + 'id' => 1, + 'title' => 'Best post', + 'body' => ' ... ' + ] +]; +$xml = Xml::build($data); +``` + +もし入力が不正であれば、 Xml クラスは例外を投げます。 : + +``` php +$xmlString = 'What is XML?'; +try { + $xmlObject = Xml::build($xmlString); // ここで例外が投げられます +} catch (\Cake\Utility\Exception\XmlException $e) { + throw new InternalErrorException(); +} +``` + +> [!NOTE] +> [DOMDocument](https://php.net/domdocument) と +> [SimpleXML](https://php.net/simplexml) は異なる API を実装します。 +> Xml によって返却されるオブジェクトの正しいメソッドを利用しているか +> 確認してください。 + +## XML 文字列を配列に変換する + +`static` Cake\\Utility\\Xml::**toArray**($obj) + +XML テキストを配列に変換するのは、 Xml クラスと同様にシンプルです。 +標準で SimpleXml オブジェクトから受け取ります。 : + +``` php +$xmlString = 'value'; +$xmlArray = Xml::toArray(Xml::build($xmlString)); +``` + +もし XML が不正の場合、 `Cake\Utility\Exception\XmlException` が起こります。 + +## 配列を XML 文字列に変換する + +``` php +$xmlArray = ['root' => ['child' => 'value']]; +// Xml::build() を使うこともできます +$xmlObject = Xml::fromArray($xmlArray, ['format' => 'tags']); +$xmlString = $xmlObject->asXML(); +``` + +配列の "トップレベル" 要素はたった一つであり、それは数字ではいけません。 +もし配列がこのフォーマットに従っていない時、Xml クラスは例外を投げます。 +不正な配列の例です。 : + +``` text +// トップレベルのキーが数字 +[ + ['key' => 'value'] +]; + +// トップレベルに複数のキーがある +[ + 'key1' => 'first value', + 'key2' => 'other value' +]; +``` + +標準では、配列の値が XML のタグとして出力されます。 +属性やテキストの値を定義したければ、接頭辞として許されている +`@` をキーに付与します。値のテキストは、 `@` をキーにします。 : + +``` php +$xmlArray = [ + 'project' => [ + '@id' => 1, + 'name' => 'Name of project, as tag', + '@' => 'Value of project' + ] +]; +$xmlObject = Xml::fromArray($xmlArray); +$xmlString = $xmlObject->asXML(); +``` + +`$xmlString` の内容は以下になります。 : + +``` php + +Value of projectName of project, as tag +``` + +### 名前空間を利用する + +XML の名前空間を利用するには、配列のキーに包括的な名前空間である +`xmlns:` を使用するか、独自の名前空間に +`xmlns:` を接頭語として加えたキーを使用して配列を作成します。 : + +``` php +$xmlArray = [ + 'root' => [ + 'xmlns:' => 'https://cakephp.org', + 'child' => 'value' + ] +]; +$xml1 = Xml::fromArray($xmlArray); + +$xmlArray( + 'root' => [ + 'tag' => [ + 'xmlns:pref' => 'https://cakephp.org', + 'pref:item' => [ + 'item 1', + 'item 2' + ] + ] + ] +); +$xml2 = Xml::fromArray($xmlArray); +``` + +`$xml1` と `$xml2` の値はそれぞれ以下になります。 : + +``` php + +value + + +item 1item 2 +``` + +### 子要素を作成 + +XML 文書を作成したのち、その文書に子要素を追加したり取り除いたり操作するには、 +単純に標準の実装を利用します。 : + +``` php +// SimpleXML を利用 +$myXmlOriginal = 'value'; +$xml = Xml::build($myXmlOriginal); +$xml->root->addChild('young', 'new value'); + +// DOMDocument を利用 +$myXmlOriginal = 'value'; +$xml = Xml::build($myXmlOriginal, ['return' => 'domdocument']); +$child = $xml->createElement('young', 'new value'); +$xml->firstChild->appendChild($child); +``` + +> [!TIP] +> SimpleXMLElement や DomDocument を利用して XML を操作したのちは、 +> `Xml::toArray()` を問題なく利用できます。 diff --git a/docs/ja/debug-kit.md b/docs/ja/debug-kit.md new file mode 100644 index 0000000000..abc92a9f03 --- /dev/null +++ b/docs/ja/debug-kit.md @@ -0,0 +1,3 @@ +# Debug Kit + +このページは [移動しました](https://book.cakephp.org/debugkit/4.x/ja/) 。 diff --git a/docs/ja/deployment.md b/docs/ja/deployment.md new file mode 100644 index 0000000000..3016c8002b --- /dev/null +++ b/docs/ja/deployment.md @@ -0,0 +1,127 @@ +# デプロイ + +アプリケーションが一度完成したら、または、完成する前でさえも、デプロイしたいと +思うでしょう。CakePHP アプリケーションをデプロイするにあたり、いくつかのことを +しなければなりません。 + +## ファイルの移動 + +git commit とあなたのサーバー上で commit やリポジトリーの pull や clone を作成し、 +`composer install` を実行することを奨励されます。git に関する幾つかの知識や +`git` や `composer` のインストールの知識が必要とされますが、このプロセスは、 +ライブラリーの依存関係やファイルやフォルダーのパーミッションについて扱います。 + +FTP 経由でデプロイするとき、少なくともファイルやフォルダーのパーミッションを +修正しなければならないことを理解してください。 + +ステージングやデモサーバー (試作品) をセットアップし、あなたの開発環境と同期を保つための +デプロイ技術を使用することもできます。 + +## config/app.php の調整 + +app.php、特に `debug` の値を調整することは非常に重要なことです。debug を +`false` に変更することにより、開発に関連する部分で、決して広くインターネットに +晒されるべきでない部分を無効にすることができます。debug を無効とすることにより、 +以下の種類のことが変更されます。 + +- `pr()` 、 `debug()` 及び `dd()` により + 生成されたデバッグメッセージが、無効化されます。 +- CakePHP コアのキャッシュが、開発時の 10 秒ごとの代わりに毎年 (約365日ごとに) + 破棄されるようになります。 +- エラービューの情報量は少なくなり、一般的なエラーメッセージしか表示されなくなります。 +- PHP エラーは表示されなくなります。 +- 例外のスタックトレースは無効化されます。 + +上記に加え、多くのプラグインとアプリケーションの拡張機能は、自らの振る舞いを +修正するために、 `debug` を使用します。 + +環境間でデバッグレベルを動的にセットするため、環境変数に対してチェックを +かけることができます。このことにより、アプリケーションをデバッグ `true` の状態で +デプロイすることを避けることができるだけでなく、毎回本番環境にデプロイする度に +デバッグレベルを変更せずに済むこととなります。 + +例えば、Apache の設定にて、環境変数をセットすることができます。 : + + SetEnv CAKEPHP_DEBUG 1 + +それから、\*\*app_local.php\*\* にてデバッグレベルを動的にセットすることができます。 : + +``` php +$debug = (bool)getenv('CAKEPHP_DEBUG'); + +return [ + 'debug' => $debug, + ..... +]; +``` + +It is recommended that you put configuration that is shared across all +of your application's environments in **config/app.php**. For configuration that +varies between environments either use **config/app_local.php** or environment +variables. + +## セキュリティのチェック + +もしあなたがウェブ上の荒野にアプリケーションを解き放とうとするなら、 +何か抜け穴がないかを確認しておくことをお勧めします。 + +- [Csrf Middleware](security/csrf#csrf-middleware) コンポーネントまたはミドルウェアを使用していることを確認して + 下さい。 +- [フォームの保護](controllers/components/form-protection) コンポーネントを有効化しておいた方が + いいかもしれません。フォームの改ざんや一括代入 (mass-assignment) 脆弱性に関する + 問題の発生可能性を削減することができます。 +- 各モデルにおいて、正しい [バリデーション](core-libraries/validation) ルールが + 有効化されているかどうかを確認して下さい。 +- `webroot` ディレクトリーのみが公開されており、その他の秘密の部分(ソルト値や + セキュリティキー等)は非公開でかつユニークな状態となっていることを確認して下さい。 + +## ドキュメントルートの指定 + +アプリケーションでドキュメントルートを正しく指定することはコードをセキュアに、 +またアプリケーションを安全に保つために重要なステップの内の一つです。 +CakePHP のアプリケーションは、アプリケーションの `webroot` に +ドキュメントルートを指定する必要があります。これによってアプリケーション、 +設定のファイルが URL を通してアクセスすることができなくなります。 +ドキュメントルートの指定の仕方はウェブサーバーごとに異なります。 +ウェブサーバー特有の情報については [Url Rewriting](installation#without-url-rewriting) ドキュメントを見てください。 + +どの場合においても `webroot/` をバーチャルホスト(バーチャルドメイン)の +ドキュメントルートに設定すべきでしょう。これは webroot ディレクトリーの外側のファイルを +実行される可能性を取り除きます。 + +## アプリケーションのパフォーマンス改善 + +クラスローディングは、アプリケーションのプロセス時間の大部分を占めることがあります。 +このような問題を避けるために、アプリケーションがデプロイされたら以下のコマンドを +本番サーバーにて走らせることを推奨します。 : + + php composer.phar dumpautoload -o + + + +プラグインの画像や JavaScript、CSS ファイルなどの静的なアセットを扱う場合、 +`Dispatcher` を通すことはかなり非効率です。本番環境においては、次のように +シンボリックリンクにすることを強くお勧めします。これは、 `plugin` シェルを +利用することで実行できます。 : + + bin/cake plugin assets symlink + +上記のコマンドは、アプリケーション内での `webroot` ディレクトリーの適切なパスに対して、 +全てのロードされたプラグインの `webroot` ディレクトリーのシンボリックリンクします。 + +もし、あなたのファイルシステムがシンボリックリンクを作成できない場合、 +ディレクトリーをシンボリックリンクする代わりにコピーします。また、以下を使用して、 +明示的にディレクトリーをコピーすることができます。 : + + bin/cake plugin assets copy + +## 更新のデプロイ + +デプロイごとに、Webサーバーで調整するタスクがいくつかある可能性があります。 +いくつかの典型的なものは次のとおりです。 + +1. `composer install` を使用して依存関係をインストールします。 + 予期しないバージョンのパッケージを取得する可能性があるため、デプロイを行うときに `composer update` は使用しないでください。 +2. Migrations プラグインまたは別のツールを使用して、データベースの [マイグレーション](migrations) を実行します。 +3. `bin/cake schema_cache clear` を実行してモデルスキーマキャッシュをクリアします。 + [スキーマキャッシュツール](console-commands/schema-cache) には、このコマンドに関する詳細情報があります。 diff --git a/docs/ja/development/application.md b/docs/ja/development/application.md new file mode 100644 index 0000000000..f9ac0f800e --- /dev/null +++ b/docs/ja/development/application.md @@ -0,0 +1,50 @@ +# アプリケーション + +`Application` はあなたのアプリケーションの心臓部です。 +アプリケーションがどのように構成され、何のプラグイン、ミドルウェア、コンソールコマンド、およびルートが含まれているかを制御します。 + +`Application` クラスは **src/Application.php** にあります。 +デフォルトでは非常にスリムで、いくつかのデフォルトの [ミドルウェア](../controllers/middleware) +を定義しているだけです。 Application は、次のフックメソッドを定義できます。 + +- `bootstrap` [設定ファイル](../development/configuration) を読み込み、 + 定数やグローバル関数を定義するために使用されます。デフォルトでは、 **config/bootstrap.php** を + 含みます。これは、あなたのアプリケーションが使用する [プラグイン](../plugins) を読み込むのに理想的な場所です。 +- `routes` [ルート](../development/routing) を読み込むために使用されます。 + デフォルトでは、 **config/routes.php** を含みます。 +- `middleware` アプリケーションに [ミドルウェア](../controllers/middleware) + を追加するために使用されます。 +- `console` アプリケーションに [コンソールコマンド](../console-commands) + を追加するために使用されます。 + デフォルトでは、アプリケーションとすべてのプラグインのシェルとコマンドが自動的に検出されます。 +- `events` アプリケーションのイベントマネージャーに + [イベントリスナー](../core-libraries/events) を追加するために使用されます。 + + + +## Application::bootstrap() + +アプリケーションの低レベルな関心事を設定するために使用する **config/bootstrap.php** ファイルに加えて、 +プラグインのロードや初期化、グローバルイベントリスナーの追加のために `Application::bootstrap()` フックメソッドが利用できます: + +``` php +// in src/Application.php +namespace App; + +use Cake\Http\BaseApplication; + +class Application extends BaseApplication +{ + public function bootstrap() + { + // Call the parent to `require_once` config/bootstrap.php + parent::bootstrap(); + + // Load MyPlugin + $this->addPlugin('MyPlugin'); + } +} +``` + +`Application::bootstrap()` でプラグインとイベントをロードすると、イベントとルートが各テストメソッドで再処理されるので、 +[Integration Testing](../development/testing#integration-testing) が簡単になります。 diff --git a/docs/ja/development/configuration.md b/docs/ja/development/configuration.md new file mode 100644 index 0000000000..2abff1c791 --- /dev/null +++ b/docs/ja/development/configuration.md @@ -0,0 +1,565 @@ +# 構成設定 + +規約は CakePHP のすべてを設定する必要性を取り除きますが、 +データベースの認証情報のようないくつかの設定をする必要があります。 + +さらに、デフォルト値と実装をアプリケーションに合わせて差し替えできるようにするオプションの +設定オプションもあります。 + +
    + +app.php, app_local.example.php + +
    + +
    + +configuration + +
    + +## アプリケーションの設定 + +設定は一般的に PHP か INI ファイルに保存され、アプリケーションのブート処理時に読み込まれます。 +CakePHP はデフォルトで一つの設定ファイルからなりますが、もし必要であれば追加の設定ファイルを加え、 +ブート処理コードで読み込むことができます。 `Cake\Core\Configure` +は一般的な設定に利用され、基底クラスのアダプターで提供されている `config()` メソッドは設定を +シンプルで明快にします。 + +アプリケーションのスケルトンは、デプロイ済のアプリケーションがにおいて +環境間で変わらない設定を **config/app.php** ファイルにを含むことを特徴としています。 +**config/app_local.php** ファイルには環境間で異なる設定データを含むべきです。 +これらのファイルはどちらも `env()` 関数を使って環境変数を参照しており、 +サーバ環境で設定値を設定することができます。 + +### 追加の設定ファイルの読み込み + +もしアプリケーションに多くの設定オプションがあるとき、設定を複数のファイルに分けることで役に立ちます。 +**config/** ディレクトリーに複数ファイルを作成したのち、 **bootstrap.php** でそれらを読み込めます。 : + +``` php +use Cake\Core\Configure; +use Cake\Core\Configure\Engine\PhpConfig; + +Configure::setConfig('default', new PhpConfig()); +Configure::load('app', 'default', false); +Configure::load('other_config', 'default'); +``` + +## 環境変数 + +例えば Heroku のように、多くの現代的なクラウド事業者では、設定データのために環境変数を定義できます。 +[12factor app style](https://12factor.net/) の環境変数を通して CakePHP を設定することができます。 +環境変数を使用すると、アプリケーションの状態を少なくして、 +多くの環境にデプロイされたアプリケーションの管理が容易になります。 + +**app.php** を参照の通り、 `env()` 関数は、環境から設定を読み込むために使用され、 +アプリケーションの設定を構築します。 CakePHP は、データベースやログ、メール送信や +キャッシュ設定のための `DSN` 文字列を使用して、各環境でこれらのライブラリーを簡単に変更できます。 + +CakePHP は、環境変数を使ってローカル開発を容易にするために [dotenv](https://github.com/josegonzalez/php-dotenv) を活用します。 +アプリケーションの中に `config/.env.default` があるでしょう。 +このファイルを `config/.env` にコピーし、値をカスタマイズすることで、 +アプリケーションを設定できます。 + +`config/.env` ファイルをあなたのリポジトリーにコミットすることは避けてください。 +代わりに、プレースホルダー値を持つテンプレートとして `config/.env.default` を使用して、 +チームの全員が、どの環境変数が使用されているのか、それぞれの環境変数を把握する必要があります。 + +環境変数がセットされると、環境からデータを読むために `env()` を使用することができます。 : + +``` php +$debug = env('APP_DEBUG', false); +``` + +env 関数に渡された2番目の値は、デフォルト値です。この値は、 +与えられたキーの環境変数が存在しない場合に使用されます。 + +### 一般的な設定 + +以下は、変数の説明と CakePHP アプリケーションに与える影響です。 + +debug +CakePHP のデバッグ出力を制御します。 `false` = 本番モードです。 +エラーメッセージやエラー、ワーニング出力を行いません。 `true` = エラーとワーニングが出力されます。 + +App.namespace +app クラスを見つけるための名前空間。 + +> [!NOTE] +> 名前空間の設定を変更した時は、おそらく **composer.json** ファイルもまた、 +> この名前空間を利用するように更新する必要があります。加えて、 +> `php composer.phar dumpautoload` を実行して、新しいオートローダーを作成してください。 + +
    + +App.baseUrl +もし CakePHP で Apache の mod_rewrite を利用する **予定がない** 場合、 +この定義のコメントを解除してください。 +.htaccess ファイルを取り除くことを忘れないでください。 + +App.base +アプリの存在するベースディレクトリーです。もし `false` をセットしたら、自動で検出されます。 +`false` 以外の場合、書き出しは / から始め、 / で終わらないことを確認してください。 +例えば、 /basedir は有効な App.base です。 + +App.encoding +あなたのアプリケーションで使用するエンコードを指定します。 +このエンコーディングはレイアウトの charset の生成やエンティティーのエンコードに利用されます。 +それは、データベースのエンコードの値と合うように指定すべきです。 + +App.webroot +webroot のディレクトリーです。 + +App.wwwRoot +webroot のファイルパスです。 + +App.fullBaseUrl +アプリケーションのルートまでの (プロトコルを含む) 完全修飾ドメイン名です。 +これは完全な URL を生成する際に利用されます。デフォルトでは、この値は +`$_SERVER` の環境情報から生成されます。しかし、パフォーマンスを最適化したり、 +他人が `Host` ヘッダーを操作するのを心配するならば、自分で指定すべきでしょう。 +CLI 環境 (シェル) ではウェブサーバーとの関連が無いので fullBaseUrl を +\$\_SERVER から読むことができません。もしシェルから URL を作成する必要がある場合 +(例えばメールの送信) 、自力で指定する必要があります。 + +App.imageBaseUrl +webroot 以下の公開画像ディレクトリーのパスになります。 +もし `CDN` を利用している場合、CDN の場所をセットすべきです。 + +App.cssBaseUrl +webroot 以下の公開 css ディレクトリーのパスになります。 +もし `CDN` を利用している場合、CDN の場所をセットすべきです。 + +App.jsBaseUrl +webroot 以下の公開 js ディレクトリーのパスになります。 +もし `CDN` を利用している場合、CDN の場所をセットすべきです。 + +App.paths +クラスベースではないリソースの Configure のパスです。 +`plugins` 、 `templates` 、 `locales` などのサブキーをサポートし、 +それぞれプラグイン、ビューテンプレート、ロケールファイルのパスを指定できます。 + +App.uploadedFilesAsObjects +アップロードされたファイルをオブジェクトとして表現するか(`true`)、 +配列として表現するか(`false`)を指定します。 +このオプションはデフォルトで有効になっています。 + +Security.salt +ハッシュ化の時に利用されるランダムな文字列です。 +この値は 対称キー暗号化の際、HMAC ソルトとして利用されます。 + +Asset.timestamp +適切なヘルパーを使用した際、アセットファイルの URL (CSS, JavaScript, Image) の終端に +そのファイルの最終更新時間のタイムスタンプを加えます。 +有効な値: + +- (bool) `false` - 何もしません (デフォルト)。 +- (bool) `true` - debug が `true` の時にタイムスタンプを加えます。 +- (string) 'force' - 常にタイムスタンプを加えます。 + +Asset.cacheTime +アセットのキャッシュ時間を設定します。 アセットのための HTTP ヘッダー `Cache-Control` の +`max-age` と HTTP ヘッダーの `Expire` の時間を決定します。 +php の [strtotime 関数](https://php.net/manual/ja/function.strtotime.php) +の書式を設定できます。デフォルトは `+1 day` です。 + +
    + +### CDNの利用 + +静的ファイルを読み込むために 例えば、 +`https://mycdn.example.com/` (最後の `/` に注意してください) +のようなCDNを使う場合は、 +`App.imageBaseUrl` 、 `App.cssBaseUrl` 、 `App.jsBaseUrl` +をCDNのURIに変更してください。 + +HtmlHelper経由で読み込まれた全ての画像、スクリプト、スタイルは、 +アプリケーションで使用されているのと同じ相対パスに合わせて、CDNの絶対パスを前に付けます。 +プラグインベースのアセットを使う場合には、特定の用途があることに注意してください。 +プラグインは絶対パスの `...BaseUrl` URIを使った場合、 +プラグインのプレフィックスを使わないようになっています。 + +デフォルトの場合: + +- `$this->Helper->assetUrl('TestPlugin.logo.png')` は `test_plugin/logo.png` に変換されます。 + +`App.imageBaseUrl` を `https://mycdn.example.com/` に設定した場合: + +- `$this->Helper->assetUrl('TestPlugin.logo.png')` は `https://mycdn.example.com/logo.png` に変換されます + +### データベースの設定 + +データベース接続の設定は [データベース設定](../orm/database-basics#database-configuration) を参照してください。 + +### キャッシュの設定 + +CakePHP のキャッシュ設定は [キャッシュ設定](../core-libraries/caching#cache-configuration) を参照してください。 + +### エラーと例外ハンドリング設定 + +エラーの設定と例外のハンドリングは [エラーと例外設定](../development/errors#error-configuration) を参照してください。 + +### ログの設定 + +CakePHP のログの設定は [Log Configuration](../core-libraries/logging#log-configuration) を参照してください。 + +### メールの設定 + +CakePHP のメールプリセットの設定は [メールの設定](../core-libraries/email#email-configuration) を参照してください。 + +### セッションの設定 + +CakePHP のセッション操作の設定は [セッションの設定](../development/sessions#session-configuration) +を参照してください。 + +### ルーティングの設定 + +ルーティングの設定やアプリケーションのルートの作成に関する詳しい情報は +[ルーティングの設定](../development/routing#routes-configuration) を参照してください。 + +## 追加のクラスパス + +追加のクラスパスはアプリケーションで利用されるオートローダーを通じてセットアップされます。 +`composer` を利用してオートローダーを作成する際、以下のように記述してコントローラーの +代わりのパスを提供します。 : + +``` json +"autoload": { + "psr-4": { + "App\\Controller\\": "/path/to/directory/with/controller/folders/", + "App\\": "src/" + } +} +``` + +上記は `App` と `App\Controller` 両方の名前空間のパスをセットアップします。 +一つ目のキーが検索され、そのパスにクラス/ファイルが含まれていなければ二つ目のキーが検索されます。 +次のようにして、一つの名前空間に複数のディレクトリーをマップすることもできます。 : + +``` json +"autoload": { + "psr-4": { + "App\\": ["src/", "/path/to/directory/"] + } +} +``` + +### プラグイン、ビュー、テンプレート、ロケールのパス + +プラグイン、ビューテンプレート、そしてロケールはクラスではないので、オートローダーの設定はありません。 +CakePHP はこれらのリソースの追加パスをセットアップするための 3 つの Configure 変数を提供します。 +**config/app.php** の中でこれらの変数をセットできます。 : + +``` text +return [ + // 他の設定 + 'App' => [ + 'paths' => [ + 'plugins' => [ + ROOT . DS . 'plugins' . DS, + '/path/to/other/plugins/' + ], + 'templates' => [ + ROOT . DS . 'templates' . DS, + ROOT . DS . 'templates2' . DS + ], + 'locales' => [ + ROOT . DS . 'resources' . DS . 'locales' . DS + ] + ] + ] +]; +``` + +パスはディレクトリーセパレーター付きで終了し、そうでないと適切に動作しないです。 + +## Inflection の設定 + +[Inflection Configuration](../core-libraries/inflector#inflection-configuration) を参照してください。 + +## Configure クラス + +`class` Cake\\Core\\**Configure** + +CakePHP の Configure クラスはアプリケーションもしくは実行時の特定の値の保存と取り出しで利用されます。 +このクラスは何でも保存でき、その後他のどのような箇所でも利用できるため、確実に CakePHP の +MVC デザインパターンを破壊する誘惑に気をつけてください。Configure クラスの主なゴールは、 +中央集権化された変数を維持し、たくさんのオブジェクト間で共有できることです。 +「設定より規約」を維持することを忘れないでください。そうすれば、CakePHP が提供する MVC 構造を +壊すことはないでしょう。 + +### 設定データの書き込み + +`static` Cake\\Core\\Configure::**write**($key, $value) + +`write()` を利用してアプリケーションの設定にデータを保存します。 : + +``` php +Configure::write('Company.name','Pizza, Inc.'); +Configure::write('Company.slogan','Pizza for your body and soul'); +``` + +> [!NOTE] +> `$key` 変数に `ドット記法` を使用すると、 論理的なグループに設定を整理できます。 + +上記の例は一度の呼び出しでも記述できます。 : + +``` php +Configure::write('Company', [ + 'name' => 'Pizza, Inc.', + 'slogan' => 'Pizza for your body and soul' +]); +``` + +`Configure::write('debug', $bool)` を利用してデバッグと本番モードを即時に変更できます。 +これはとりわけ JSON のやりとりで使いやすく、デバッグ情報がパースの問題を引き起こす際です。 + +> [!NOTE] +> Configure::write()\`\`を使って行われた設定の変更はすべてメモリに保存され、 +> リクエストをまたいでも持続しないようになっています。 + +### 設定データの読み込み + +`static` Cake\\Core\\Configure::**read**($key = null, $default = null) + +アプリケーションから設定データを読み込むために利用されます。もしキーが指定されれば、 +そのデータが返却されます。上記の write() の例を取り上げると、以下のようにデータを読み込みます。 : + +``` php +// 'Pizza Inc.' を返します +Configure::read('Company.name'); + +// 'Pizza for your body and soul' を返します +Configure::read('Company.slogan'); + +Configure::read('Company'); +// 戻り値: +['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul']; + +// Company.nope は定義されていないので 'fallback' を返します +Configure::read('Company.nope', 'fallback'); +``` + +もし `$key` が null のままだと、Configure のすべての値が返却されます。 + +`static` Cake\\Core\\Configure::**readOrFail**($key) + +設定データを単に `Cake\Core\Configure::read` で読み込みますが、 +一方で key/value ペアを検索することを期待します。要求されたペアが存在しない場合、 +`RuntimeException` が投げられます。 : + +``` php +Configure::readOrFail('Company.name'); // 出力: 'Pizza, Inc.' +Configure::readOrFail('Company.geolocation'); // 例外を投げる + +Configure::readOrFail('Company'); + +// 出力: +['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul']; +``` + +### 定義されている設定データのチェック + +`static` Cake\\Core\\Configure::**check**($key) + +キー / パス が存在しているか、値が null でないかチェックする場合に利用します。 : + +``` php +$exists = Configure::check('Company.name'); +``` + +### 設定データの削除 + +`static` Cake\\Core\\Configure::**delete**($key) + +アプリケーションの設定から情報を削除するために利用されます。 : + +``` php +Configure::delete('Company.name'); +``` + +### 設定データの読み書き + +`static` Cake\\Core\\Configure::**consume**($key) + +Configure からキーの読み込みと削除を行います。 +もしあなたが値の読み込みと削除を単一の動作で組み合わせたい時に便利です。 + +`static` Cake\\Core\\Configure::**consumeOrFail**($key) + +`Cake\Core\Configure::consume` のように設定データを消費しますが、 +一方でキーと値のペアが見つかることを期待します。要求されたペアが存在しない場合、 +`RuntimeException` が投げられます。 : + +``` php +Configure::consumeOrFail('Company.name'); // 出力: 'Pizza, Inc.' +Configure::consumeOrFail('Company.geolocation'); // 例外を投げる + +Configure::consumeOrFail('Company'); + +// 出力: +['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul']; +``` + +## 設定ファイルの読み書き + +`static` Cake\\Core\\Configure::**config**($name, $engine) + +CakePHP は 2 つの組み込み設定ファイルエンジンを搭載しています。 +`Cake\Core\Configure\Engine\PhpConfig` は +Configure が昔から読んできた同じフォーマットで PHP の設定ファイル形式を読み込むことができます。 +`Cake\Core\Configure\Engine\IniConfig` は ini 設定ファイル形式を読み込めます。 +詳細な ini ファイルの仕様は [PHP マニュアル](https://php.net/parse_ini_file) を参照してください。 +コアの設定エンジンを利用するにあたり、Configure に `Configure::config()` +を設定する必要があります。 : + +``` php +use Cake\Core\Configure\Engine\PhpConfig; + +// config から設定ファイルを読み込み +Configure::config('default', new PhpConfig()); + +// 別のパスから設定ファイルを読み込み +Configure::config('default', new PhpConfig('/path/to/your/config/files/')); +``` + +複数のエンジンを Configure に設定することができ、それぞれ異なった種類もしくはパスの設定ファイルを +読み込みます。Configure のいくつかのメソッドを利用して設定されたエンジンとやり取りできます。 +どのエンジンのエイリアスが設定されているかチェックするには、 `Configure::configured()` +が利用できます。 : + +``` php +// 配置されたエンジンのエイリアスの配列を取得する +Configure::configured(); + +// 特定のエンジンが配置されているかチェックする +Configure::configured('default'); +``` + +`static` Cake\\Core\\Configure::**drop**($name) + +配置されたエンジンを取り除くことができます。 +`Configure::drop('default')` は default のエンジンエイリアスを取り除きます。 +この先、そのエンジンを使って設定ファイルを読み込もうとする試みは失敗します。 : + +``` php +Configure::drop('default'); +``` + +### 設定ファイルの読み込み + +`static` Cake\\Core\\Configure::**load**($key, $config = 'default', $merge = true) + +一旦設定エンジンに Configure を設定すると、設定ファイルを読み込むことができます。 : + +``` php +// 'default' エンジンオブジェクトを使用して my_file.php を読み込む +Configure::load('my_file', 'default'); +``` + +読み込まれた設定ファイルは、自身のデータを Configure 内に存在している実行時の設定とマージします。 +これは存在している実行時の設定へ値の上書きや新規追加を可能とします。 +`$merge` を `true` にセットすることで、存在している設定の値を上書きしなくなります。 + +### 設定ファイルの作成や編集 + +`static` Cake\\Core\\Configure::**dump**($key, $config = 'default', $keys = []) + +全て、もしくはいくつかの Configure にあるデータを、 +ファイルや設定エンジンがサポートしているストレージシステムにダンプします。 +シリアライズのフォーマットは、\$config で配置された設定エンジンから決定されます。 +例えば、もし 'default' エンジンが `Cake\Core\Configure\Engine\PhpConfig` +ならば、生成されたファイルは `Cake\Core\Configure\Engine\PhpConfig` +によって読み込み可能な PHP の設定ファイルになるでしょう。 + +'default' エンジンは PhpConfig のインスタンスとして考えられます。 +Configure の全てのデータを my_config.php に保存します。 : + +``` php +Configure::dump('my_config', 'default'); +``` + +エラーハンドリング設定のみ保存します。 : + +``` php +Configure::dump('error', 'default', ['Error', 'Exception']); +``` + +`Configure::dump()` は `Configure::load()` で読み込み可能な設定ファイルを +変更もしくは上書きするために利用できます。 + +### 実行時の設定を保存 + +`static` Cake\\Core\\Configure::**store**($name, $cacheConfig = 'default', $data = null) + +将来のリクエストのために、実行時の設定を保存することができます。 +設定は現在のリクエストのみ値を記憶するので、 +もしその後のリクエストで編集された設定情報を利用したければ、それを保存する必要があります。 : + +``` php +// 現在の設定を 'user_1234' キーに 'default' キャッシュとして保存 +Configure::store('user_1234', 'default'); +``` + +保存された設定データはその名前のキャッシュ設定で存続します。 +キャッシュに関するより詳しい情報は [キャッシュ](../core-libraries/caching) を参照してください。 + +### 実行時の設定を復元 + +`static` Cake\\Core\\Configure::**restore**($name, $cacheConfig = 'default') + +実行時の設定を保存すると、おそらくそれを復元して、再びそれにアクセスする必要があります。 +`Configure::restore()` がちょうどそれに該当します。 : + +``` php +// キャッシュから実行時の設定を復元する +Configure::restore('user_1234', 'default'); +``` + +設定情報を復元する場合、それを保存する時に使われたのと同じ鍵、 +およびキャッシュ設定で復元することが重要です。 +復元された情報は、既存の実行時設定の最上位にマージされます。 + +### 設定エンジン + +CakePHP は、さまざまなソースから設定ファイルを読み込む機能を提供し、 +[独自の設定エンジンを作成するための](https://api.cakephp.org/4.x/class-Cake.Core.Configure.ConfigEngineInterface.html) +プラガブルなシステムを備えています。組み込みの設定エンジンは次の通りです。 + +- [JsonConfig](https://api.cakephp.org/4.x/class-Cake.Core.Configure.Engine.JsonConfig.html) +- [IniConfig](https://api.cakephp.org/4.x/class-Cake.Core.Configure.Engine.IniConfig.html) +- [PhpConfig](https://api.cakephp.org/4.x/class-Cake.Core.Configure.Engine.PhpConfig.html) + +デフォルトでは、アプリケーションは `PhpConfig` を使用します。 + +## 汎用テーブルの無効化 + +新しいアプリケーションを素早く作成したり、モデルを生成する時に便利な +auto-tables とも呼ばれる汎用テーブルクラスを利用していますが、 +汎用テーブルクラスは、ある場面ではデバッグが困難になることがあります。 + +DebugKit の SQL パネルから DebugKit 経由で汎用テーブルクラスから +クエリーが発行されたかどうかを確認できます。もし、なおも auto-tables によって +引き起こされたかもしれない問題を診断するのに困っている場合、次のように、 +CakePHP が固有のクラスを使用する代わりに、暗黙的に汎用的な `Cake\ORM\Table` を +使用する時に例外を投げることができます。 : + +``` php +// bootstrap.php の中で +use Cake\Event\EventManager; +use Cake\Http\Exception\InternalErrorException; + +$isCakeBakeShellRunning = (PHP_SAPI === 'cli' && isset($argv[1]) && $argv[1] === 'bake'); +if (!$isCakeBakeShellRunning) { + EventManager::instance()->on('Model.initialize', function($event) { + $subject = $event->getSubject(); + if (get_class($subject) === 'Cake\ORM\Table') { + $msg = sprintf( + 'データベーステーブル %s のテーブルクラスを登録する時、テーブルクラスが見つからないか、エイリアスが不正です。', + $subject->getTable()); + throw new InternalErrorException($msg); + } + }); +} +``` diff --git a/docs/ja/development/debugging.md b/docs/ja/development/debugging.md new file mode 100644 index 0000000000..0b275804c6 --- /dev/null +++ b/docs/ja/development/debugging.md @@ -0,0 +1,183 @@ +# デバッグ + +デバッグはいかなる開発サイクルにおいても避けることのできない、必要なものです。 +CakePHP は IDE やエディターと直接連携するようなツールは提供しませんが、 +CakePHP はデバッグ作業やあなたのアプリケーション内部で何が走っているのかを探る作業を +助けるためのツールをいくつか提供します。 + +## 基本的なデバッグ + +`function` **debug(mixed $var, boolean $showHtml = null, $showFrom = true)** + +`debug()` 関数は PHP 関数の `print_r()` と同様に、グローバルに利用可能な関数です。 +`debug()` 関数により、さまざまな方法で変数の内容を出力することができます。 +データを HTML に優しい方法で表示させたいなら、第2引数を `true` にしてください。 +この関数はまた、デフォルトで呼ばれた場所となるファイルと行番号も出力します。 + +この関数からの出力は、core の `$debug` 変数が `true` の場合のみ行われます。 + +`dd()` 、 `pr()` 及び `pj()` もご確認ください。 + +`function` **stackTrace()** + +`stackTrace()` 関数はグローバルに使用でき、関数がどこで呼ばれたかのスタックトレースを +出力することができます。 + +`function` **breakpoint()** + +もし [Psysh](https://psysh.org/) をインストールしている場合、この関数を +CLI 環境で使用することで現在のローカルスコープで対話型コンソールを開くことができます。 : + +``` text +// 実行したいコード +eval(breakpoint()); +``` + +開いた対話型コンソールでローカル変数のチェックや他のコードの実行をすることができます。 +対話型デバッガーを終了して元の処理に戻りたい時は `quit` か `q` を入力してください。 + +## Debugger クラスの使用 + +`class` Cake\\Error\\**Debugger** + +Debugger を使用する際にはまず、 `Configure::read('debug')` に +`true` がセットされていることを確認してください。 + +## 値の出力 + +`static` Cake\\Error\\Debugger::**dump**($var, $depth = 3) + +dump は変数の内容を出力します。渡された変数のすべてのプロパティーと +(可能なら)メソッドを出力します。 : + +``` php +$foo = [1,2,3]; + +Debugger::dump($foo); + +// 出力 +array( + 1, + 2, + 3 +) + +// シンプルなオブジェクト +$car = new Car(); + +Debugger::dump($car); + +// 出力 +object(Car) { + color => 'red' + make => 'Toyota' + model => 'Camry' + mileage => (int)15000 +} +``` + +### データのマスク + +`Debugger` でデータをダンプしたり、エラーページを描画する際、パスワードや API キーなど +機密キーを隠したくなるでしょう。 `config/bootstrap.php` の中で、指定したキーを +マスクすることができます。 : + +``` css +Debugger::setOutputMask([ + 'password' => 'xxxxx', + 'awsKey' => 'yyyyy', +]); +``` + +## スタックトレース付きのログ出力 + +`static` Cake\\Error\\Debugger::**log**($var, $level = 7, $depth = 3) + +呼び出されたときに詳細なスタックトレースを生成します。 +`log()` メソッドは `Debugger::dump()` によるものと似たデータを出力しますが、 +出力バッファにではなく、 debug.log に出力します。 `log()` が正常に動作するためには、 +あなたの **tmp** ディレクトリー(と、その中)はウェブサーバーにより +書き込み可能でなければならないことに気をつけてください。 + +## スタックトレースの生成 + +`static` Cake\\Error\\Debugger::**trace**($options) + +現在のスタックトレースを返します。トレースの各行には、呼び出しているメソッド、 +どこから呼ばれたかというファイルと行番号が含まれています。 : + +``` text +// PostsController::index() の中で +pr(Debugger::trace()); + +// 出力 +PostsController::index() - APP/Controller/DownloadsController.php, line 48 +Dispatcher::_invoke() - CORE/src/Routing/Dispatcher.php, line 265 +Dispatcher::dispatch() - CORE/src/Routing/Dispatcher.php, line 237 +[main] - APP/webroot/index.php, line 84 +``` + +上記では、コントローラーのアクション内で `Debugger::trace()` を呼ぶことで、 +スタックトレースを生成しています。 +スタックトレースは下から上へと読み、現在走っている関数(スタックフレーム)の順になっています。 + +## ファイルから抜粋を取得 + +`static` Cake\\Error\\Debugger::**excerpt**($file, $line, $context) + +\$path(絶対パス)にあるファイルからの抜粋を取得します。\$line 行目をハイライトし、 +\$line 行目の前後 \$context 行もあわせて取得します。 : + +``` php +pr(Debugger::excerpt(ROOT . DS . LIBS . 'debugger.php', 321, 2)); + +// 下記のように出力されます +Array +( + [0] => * @access public + [1] => */ + [2] => function excerpt($file, $line, $context = 2) { + + [3] => $data = $lines = array(); + [4] => $data = @explode("\n", file_get_contents($file)); +) +``` + +このメソッドは内部的に使われているものですが、あなたが独自のエラーメッセージを生成する場合や +独自の状況でログ出力する場合にも使いやすいものです。 + +`static` Debugger::**getType**($var) + +変数の型を取得します。オブジェクトならクラス名を返します。 + +## ログ出力によるデバッグ + +アプリケーションをデバッグするもう一つの良い方法はログメッセージです。 +`Cake\Log\Log` を使うことで、あなたのアプリケーションでログ出力を +させることができます。 `LogTrait` を利用するすべてのオブジェクトは、 +インスタンスメソッド `log()` を持っており、ログメッセージを出力するのに使えます。 : + +``` php +$this->log('通ったよ', 'debug'); +``` + +上記では `通ったよ` がデバッグログに出力されます。 +ログに出力することで、リダイレクトや複雑なループを含むメソッドをデバッグしやすくなるでしょう。 +また、`Cake\Log\Log::write()` を使うことで、ログメッセージを書きだすことも可能です。 +このメソッドは Log がロードされているなら static にあなたのアプリケーション内の +どこからでも呼び出すことができるのです。 : + +``` php +// ログを使用したいファイルの一番最初で +use Cake\Log\Log; + +// Log がインポートされている場所で +Log::debug('通ったよ'); +``` + +## Debug Kit + +DebugKit は便利なデバッグツールをたくさん提供してくれるプラグインです。 +まずは、レンダリングされた HTML 内にツールバーを表示して、あなたのアプリケーションや +現在のリクエストについての情報を大量に提供してくれます。 +DebugKit のインストールと使用方法については [Debug Kit](../debug-kit) の章を見てください。 diff --git a/docs/ja/development/dependency-injection.md b/docs/ja/development/dependency-injection.md new file mode 100644 index 0000000000..87cac121c1 --- /dev/null +++ b/docs/ja/development/dependency-injection.md @@ -0,0 +1,280 @@ +# 依存性の注入(DI) + +CakePHPのサービスコンテナは依存性の注入(DI)によりアプリケーションのサービスのためのクラス依存性を管理できます。 +DIは手動でインスタンス化することなく、自動でコンストラクタを通してオブジェクトの依存性を"注入"します。 + +サービスコンテナを使うことで‘application services’を定義することができ、 +これらのクラスはモデルを使います。また、loggerやmailerなどを使って +再利用可能なワークフローの構築やアプリケーションのビジネスロジックに作用します。 + +CakePHPはコントローラーでアクションを呼ぶ際サービスコンテナを使い、その後コンソールコマンドを呼び出します。 +コントローラーのコンストラクタにもDIを持たせることができます。 + +簡単な例: + +``` php +// src/Controller/UsersController.php +class UsersController extends AppController +{ + // サービスコンテナを通して$usersサービスが作られる + public function ssoCallback(UsersService $users) + { + if ($this->request->is('post')) { + // シングルサインオンのプロバイダーからUsersServiceのcreateやgetを使う + $user = $users->ensureExists($this->request->getData()); + } + } +} +``` + +この例では、`UsersController::ssoCallback()` アクションは +シングルサインオンプロバイダーからユーザーを取得し、ローカルのデータベースにその値が存在すると保証されている必要があります。 +このサービスはコントローラーに注入されているからこそ、テストをする際に簡単に実装をモックオブジェクトや偽のサブクラスと交換できるのです。 + +コマンド内でサービス注入を行う例: + +``` php +// In src/Command/CheckUsersCommand.php +class CheckUsersCommand extends Command +{ + /** @var UsersService */ + public $users; + + public function __construct(UsersService $users) + { + parent::__construct(); + $this->users = $users; + } + + public function execute( Arguments $args, ConsoleIo $io ) + { + $valid = $this->users->check('all'); + } + +} + +// In src/Application.php +public function services( ContainerInterface $container ): void +{ + $container + ->add(CheckUsersCommand::class) + ->addArgument(UsersService::class); + $container->add(UsersService::class); +} +``` + +ここでは、インジェクションの手順が少し異なります。 +`UsersService` をコンテナに追加するのではなく、まずCommand全体をコンテナに追加し、 `UsersService` を引数として追加する必要があります。 +これで、コマンドのコンストラクタ内でそのサービスにアクセスできるようになります。 + +## サービスの追加 + +コンテナに作成したサービスを持たせるには、 +どのクラスが作成でき、どうビルドするかを伝える必要があります。 + +最もシンプルな方法はクラス名で定義することです: + +``` php +// 名前でクラスを追加する +$container->add(BillingService::class); +``` + +アプリケーションとプラグイン内の `services()` フックメソッドからサービスを定義します。: + +``` php +// in src/Application.php +namespace App; + +use App\Service\BillingService; +use Cake\Core\ContainerInterface; +use Cake\Http\BaseApplication; + +class Application extends BaseApplication +{ + public function services(ContainerInterface $container): void + { + $container->add(BillingService::class); + } +} +``` + +アプリケーションが使うインターフェースに実装を定義できます: + +``` php +use App\Service\AuditLogServiceInterface; +use App\Service\AuditLogService; + +// あなたのApplication::services() メソッド内 + +// 実装をインターフェースに追加 +$container->add(AuditLogServiceInterface::class, AuditLogService::class); +``` + +必要ならオブジェクト生成にコンテナ側でファクトリー関数を活用できます: + +``` php +$container->add(AuditLogServiceInterface::class, function (...$args) { + return new AuditLogService(...$args); +}); +``` + +ファクトリー関数はすべてのクラス解決された依存関係を引数として受け取ります。 + +一度クラスを定義すると求められる依存性も定義する必要があります。それらの依存性はオブジェクトやプリミティブ値にもなります。: + +``` php +// 文字列や配列や数値のプリミティブ値を追加する +$container->add('apiKey', 'abc123'); + +$container->add(BillingService::class) + ->addArgument('apiKey'); +``` + +### 共有サービスを追加する + +デフォルトではサービスは共有されません。オブジェクトや(依存性)はコンテナから取得される時にそれぞれ生成されます。 +もしシングルトン・パターンに基づく単一のインスタンスを再利用したい場合は、サービスに'shared'をつけてください。: + +``` php +// あなたのApplication::services()メソッド内で + +$container->addShared(BillingService::class); +``` + +### 定義の拡張 + +定義の拡張によって、一度サービスが定義されてからも編集や更新が可能です。 +これにより、定義されたサービスに引数を追加できます。 + +コード内どこかで: + +``` php +// 部分的に定義されたサービスのどこかで引数の追加 +$container->extend(BillingService::class) + ->addArgument('logLevel'); +``` + +### サービスのタグ化 + +サービスのタグ化により同時にすべてのタグ化されたサービスを取得できます。 +レポートシステムなど他サービスのコレクションと組み合わせるサービスをビルドする際に使えます。: + +``` php +$container->add(BillingReport::class)->addTag('reports'); +$container->add(UsageReport::class)->addTag('reports'); + +$container->add(ReportAggregate::class, function () use ($container) { + return new ReportAggregate($container->get('reports')); +}); +``` + +### 設定データを使用する場合 + +しばしば、サービスで設定データが必要な時がありますよね。 +コンテナに入れる際必要なサービスの設定キーをすべて追加するなんてうんざりします。 +サービス設定をより簡単にするために、CakePHPの注入可能な設定読み込み機能を使います。: + +``` php +use Cake\Core\ServiceConfig; + +// シェアされたインスタンスを使用する +$container->addShared(ServiceConfig::class); +``` + +`ServiceConfig` クラスは `Configure` で利用可能な全データのread-onlyな一覧を提供します。 +なので、誤って設定が変わる心配はありません。 + +## サービス・プロバイダー + +サービス・プロバイダーによって関連したサービスをまとめ上げる補助をし、グループ化することができます。 + +また、サービス・プロバイダーは定義したサービスが初めて使われる際、遅延登録され +アプリケーションのパフォーマンスを上げることができます。 + +### サービス・プロバイダーの作成 + +ServiceProviderの一例: + +``` php +namespace App\ServiceProvider; + +use Cake\Core\ContainerInterface; +use Cake\Core\ServiceProvider; +// 他はここにインポート + +class BillingServiceProvider extends ServiceProvider +{ + protected $provides = [ + StripeService::class, + 'configKey', + ]; + + public function services(ContainerInterface $container): void + { + $container->add(StripService::class); + $container->add('configKey', 'some value'); + } +} +``` + +サービス・プロバイダーは自身の `services()` メソッドを使って、提供するサービスをすべて定義します。 +さらに、それらのサービスは **絶対に** `$provides` に正しく定義する必要があります。 +正しく `$provides` に含められなかった場合、コンテナから読み込めなくなります。 + +### サービス・プロバイダーの使用 + +サービス・プロバイダーを読み込むには `addServiceProvider()` メソッドを使ってコンテナに追加してください: + +``` php +// Application::services()メソッド内で +$container->addServiceProvider(new BillingServiceProvider()); +``` + +### 起動可能なサービス・プロバイダー + +もしサービス・プロバイダーがコンテナに追加された時、ロジックを走らせる必要がある場合 +`bootstrap()` メソッドを使ってください。 +想定される状況として +サービス・プロバイダーが追加の設定ファイルを読み込む必要があったり、 +追加のサービス・プロバイダーを読み込んだり、 +アプリケーションのどこかで定義されたサービスを変更する場合などが考えられます。 + +起動可能なサービス・プロバイダーの例: + +``` php +namespace App\ServiceProvider; + +use Cake\Core\ServiceProvider; +// 他はここにインポート + +class BillingServiceProvider extends ServiceProvider +{ + protected $provides = [ + StripeService::class, + 'configKey', + ]; + + public function bootstrap($container) + { + $container->addServiceProvider(new InvoicingServiceProvider()); + } +} +``` + +## サービスをモック化してテストする + +テスト内で `ConsoleIntegrationTestTrait` や `IntegrationTestTrait` を使うことででコンテナを通して注入されるサービスとスタブやモックを入れ替えることができます。: + +``` php +// テストメソッドやsetup()内で +$this->mockService(StripeService::class, function () { + return new FakeStripe(); +}); + +// モックを削除する場合 +$this->removeMockService(StripeService::class); +``` + +テスト時には定義されたどんなモックもアプリケーションのコンテナ内で交換されます。 +そして、自動的にコンテナやコマンドに注入されます。 +それぞれのテストの最後でモックは除去されます。 diff --git a/docs/ja/development/errors.md b/docs/ja/development/errors.md new file mode 100644 index 0000000000..eb6e4e0387 --- /dev/null +++ b/docs/ja/development/errors.md @@ -0,0 +1,603 @@ +# エラーと例外の処理 + +CakePHP アプリケーションには、エラー処理と例外処理が用意されています。 +PHP エラーはトラップされ、表示またはログに記録されます。 +キャッチされなかった例外はエラーページに自動的にレンダリングされます。 + +## エラーと例外の設定 + +エラーの設定はアプリケーションの **config/app.php** ファイル中で行われます。デフォルトでは、 +CakePHP は PHP エラーと例外の両方を処理するために `Cake\Error\ErrorHandler` を使います。 +エラーの設定を使用すると、アプリケーションのエラー処理をカスタマイズできます。 +次のオプションをサポートします。 + +- `errorLevel` - int - あなたが捕捉したいエラーレベル。組み込みの PHP エラー定数を使い、 + 捕捉したいエラーレベルを選択するためにビットマスクします。非推奨の警告を無効にするために、 + `E_ALL ^ E_USER_DEPRECATED` をセットすることができます。 +- `trace` - bool - ログファイル中にエラーのスタックトレースを含めます。 + スタックトレースはログ中の各エラーの後に含まれるでしょう。 + これはどこで/いつそのエラーが引き起こされたかを見つけるために役に立ちます。 +- `exceptionRenderer` - string - キャッチされなかった例外を描画する役目を担うクラス。 + もしもカスタムクラスを選択する場合は **src/Error** 中にそのクラスのファイルを置くべきです。 + このクラスは `render()` メソッドを実装する必要があります。 +- `log` - bool - `true` の時、 `Cake\Log\Log` によって例外と + そのスタックトレースが `Cake\Log\Log` に記録されます。 +- `skipLog` - array - ログに記録されるべきではない例外クラス名の配列。 + これは NotFoundException や他のありふれた、でもログにはメッセージを残したくない例外を + 除外するのに役立ちます。 +- `extraFatalErrorMemory` - int - 致命的エラーが起きた時にメモリーの上限を増加させるための + メガバイト数を設定します。これはログの記録やエラー処理を完了するために猶予を与えます。 +- `errorLogger` - `Cake\Error\ErrorLoggerInterface` - エラーログの記録および未処理の + 例外を担当するクラス。デフォルトは `Cake\Error\ErrorLogger` +- `ignoredDeprecationPaths` - array - 非推奨のエラーが無視されるべきパス(glob互換)のリスト。 + 4.2.0で追加 + +エラーハンドラーは既定では、 `debug` が `true` の時にエラーを表示し、 +`debug` が `false` の時にエラーをログに記録します。 +いずれも捕捉されるエラータイプは `errorLevel` によって制御されます。 +致命的エラーのハンドラーは `debug` レベルや `errorLevel` とは独立して呼び出されますが、 +その結果は `debug` レベルによって変わるでしょう。 +致命的エラーに対する既定のふるまいは内部サーバーエラーページ (`debug` 無効) +またはエラーメッセージ、ファイルおよび行を含むページ (`debug` 有効) を表示します。 + +> [!NOTE] +> もしカスタムエラーハンドラーを使うなら、サポートされるオプションはあなたのハンドラーに依存します。 + + + +### 非推奨の警告 + +CakePHPは、非推奨の警告を使用して、機能が非推奨になったことを示します。 +また、このシステムをプラグインやアプリケーションコードで使用することをお勧めします。 +`deprecationWarning()` を使用して非推奨の警告をトリガーできます。 : + +``` text +deprecationWarning('The example() method is deprecated. Use getExample() instead.'); +``` + +CakePHPまたはプラグインをアップグレードすると、新しい非推奨の警告が表示される場合があります。 +いくつかの方法のいずれかで、非推奨の警告を一時的に無効にすることができます。 + +1. `Error.errorLevel` を `E_ALL ^ E_USER_DEPRECATED` に設定して、すべての非推奨警告を無視します。 + +2. `Error.ignoredDeprecationPaths` 設定オプションを使用して、glob互換の表現で非推奨を無視します。 + 以下をご覧ください。 : + + ``` text + 'Error' => [ + 'ignoredDeprecationPaths' => [ + 'vendors/company/contacts/*', + 'src/Models/*', + ] + ], + ``` + + アプリケーションにおける `Models` ディレクトリと `Contacts` プラグインからのすべての非推奨を無視します。 + +`class` **ExceptionRenderer**(Exception $exception) + +## 例外処理の変更 + +例外処理では、例外の処理方法を調整するいくつかの方法が用意されています。 +それぞれのアプローチでは、例外処理プロセスの制御量が異なります。 + +1. *エラーテンプレートのカスタマイズ* 描画されたビューテンプレートを + アプリケーション内の他のテンプレートと同様に変更できます。 +2. *ErrorController のカスタマイズ* 例外ページの描画方法を制御できます。 +3. *ExceptionRenderer のカスタマイズ* 例外ページとロギングの実行方法を制御できます。 +4. *独自のエラーハンドラーの作成と登録* エラーと例外がどのように処理され、記録され、 + 描画されるかを完全に制御することができます。 + +## エラーテンプレートのカスタマイズ + +デフォルトのエラーハンドラは、 `Cake\Error\ExceptionRenderer` とアプリケーションの +`ErrorController` の助けを借りて、アプリケーションで発生した全ての捕捉されない例外を描画します。 + +エラーページのビューは **templates/Error/** に配置されます。デフォルトでは、 +すべての 4xx エラーは **error400.php** テンプレートを使い、 +すべての 5xx エラーは **error500.php** を使います。 +エラーテンプレートの変数は次のとおりです。 + +- `message` 例外メッセージ。 +- `code` 例外コード。 +- `url` リクエスト URL。 +- `error` 例外オブジェクト。 + +デバッグモードでエラーが `Cake\Core\Exception\Exception` を継承した場合、 +`getAttributes()` によって返されたデータはビュー変数としても公開されます。 + +> [!NOTE] +> **error404** と **error500** テンプレートを表示するには `debug` を false に +> 設定する必要があります。デバッグモードだと、 CakePHP の開発用エラーページが表示されます。 + +### エラーページレイアウトのカスタマイズ + +デフォルトでは、エラーテンプレートは、レイアウトに **templates/layout/error.php** を使います。 +`layout` プロパティーを使って別のレイアウトを選ぶことができます。 : + +``` php +// templates/Error/error400.php の中で +$this->layout = 'my_error'; +``` + +上記は、エラーページのレイアウトとして **templates/layout/my_error.php** を使用します。 +CakePHP によって引き起こされる多くの例外は、特定のビューテンプレートをデバッグモードで描画します。 +デバッグをオフにすると、CakePHP によって生成されたすべての例外は、ステータスコードに基づいて +**error400.php** または **error500.php** のいずれかを使用します。 + +## ErrorController のカスタマイズ + +`App\Controller\ErrorController` クラスは CakePHP の例外レンダリングでエラーページビューを +描画するために使われ、すべての標準リクエストライフサイクルイベントを受け取ります。 +このクラスを変更することで、どのコンポーネントが使用され、どのテンプレートが描画されるかを制御できます。 + +アプリケーション内で [プレフィックスルーティング](../development/routing#prefix-routing) を利用している場合は、 +それぞれのルーティングプレフィックスに対してカスタムエラーコントローラーを作成できます。 +例えば、 `Admin` プレフィックスの場合は以下のクラスを作成することができます。 : + +``` php +namespace App\Controller\Admin; + +use App\Controller\AppController; +use Cake\Event\EventInterface; + +class ErrorController extends AppController +{ + /** + * Initialization hook method. + * + * @return void + */ + public function initialize(): void + { + $this->loadComponent('RequestHandler'); + } + + /** + * beforeRender callback. + * + * @param \Cake\Event\EventInterface $event Event. + * @return void + */ + public function beforeRender(EventInterface $event) + { + $this->viewBuilder()->setTemplatePath('Error'); + } +} +``` + +このコントローラーは、プレフィックス付きのコントローラーでエラーが発生したときにのみ利用できます。 +そして、必要に応じてプレフィックス固有のロジック/テンプレートを定義できます。 + +## ExceptionRenderer の変更 + +例外レンダリングとロギングプロセス全体を制御したい場合は **config/app.php** の +`Error.exceptionRenderer` オプションを使用して、例外ページをレンダリングするクラスを +選択することができます。ExceptionRenderer の変更は、アプリケーション固有の +例外クラスに対してカスタムエラーページを提供する場合に便利です。 + +カスタム例外レンダラークラスは **src/Error** に配置する必要があります。 +アプリケーションで `App\Exception\MissingWidgetException` を使用して欠落している +ウィジェットを示すとしましょう。このエラーが処理されたときに特定のエラーページを +レンダリングする例外レンダラーを作成することができます。 : + +``` php +// src/Error/AppExceptionRenderer.php の中で +namespace App\Error; + +use Cake\Error\ExceptionRenderer; + +class AppExceptionRenderer extends ExceptionRenderer +{ + public function missingWidget($error) + { + $response = $this->controller->response; + + return $response->withStringBody('おっとウィジェットが見つからない!'); + } +} + +// config/app.php の中で +'Error' => [ + 'exceptionRenderer' => 'App\Error\AppExceptionRenderer', + // ... +], +// ... +``` + +上記は `MissingWidgetException` 型のあらゆる例外を処理し、 +それらのアプリケーション例外を表示/処理するためのカスタム処理ができるようにします。 + +例外レンダリングメソッドは、引数として処理される例外を受け取り、 +`Response` オブジェクトを返さなければなりません。 +また、CakePHP のエラーを処理する際にロジックを追加するメソッドを実装することもできます。 : + +``` php +// src/Error/AppExceptionRenderer.php の中で +namespace App\Error; + +use Cake\Error\ExceptionRenderer; + +class AppExceptionRenderer extends ExceptionRenderer +{ + public function notFound($error) + { + // NotFoundException オブジェクトで何かをします。 + } +} +``` + +### ErrorController クラスの変更 + +例外レンダラーは、例外の描画に使用されるコントローラーを指定します。 +例外を描画するコントローラーを変更したい場合は、例外レンダラーの +`_getController()` メソッドをオーバーライドしてください。 : + +``` php +// src/Error/AppExceptionRenderer の中で +namespace App\Error; + +use App\Controller\SuperCustomErrorController; +use Cake\Error\ExceptionRenderer; + +class AppExceptionRenderer extends ExceptionRenderer +{ + protected function _getController() + { + return new SuperCustomErrorController(); + } +} + +// config/app.php の中で +'Error' => [ + 'exceptionRenderer' => 'App\Error\AppExceptionRenderer', + // ... +], +// ... +``` + +## 独自エラーハンドラーの作成 + +エラーハンドラーを置き換えることによって、エラーおよび例外処理プロセス全体をカスタマイズできます。 +`Cake\Error\BaseErrorHandler` を継承することでエラーを処理するためのカスタムロジックを提供できます。 +たとえば、エラーを処理するために `AppError` というクラスを使うことができます。 : + +``` php +// config/bootstrap.php の中で +use App\Error\AppError; + +$errorHandler = new AppError(); +$errorHandler->register(); + +// src/Error/AppError.php の中で +namespace App\Error; + +use Cake\Error\ErrorHandler; +use Throwable; + +class AppError extends ErrorHandler +{ + protected function _displayError(array $error, bool $debug): void + { + echo 'エラーがありました!'; + } + + protected function _displayException(Throwable $exception): void + { + echo '例外がありました!'; + } +} +``` + +Then we can register our error handler as the PHP error handler: + +``` php +// In config/bootstrap.php +use App\Error\AppError; + +if (PHP_SAPI !== 'cli') { + $errorHandler = new AppError(); + $errorHandler->register(); +} +``` + +Finally, we can use our error handler in the `ErrorHandlerMiddleware`: + +``` php +// in src/Application.php +public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue +{ + $error = new AppError(Configure::read('Error')); + $middleware->add(new ErrorHandlerMiddleware($error)); + + return $middleware; +} +``` + +For console error handling, you should extend `Cake\Error\ConsoleErrorHandler` instead: + +``` php +// In /src/Error/AppConsoleErrorHandler.php +namespace App\Error; +use Cake\Error\ConsoleErrorHandler; + +class AppConsoleErrorHandler extends ConsoleErrorHandler { + + protected function _displayException(Throwable $exception): void { + parent::_displayException($exception); + if (isset($exception->queryString)) { + $this->_stderr->write('Query String: ' . $exception->queryString); + } + } + +} +``` + +`BaseErrorHandler` は二つの抽象メソッドを定義しています。 +`_displayError()` はエラーが引き起こされた時に使われます。 +`_displayException()` メソッドはキャッチされなかった例外がある時に呼ばれます。 + +### 致命的エラーのふるまい変更 + +既定のエラーハンドラーは致命的エラーを例外に変換し +エラーページを描画するための例外処理方法を再利用します。 +もし標準のエラーページを表示したくない場合は、あなたはそれをオーバーライドできます。 : + +``` php +// src/Error/AppError.php の中で +namespace App\Error; + +use Cake\Error\BaseErrorHandler; + +class AppError extends BaseErrorHandler +{ + // 他のメソッド + + public function handleFatalError(int $code, string $description, string $file, int $line): bool + { + echo '致命的エラーが発生しました'; + } +} +``` + +## Custom Error Logging + +Error handlers use instances of `Cake\Error\ErrorLoggingInterface` to create +log messages and log them to the appropriate place. You can replace the error +logger using the `Error.logger` configure value. An example error +logger: + +``` php +namespace App\Error; + +use Cake\Error\ErrorLoggerInterface; +use Psr\Http\Message\ServerRequestInterface; +use Throwable; + +/** + * Log errors and unhandled exceptions to `Cake\Log\Log` + */ +class ErrorLogger implements ErrorLoggerInterface +{ + /** + * @inheritDoc + */ + public function logMessage($level, string $message, array $context = []): bool + { + // Log PHP Errors + } + + public function log(Throwable $exception, ?ServerRequestInterface $request = null): bool + { + // Log exceptions. + } +} +``` + +
    + +application exceptions + +
    + +## 独自アプリケーション例外の作成 + +組み込みの [SPL の例外](https://php.net/manual/en/spl.exceptions.php) 、 +`Exception` そのもの、または `Cake\Core\Exception\Exception` +のいずれかを使って、独自のアプリケーション例外を作ることができます。 +もしアプリケーションが以下の例外を含んでいたなら: + +``` php +use Cake\Core\Exception\Exception; + +class MissingWidgetException extends Exception +{ +} +``` + +**templates/Error/missing_widget.php** を作ることで、素晴らしい開発用エラーを提供できるでしょう。 +本番モードでは、上記のエラーは 500 エラーとして扱われ、 **error500** テンプレートを使用するでしょう。 + +例外コードが `400` と `506` の間にある場合、例外コードは HTTP レスポンスコードとして使用されます。 + +`Cake\Core\Exception\Exception` のコンストラクターが継承されており、 +追加のデータを渡すことができます。それら追加のデータは `_messageTemplate` に差し込まれます。 +これにより、エラー用の多くのコンテキスト提供して、データ豊富な例外を作ることができます。 : + +``` php +use Cake\Core\Exception\Exception; + +class MissingWidgetException extends Exception +{ + // コンテキストデータはこのフォーマット文字列に差し込まれます。 + protected $_messageTemplate = '%s が見当たらないようです。'; + + // デフォルトの例外コードも設定できます。 + protected $_defaultCode = 404; +} + +throw new MissingWidgetException(['widget' => 'Pointy']); +``` + +レンダリングされると、このビューテンプレートには `$widget` 変数が設定されます。 +もしその例外を文字列にキャストするかその `getMessage()` メソッドを使うと +`Pointy が見当たらないようです。` を得られるでしょう。 + +### 例外のログ記録 + +組み込みの例外処理を使うと、 **config/app.php** 中で `log` オプションに `true` を設定することで +ErrorHandler によって対処されるすべての例外をログに記録することができます。 +これを有効にすることで `Cake\Log\Log` と設定済みのロガーに各例外の記録が残るでしょう。 + +> [!NOTE] +> もしもカスタム例外ハンドラーを使用している場合、 +> あなたの実装の中でそれを参照しない限り、この設定は効果がないでしょう。 + +## CakePHP 用の組み込みの例外 + +### HTTP の例外 + +CakePHP 内部のいくつかの組み込みの例外には、内部的なフレームワークの例外の他に、 +HTTP メソッド用のいくつかの例外があります。 + +> 400 Bad Request エラーに使われます。 +> +> 401 Unauthorized エラーに使われます。 +> +> 403 Forbidden エラーに使われます。 +> +> 無効な CSRF トークンによって引き起こされた 403 エラーに使われます。 +> +> 404 Not Found エラーに使われます。 +> +> 405 Method Not Allowed エラーに使われます。 +> +> 406 Not Acceptable エラーに使われます。 +> +> 409 Conflict エラーに使われます。 +> +> 410 Gone エラーに使われます。 + +HTTP 4xx エラーステータスコードの詳細は `2616#section-10.4` をご覧ください。 + +> 500 Internal Server Error に使われます。 +> +> 501 Not Implemented エラーに使われます。 +> +> 503 Service Unavailable エラーに使われます。 + +HTTP 5xx エラーステータスコードの詳細は `2616#section-10.5` をご覧ください。 + +失敗の状態や HTTP エラーを示すためにあなたのコントローラーからこれらの例外を投げることができます。 +HTTP の例外の使用例はアイテムが見つからなかった場合に 404 ページを描画することでしょう。 : + +``` php +use Cake\Http\Exception\NotFoundException; + +public function view($id = null) +{ + $article = $this->Articles->findById($id)->first(); + if (empty($article)) { + throw new NotFoundException(__('記事が見つかりません')); + } + $this->set('article', $article); + $this->viewBuilder()->setOption('serialize', ['article']); +} +``` + +HTTP エラー用の例外を使うことで、あなたのコードを綺麗にし、 +かつ RESTful なレスポンスをアプリケーションのクライアントやユーザーに返すことができます。 + +### コントローラー中での HTTP の例外の使用 + +失敗の状態を示すためにコントローラーのアクションからあらゆる +HTTP 関連の例外を投げることができます。例: + +``` php +use Cake\Network\Exception\NotFoundException; + +public function view($id = null) +{ + $article = $this->Articles->findById($id)->first(); + if (empty($article)) { + throw new NotFoundException(__('記事が見つかりません')); + } + $this->set('article', 'article'); + $this->viewBuilder()->setOption('serialize', ['article']); +} +``` + +上記は `NotFoundException` をキャッチして処理するための例外ハンドラーを設定するでしょう。 +デフォルトではエラーページを作り、例外をログに記録するでしょう。 + +### その他の組み込みの例外 + +さらに、CakePHP は次の例外を使用します。 + +> 選択されたビュークラスが見つかりません。 +> +> 選択されたテンプレートファイルが見つかりません。 +> +> 選択されたレイアウトが見つかりません。 +> +> 選択されたヘルパーが見つかりません。 +> +> 選択されたエレメントのファイルが見つかりません。 +> +> 選択されたセルクラスが見つかりません。 +> +> 選択されたセルのビューファイルが見つかりません。 +> +> 設定されたコンポーネントが見つかりません。 +> +> 要求されたコントローラーのアクションが見つかりません。 +> +> private/protected/\_ が前置されたアクションへのアクセス。 +> +> コンソールライブラリークラスがエラーに遭遇しました。 +> +> 設定されたタスクが見つかりません。 +> +> シェルクラスが見つかりません。 +> +> 選択されたシェルクラスが該当の名前のメソッドを持っていません。 +> +> モデルの接続がありません。 +> +> データベースドライバーが見つかりません。 +> +> データベースドライバーのための PHP 拡張がありません。 +> +> モデルのテーブルが見つかりません。 +> +> モデルのエンティティーが見つかりません。 +> +> モデルのビヘイビアーが見つかりません。 +> +> `Cake\ORM\Table::saveOrFail()` や +> `Cake\ORM\Table::deleteOrFail()` を使用しましたが、 +> エンティティーは、保存/削除されませんでした。 +> +> 要求されたレコードが見つかりません。 +> これにより HTTP 応答ヘッダーも 404 に設定されます。 +> +> 要求されたコントローラーが見つかりません。 +> +> 要求された URL はルーティングの逆引きができないか解析できません。 +> +> CakePHP での基底例外クラス。 +> CakePHP によって投げられるすべてのフレームワーク層の例外はこのクラスを継承するでしょう。 + +これらの例外クラスはすべて `Exception` を継承します。 +Exception を継承することにより、あなたは独自の‘フレームワーク’エラーを作ることができます。 + +`method` Cake\\Core\\Exception\\ExceptionRenderer::**responseHeader**($header = null, $value = null) + +すべての Http と Cake の例外は Exception クラスを継承し、 +レスポンスにヘッダーを追加するためのメソッドを持っています。 +例えば、405 MethodNotAllowdException を投げる時、RFC2616 によると: + + "The response MUST include an Allow header containing a list of valid + methods for the requested resource." + + 「レスポンスは要求されたリソースに有効なメソッドの一覧を含むAllowヘッダーを含まなければ【ならない】」 diff --git a/docs/ja/development/rest.md b/docs/ja/development/rest.md new file mode 100644 index 0000000000..e194fcb4d6 --- /dev/null +++ b/docs/ja/development/rest.md @@ -0,0 +1,160 @@ +# REST + +最近のアプリケーションプログラマーは、サービスのコア機能を +ユーザーにオープンにする必要があると気付き始めています。 +簡単に提供でき、自由にコア API にアクセスできれば、広く受け入れられ、 +マッシュアップされたり、簡単に他のシステムと統合できます。 + +簡単にあなたの作ったアプリケーションロジックにアクセスさせる方法は色々ありますが、 +REST はその中でもすばらしい方法でしょう。とてもシンプルで、大抵は XML ベース +(SOAP のようなものではなく、単純な XML のこと) で、HTTP ヘッダーによって制御されます。 +CakePHP を使って REST の API を提供するのはすごく簡単です。 + +## 簡単なセットアップ + +REST を動かすための手っ取り早い方法は、 config/routes.php ファイルに +[リソースルート](../development/routing#resource-routes) をセットアップするための数行を追記することです。 + +一度、Router が REST リクエストを、コントローラーのアクションにマッピングしておけば、 +そのコントローラーのアクションのロジックの作成に移ることができます。 +基本的なコントローラーのサンプルは下記のようになります。 : + +``` php +// src/Controller/RecipesController.php +class RecipesController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('RequestHandler'); + } + + public function index() + { + $recipes = $this->Recipes->find('all')->all(); + $this->set('recipes', $recipes); + $this->viewBuilder()->setOption('serialize', ['recipes']); + } + + public function view($id) + { + $recipe = $this->Recipes->get($id); + $this->set('recipe', $recipe); + $this->viewBuilder()->setOption('serialize', ['recipe']); + } + + public function add() + { + $this->request->allowMethod(['post', 'put']); + $recipe = $this->Recipes->newEntity($this->request->getData()); + if ($this->Recipes->save($recipe)) { + $message = 'Saved'; + } else { + $message = 'Error'; + } + $this->set([ + 'message' => $message, + 'recipe' => $recipe, + ]); + $this->viewBuilder()->setOption('serialize', ['recipe', 'message']); + } + + public function edit($id) + { + $this->request->allowMethod(['patch', 'post', 'put']); + $recipe = $this->Recipes->get($id); + $recipe = $this->Recipes->patchEntity($recipe, $this->request->getData()); + if ($this->Recipes->save($recipe)) { + $message = 'Saved'; + } else { + $message = 'Error'; + } + $this->set([ + 'message' => $message, + 'recipe' => $recipe, + ]); + $this->viewBuilder()->setOption('serialize', ['recipe', 'message']); + } + + public function delete($id) + { + $this->request->allowMethod(['delete']); + $recipe = $this->Recipes->get($id); + $message = 'Deleted'; + if (!$this->Recipes->delete($recipe)) { + $message = 'Error'; + } + $this->set('message', $message); + $this->viewBuilder()->setOption('serialize', ['message']); + } +} +``` + +しばしば、RESTful なコントローラーは、リクエストの種類ごとに異なるビューファイルを扱うために +パースされた拡張子を使用します。REST リクエストが処理できるようになったので、XML ビューなどが +作成できます。CakePHP の組み込みの [JSON と XML ビュー](../views/json-and-xml-views) を利用して +JSON ビューを作成できます。組み込みの `XmlView` を扱うために、 +`_serialize` というビュー変数を定義します。この特別なビュー変数は、 `XmlView` の中に +取り込まれ、出力結果が XML に変換されます。 + +XML データに変換する前にデータを修正したい場合は、 `_serialize` ビュー変数ではなく、 +ビューファイルを使いましょう。RecipesController に対する REST ビューを +**templates/Recipes/xml** 以下に置きます。 `Xml` クラスを使えば、 +このビューファイル内で簡単に素早く XML を出力させることができます。 +下記に index ビューの例を載せます。 : + +``` php +// templates/Recipes/xml/index.php +// $recipes 配列に対して いくつかのフォーマットと操作を行う。 +$xml = Xml::fromArray(['response' => $recipes]); +echo $xml->asXML(); +``` + +`Cake\Routing\Router::extensions()` を使って、特定のコンテンツタイプを扱う場合、 +CakePHP は自動的にそのタイプに対応するビューヘルパーを探します。 +ここではコンテンツタイプとして XML を利用していて、標準のビルトインヘルパーは存在しないのですが、 +もし自作のヘルパーがあれば CakePHP はそれを自動読込みして利用可能にします。 + +レンダリングされた XML は下記のような感じになります。 : + +``` html + + + 234 + 2008-06-13 + 2008-06-14 + + 23423 + Billy + Bob + + + 245 + Yummy yummmy + + + ... + +``` + +edit アクションのロジックを作るのは少しだけトリッキーです。XML 出力 の API を提供する場合、 +入力も XML で受付けるほうが自然です。心配せずとも、 +`Cake\Controller\Component\RequestHandler` と +`Cake\Routing\Router` クラスが取り計らってくれます。 +POST もしくは PUT リクエストのコンテンツタイプが XML であれば、入力データは CakePHP の +`Xml` クラスに渡され、配列に変換され、 `$this->request->getData()` に入ります。 +この機能によって、XML と POST データの処理はシームレスになるのです。コントローラーもモデルも +XML の入力を気にせずに、 `$this->request->getData()` のみを扱えば良いのです。 + +## 他のフォーマットのインプットデータ + +REST アプリケーションの場合、様々なフォーマットのデータを扱います。 +CakePHP では、 `RequestHandlerComponent` クラスが助けてくれます。 +デフォルトでは、POST や PUT で送られてくる JSON/XML の入力データはデコードされ、 +配列に変換されてから `$this->request->getData()` に格納されます。独自のデコード処理も +`RequestHandler::addInputType()` を利用すれば追加可能です。 + +## RESTful ルーティング + +CakePHP の Router は、 RESTful なリソースルートへの接続は容易です。詳しくは、 +[Resource Routes](../development/routing#resource-routes) セクションをご覧ください。 diff --git a/docs/ja/development/routing.md b/docs/ja/development/routing.md new file mode 100644 index 0000000000..d186c48c06 --- /dev/null +++ b/docs/ja/development/routing.md @@ -0,0 +1,1626 @@ +# ルーティング + +`class` Cake\\Routing\\**RouterBuilder** + +ルーティングは URL とコントローラーのアクションをマップするツールを提供します。 +ルートを設定することで、アプリケーションの実装方法を URL の構造から分離できます。 + +CakePHP でのルーティングはまた パラメーターの配列を URL 文字列に変換する +リバースルーティングというアイディアも含まれます。リバースルーティングを使用することによって、 +アプリケーションの URL の構造を全部のコードの書き直しをせずに再調整できます。 + +
    + +routes.php + +
    + +## クイックツアー + +ここでは、 CakePHP の最も一般的なルーティングの方法について例を出して説明します。 +ランディングページとして何かを表示したい時がよくあるでしょう。そのときは、 **routes.php** +ファイルに以下を加えます。 : + +``` php +/** @var \Cake\Routing\RouteBuilder $routes */ +$routes->connect('/', ['controller' => 'Articles', 'action' => 'index']); +``` + +これはサイトのホームページにアクセスした時に `ArticlesController` の +index メソッドを実行します。時々、複数のパラメーターを受け取る動的なルートが +必要になると思います。それが必要になるケースは、例えば、記事の内容を表示するためのルートです。 : + +``` php +$routes->connect('/articles/*', ['controller' => 'Articles', 'action' => 'view']); +``` + +上記のルートは、 `/articles/15` のような URL を受け取り、 `ArticlesController` +の `view(15)` メソッドを呼びます。しかし、これは `/articles/foobar` のような URL からの +アクセスを防ぐわけではありません。もし、あなたが望むなら、いくつかのパラメーターを正規表現に従うように +制限することができます。 : + +``` php +// Using fluent interface +$routes->connect( + '/articles/{id}', + ['controller' => 'Articles', 'action' => 'view'], +) +->setPatterns(['id' => '\d+']) +->setPass(['id']); + +// Using options array +$routes->connect( + '/articles/{id}', + ['controller' => 'Articles', 'action' => 'view'], + ['id' => '\d+', 'pass' => ['id']] +); +``` + +上の例はスターマッチャーを新たにプレースホルダー `:id` に変更しました。 +プレースホルダーを使うことで、URL 部分のバリデーションができます。 +このケースでは `\d+` という正規表現を使いました。なので、数字のみが一致します。 +最後に、 Router に `id` プレースホルダーを `view()` 関数の引数として渡すように +`pass` オプションで伝えます。このオプションの詳細は後で説明します。 + +CakePHP の Router はルートを逆にできます。それは、一致するパラメーターを含む配列から、 +URL 文字列を生成できることを意味します。 : + +``` php +use Cake\Routing\Router; + +echo Router::url(['controller' => 'Articles', 'action' => 'view', 'id' => 15]); +// 出力結果 +/articles/15 +``` + +ルートは一意の名前を付けることもできます。これは、リンクを構築する際に、 +ルーティングパラメーターをそれぞれ指定する代わりに、ルートを素早く参照することができます。 : + +``` php +// routes.php の中で +$routes->connect( + '/login', + ['controller' => 'Users', 'action' => 'login'], + ['_name' => 'login'] +); + +use Cake\Routing\Router; + +echo Router::url(['_name' => 'login']); +// 出力結果 +/login +``` + +ルーティングコードを DRY に保つために、Router は 'スコープ' (scopes) というコンセプトを +持っています。スコープは一般的なパスセグメントを定義し、オプションとして、デフォルトに +ルーティングします。すべてのスコープの内側に接続されているルートは、ラップしているスコープの +パスとデフォルトを継承します。 : + +``` php +$routes->scope('/blog', ['plugin' => 'Blog'], function (RouteBuilder $routes) { + $routes->connect('/', ['controller' => 'Articles']); +}); +``` + +上記のルートは `/blog/` と一致し、それを +`Blog\Controller\ArticlesController::index()` に送ります。 + +アプリケーションの雛形は、いくつかのルートをはじめから持った状態で作られます。 +一度自分でルートを追加したら、デフォルトルートが必要ない場合は除去できます。 + +
    + +{controller}, {action}, {plugin} + +
    + +
    + +greedy star, trailing star + +
    + + + +## ルートを接続 + +コードを `DRY` に保つために 'ルーティングスコープ' を使用してください。 +ルーティングスコープはコードを DRY に保つためだけではなく、Router の操作を最適化します。 +このメソッドは `/` スコープがデフォルトです。スコープを作成しいくつかのルートを +接続するために、 `scope()` メソッドを使います。 : + +``` php +// config/routes.php 内で、 +use Cake\Routing\Route\DashedRoute; + +$routes->scope('/', function (RouteBuilder $routes) { + // 標準のフォールバックルートを接続します。 + $routes->fallbacks(DashedRoute::class); +}); +``` + +`connect()` メソッドは3つのパラメーターを持ちます。あなたが一致させたい URL テンプレート、 +ルート要素のためのデフォルト値、そしてルートのためのオプションの3つです。 +しばしば、オプションには、ルーターが URL の要素を一致することに役立つ +正規表現ルールが含まれます。 + +ルートを定義するための基本のフォーマットは、次の通りです。 : + +``` php +$routes->connect( + '/url/template', + ['targetKey' => 'targetValue'], + ['option' => 'matchingRegex'] +); +``` + +1番目のパラメーターは、Router にどの URL を制御しようとしているのか伝えます。この URL は +普通のスラッシュで区切られた文字列ですが、ワイルドカード (\*) や [Route Elements](#route-elements) +を含むことができます。ワイルドカードは、すべての引数を受け付けることを意味します。 +\* なしだと、文字列に完全一致するものだけに絞られます。 + +URL が特定されたら、一致したときにどのような動作をするかを CakePHP に伝えるために +`connect()` の残り2つのパラメーターを使います。2番目のパラメーターは、ルートの +'ターゲット' を定義します。 これは、配列または行先の文字列として定義できます。 +ルートターゲットの例をいくつか示します。 : + +``` php +// アプリケーションのコントローラーへの配列ターゲット +$routes->connect( + '/users/view/*', + ['controller' => 'Users', 'action' => 'view'] +); +$routes->connect('/users/view/*', 'Users::view'); + +// プレフィックス付きのプラグインコントローラーへの配列ターゲット +$routes->connect( + '/admin/cms/articles', + ['prefix' => 'Admin', 'plugin' => 'Cms', 'controller' => 'Articles', 'action' => 'index'] +); +$routes->connect('/admin/cms/articles', 'Cms.Admin/Articles::index'); +``` + +接続する最初のルートは、 `/users/view` から始まる URL と一致し、 +それらのリクエストを `UsersController->view()` にマップします。 +末尾の `/*` は、メソッド引数として追加の部分を渡すようにルーターに指示します。 +例えば、 `/users/view/123` は、 `UsersController->view(123)` にマップされます。 + +上記は文字列ターゲットの例を示しています。文字列ターゲットは、 +ルートの宛先をコンパクトに定義する方法を提供します。 +文字列ターゲットの構文は次のとおりです。 : + +``` text +[Plugin].[Prefix]/[Controller]::[action] +``` + +いくつかの文字列ターゲットの例です。 : + +``` text +// アプリケーションのコントローラー +'Bookmarks::view' + +// プレフィックス付きのアプリケーションコントローラー +Admin/Bookmarks::view + +// プラグインのコントローラー +Cms.Articles::edit + +// プレフィックス付きのプラグインコントローラー +Vendor/Cms.Management/Admin/Articles::view +``` + +先ほど、パスの追加の部分を取り込むために貪欲なスター (greedy star) `/*` を使用していましたが、 +`/**` 流れ星 (trailing star) もあります。2つのアスタリスクをつなげると、 +URL の残りを1つの引数として取り込みます。これは、 `/` を含む引数を使用したい時に便利です。 : + +``` php +$routes->connect( + '/pages/**', + ['controller' => 'Pages', 'action' => 'show'] +); +``` + +`/pages/the-example-/-and-proof` が URL として渡ってきたときに、 +`the-example-/-and-proof` を引数として渡せます。 + +`connect()` の2番目のパラメーターは、 +デフォルトのルートパラメーターを構成する任意のパラメーターを定義できます。 : + +``` php +$routes->connect( + '/government', + ['controller' => 'Pages', 'action' => 'display', 5] +); +``` + +この例では、 `connect()` の2番目のパラメーターを使用してデフォルトパラメーターを定義しています。 +もし、いろいろなカテゴリーの製品を顧客に対して提供するアプリケーションを作るのであれば、 +ルーティングすることを考えるべきです。上記は、 `/pages/display/5` ではなく `/government` +にリンクすることができます。 + +ルーティングの一般的な用途は、コントローラーとそのアクションの名前を変更することです。 +`/users/some_action/5` で users コントローラーにアクセスするのではなく、 +`/cooks/some_action/5` を使ってアクセスすることができます。 +次のルートでそれが処理できます。 : + +``` php +$routes->connect( + '/cooks/{action}/*', ['controller' => 'Users'] +); +``` + +これは Router に `/cooks/` で始まるすべての URL は `UsersController` に送るように +伝えています。 アクションは `:action` パラメーターの値によって呼ばれるかどうか決まります。 +[Route Elements](#route-elements) を使って、ユーザーの入力や変数を受け付けるいろいろなルーティングが +できます。上記のルーティングの方法は、貪欲なスター (greedy star) を使います。 +貪欲なスターは `Router` がすべての位置指定引数を受け取ることを意味します。 +それらの引数は [Passed Arguments](#passed-arguments) 配列で有効化されます。 + +URL を生成するときにもルーティングは使われます。もし最初に一致するものがあった場合、 +`['controller' => 'users', 'action' => 'some_action', 5]` を使って +`/cooks/some_action/5` と出力します。 + +これまでに接続したルートは、任意の HTTP 動詞と一致します。REST API を構築している際、 +HTTP アクションを異なるコントローラーメソッドにマップすることがよくあります。 +`RouteBuilder` はヘルパーメソッドを提供し、特定の HTTP 動詞のルートをより簡単に定義します。 : + +``` php +// GET リクエストへのみ応答するルートの作成 +$routes->get( + '/cooks/{id}', + ['controller' => 'Users', 'action' => 'view'], + 'users:view' +); + +// PUT リクエストへのみ応答するルートの作成 +$routes->put( + '/cooks/{id}', + ['controller' => 'Users', 'action' => 'update'], + 'users:update' +); +``` + +上記のルートは、使用される HTTP 動詞に基づいて、同じ URL を異なるコントローラーアクションに +マップします。GET リクエストは 'view' アクションに行き、PUT リクエストは 'update' アクションに +行きます。次の HTTP ヘルパーメソッドがあります。 + +- GET +- POST +- PUT +- PATCH +- DELETE +- OPTIONS +- HEAD + +これらのメソッドはすべてルートインスタンスを返すので、 [流暢なセッター](#route-fluent-methods) を活用してルートをさらに設定することができます。 + +### ルート要素 + +あなたは独自のルート要素を指定し、コントローラーのアクションのパラメーターを +URL のどこに配置すべきなのかを定義することができます。リクエストされたとき、 +これらのルート要素の値は、コントローラーの `$this->request->getParam()` から取得できます。 +カスタムルート要素を定義した場合、正規表現をオプションで指定できます。 +これは CakePHP にどんな URL が正しいフォーマットなのかを伝えます。 +正規表現を使用しなかった場合、 `/` 以外の文字はパラメーターの一部として扱われます。 : + +``` php +$routes->connect( + '/{controller}/{id}', + ['action' => 'view'] +)->setPatterns(['id' => '[0-9]+']); +``` + +上記の例は、 `/コントローラー名/{id}` のような形の URL で、任意のコントローラーの +モデルを表示するための、素早く作成する方法を示しています。 `connect()` に渡した URL は +`:controller` と `:id` という2つのルート要素を指定します。この `:controller` 要素は +CakePHP のデフォルトルート要素であるため、ルーターが URL のコントローラー名をどのように照合し識別するかを +知っています。 `:id` 要素はカスタムルート要素で、 `connect()` の3番目のパラメーターに +一致する正規表現を指定することで、より明確にする必要があります。 + +CakePHP は小文字とダッシュによって表された URL を `:controller` を使った時には出力しません。 +これを出力したい場合、上記の例を次のように書きなおしてください。 : + +``` php +use Cake\Routing\Route\DashedRoute; + +// 異なるルートクラスを持つビルダーを作成します。 +$routes->scope('/', function ($routes) { + $routes->setRouteClass(DashedRoute::class); + $routes->connect('/{controller}/{id}', ['action' => 'view']) + ->setPatterns(['id' => '[0-9]+']); +}); +``` + +`DashedRoute` クラス `:controller` を確認し、 +`:plugin` パラメーターを正しく小文字とダッシュによって表します。 + +> [!NOTE] +> ルート要素で使用する正規表現パターンはキャプチャーグループを含んではいけません。 +> もし含んでいると、Router は正しく機能しません。 + +一度、ルートが定義されたら、 `/apples/5` を呼ぶと +ApplesController の `view()` メソッドを呼びます。 `view()` メソッドの中で、 +`$this->request->getParam('id')` で渡された ID にアクセスする必要があります。 + +アプリケーションの中に一つのコントローラーがあり、URL にコントローラー名を表示されないようにするには、 +全ての URL をコントローラーのアクションにマップすることができます。 +たとえば、 `home` コントローラーのアクションにすべての URL をマップするために、 +`/home/demo` の代わりに `/demo` という URL を持つとすると、次のようにできます。 : + +``` php +$routes->connect('/{action}', ['controller' => 'Home']); +``` + +もし、大文字小文字を区別しない URL を提供したい場合、正規表現インライン修飾子を使います。 : + +``` php +$routes->connect( + '/{userShortcut}', + ['controller' => 'Teachers', 'action' => 'profile', 1], +)->setPatterns(['userShortcut' => '(?i:principal)']); +``` + +もう一つ例を挙げます。これであなたはルーティングのプロです。 : + +``` php +$routes->connect( + '/{controller}/{year}/{month}/{day}', + ['action' => 'index'] +)->setPatterns([ + 'year' => '[12][0-9]{3}', + 'month' => '0[1-9]|1[012]', + 'day' => '0[1-9]|[12][0-9]|3[01]' +]); +``` + +これは、いっそう複雑になりますが、ルーティングがとても強力になったことを示しています。 +この URL は4つのルート要素を持っています。1番目は、なじみがあります。デフォルトのルート要素で +CakePHP にコントローラー名が必要なことを伝えています。 + +次に、デフォルト値を指定します。コントローラーにかかわらず `index()` アクションを呼び出す必要があります。 + +最後に、数字形式の年と月と日に一致する正規表現を指定しています。正規表現の中で、丸括弧(グループ化)は +サポートされていないことに注意してください。上記のように、代わりのものを指定できますが、 +丸括弧によるグループ化はできません。 + +一度定義されると、このルートは `/articles/2007/02/01` , `/articles/2004/11/16` +にマッチし、 `$this->request->getParam()` の中の日付パラメーターを伴って +それぞれのコントローラーの `index()` アクションにリクエストを渡します。 + +### 予約済みルート要素 + +CakePHP には、いくつかの特別な意味を持つルート要素があります。 +そして、特別な意味を持たせたくないなら、使わないでください。 + +- `controller` ルートのためのコントローラー名に使います。 +- `action` ルートのためのコントローラーアクション名に使います。 +- `plugin` コントローラーが配置されているプラグイン名に使います。 +- `prefix` [Prefix Routing](#prefix-routing) のために使います。 +- `_ext` [ファイル拡張子ルーティング](#file-extensions) のために使います。 +- `_base` 生成された URL からベースパスを削除するには `false` をセットしてください。 + アプリケーションがルートディレクトリーにない場合、 'cake relative' な URL の生成に使えます。 +- `_scheme` webcalftp のように異なるスキーマのリンクを作成するために設定します。 + 現在のスキーマがデフォルトに設定されています。 +- `_host` リンクで使用するホストを設定します。デフォルトは、現在のホストです。 +- `_port` 非標準のポートにリンクを作成するときにポートを設定します。 +- `_full` `true` にすると FULL_BASE_URL 定数が + 生成された URL の前に加えられます。 +- `#` URL のハッシュフラグメントを設定できます。 +- `_https` `true` にすると生成された URL を https に変換します。 + `false` にすると、強制的に http になります。 +- `_method` HTTP 動詞/メソッドを使うために定義します。 + [Resource Routes](#resource-routes) と一緒に使うときに役に立ちます。 +- `_name` ルートの名前。名前付きルートをセットアップするときに、 + それを指定するためのキーとして使えます。 + +### ルートオプションの設定 + +各ルートに設定できる多くのルートオプションがあります。ルートを接続したら、 +その流れるようなビルダーメソッドを使用してルートをさらに設定できます。 +これらのメソッドは、 `connect()` の `$options` パラメーターの多くのキーを置き換えます。 : + +``` php +$routes->connect( + '/{lang}/articles/{slug}', + ['controller' => 'Articles', 'action' => 'view'] +) +// GET と POST リクエストを許可 +->setMethods(['GET', 'POST']) + +// blog サブドメインにのみ一致 +->setHost('blog.example.com') + +// 渡された引数に変換されるルート要素を設定 +->setPass(['slug']) + +// ルート要素の一致するパターンを設定 +->setPatterns([ + 'slug' => '[a-z0-9-_]+', + 'lang' => 'en|fr|es', +]) + +// JSON ファイル拡張子も許可 +->setExtensions(['json']) + +// lang を永続的なパラメーターに設定 +->setPersist(['lang']); +``` + +### アクションへのパラメーター渡し + +[Route Elements](#route-elements) を使ってルートを接続している時に、ルート要素で +引数を渡したい時があると思います。 `pass` オプションはルート要素が +コントローラーの関数に引数を渡せるようにするためのホワイトリストです。 : + +``` php +// src/Controller/BlogsController.php +public function view($articleId = null, $slug = null) +{ + // いくつかのコードがここに... +} + +// routes.php +$routes->scope('/', function (RouteBuilder $routes) { + $routes->connect( + '/blog/{id}-{slug}', // 例えば /blog/3-CakePHP_Rocks + ['controller' => 'Blogs', 'action' => 'view'] + ) + // 関数に引数を渡すためのルーティングテンプレートの中で、ルート要素を定義します。 + // テンプレートの中で、ルート要素を定義します。 + // ":id" をアクション内の $articleId にマップします。 + ->setPass(['id', 'slug']) + // `id` が一致するパターンを定義します。 + ->setPatterns([ + 'id' => '[0-9]+', + ]); +}); +``` + +今、リバースルーティング機能のおかげで、下記のように URL 配列を渡し、 +CakePHP はルートに定義された URL をどのように整えるのかを知ることができます。 : + +``` php +// view.php +// これは /blog/3-CakePHP_Rocks へのリンクを返します +echo $this->Html->link('CakePHP Rocks', [ + 'controller' => 'Blog', + 'action' => 'view', + 'id' => 3, + 'slug' => 'CakePHP_Rocks' +]); + +// 数字のインデックスを持つパラメーターを使えます。 +echo $this->Html->link('CakePHP Rocks', [ + 'controller' => 'Blog', + 'action' => 'view', + 3, + 'CakePHP_Rocks' +]); +``` + +### 名前付きルートの使用 + +時々、ルートのためにすべての URL パラメーターを記述することがとても煩雑で、 +名前付きルートでパフォーマンスを上げたいと思うようになるでしょう。 +ルートを接続するときに、 `_name` オプションを指定できます。このプションは、 +あなたが使いたいルートを識別するために、リバースルーティングで使われます。 : + +``` php +// 名前付きでルートを接続 +$routes->connect( + '/login', + ['controller' => 'Users', 'action' => 'login'], + ['_name' => 'login'] +); + +// HTTP メソッド指定でルートを命名 +$routes->post( + '/logout', + ['controller' => 'Users', 'action' => 'logout'], + 'logout' +); + +// 名前付きルートで URL の生成 +$url = Router::url(['_name' => 'logout']); + +// クエリー文字列引数付きの +// 名前付きルートで URL の生成 +$url = Router::url(['_name' => 'login', 'username' => 'jimmy']); +``` + +あなたのルートテンプレートに `:controller` のようなルート要素が含まれている場合、 +`Router::url()` にオプションの一部としてそれらを提供したい場合があると思います。 + +> [!NOTE] +> ルート名はアプリケーション全体で一意でなければなりません。 +> 違うルーティングスコープであっても、同じ `_name` を二度使えません。 + +名前付きルートを構築する時、ルート名にいくつかの命名規則を適用したいでしょう。 +CakePHP は、各スコープで名前のプレフィックスを定義することで、 +より簡単にルート名を構築できます。 : + +``` php +$routes->scope('/api', ['_namePrefix' => 'api:'], function (RouteBuilder $routes) { + // このルートの名前は `api:ping` になります。 + $routes->get('/ping', ['controller' => 'Pings'], 'ping'); +}); +// ping ルートのための URL を生成 +Router::url(['_name' => 'api:ping']); + +// plugin() で namePrefix を使用 +$routes->plugin('Contacts', ['_namePrefix' => 'contacts:'], function (RouteBuilder $routes) { + // ルートを接続。 +}); + +// または、 prefix() で +$routes->prefix('Admin', ['_namePrefix' => 'admin:'], function (RouteBuilder $routes) { + // ルートを接続。 +}); +``` + +`_namePrefix` オプションはネストしたスコープの中でも使えます。 +それは、あなたの期待通りに動きます。 : + +``` php +$routes->plugin('Contacts', ['_namePrefix' => 'contacts:'], function (RouteBuilder $routes) { + $routes->scope('/api', ['_namePrefix' => 'api:'], function ($routes) { + // このルートの名前は `contacts:api:ping` になります。 + $routes->get('/ping', ['controller' => 'Pings'], 'ping'); + }); +}); + +// ping ルートのための URL を生成 +Router::url(['_name' => 'contacts:api:ping']); +``` + +名前付きスコープに接続されているルートは命名されていているルートのみ追加されます。 +名前なしルートはそれらに適用される `_namePrefix` がありません。 + +
    + +admin routing, prefix routing + +
    + +### プレフィックスルーティング + +`static` Cake\\Routing\\RouterBuilder::**prefix**($name, $callback) + +多くのアプリケーションは特権を持ったユーザーが変更を加えるための管理者領域が必要です。 +これはしばしば、特別な `/admin/users/edit/5` のような URL を通してなされます。 +CakePHP では、プレフィックスルーティングは, `prefix` スコープメソッドによって +有効化されます。 : + +``` php +use Cake\Routing\Route\DashedRoute; + +$routes->prefix('Admin', function (RouteBuilder $routes) { + // ここのすべてのルートには、 `/admin` というプレフィックスが付きます。 + // また、 `'prefix' => 'Admin'` ルート要素が追加されます。 + // これは、これらのルートのURLを生成するときに必要になります + $routes->fallbacks(DashedRoute::class); +}); +``` + +プレフィックスは `Controller` 名前空間に属するようにマップされます。コントローラーと +分離してプレフィックスすることによって、小さくて単純なコントローラーをもつことができます。 +プレフィックス付き及び付かないコントローラーに共通の動作は、継承や +[コンポーネント](../controllers/components) やトレイトを使用してカプセル化できます。 +このユーザーの例を使うと、 `/admin/users/edit/5` にアクセスしたとき、 +**src/Controller/Admin/UsersController.php** の `edit()` メソッドを +5 を1番目のパラメーターとして渡しながら呼びます。 +ビューファイルは、 **templates/Admin/Users/edit.php** が使われます。 + +/admin へのアクセスを pages コントローラーの `index()` アクションに +以下のルートを使ってマップします。 : + +``` php +$routes->prefix('Admin', function (RouteBuilder $routes) { + // admin スコープの中なので、/admin プレフィックスや、 + // admin ルート要素を含める必要はありません。 + $routes->connect('/', ['controller' => 'Pages', 'action' => 'index']); +}); +``` + +プレフィックスルートを作成するときに、 `$options` 引数で、追加のルートのパラメーターを +設定できます。 : + +``` php +$routes->prefix('Admin', ['param' => 'value'], function (RouteBuilder $routes) { + // ここで接続されているルートは '/admin' でプレフィックスされており、 + // 'param' ルーティングキーを持っています。 + $routes->connect('/{controller}'); +}); +``` + +マルチワードのプレフィックスはデフォルトでダッシュの屈折を使用して変換されます。つまり、 +`MyPrefix` はURLの `my-prefix` にマッピングされます。 +アンダースコアなどの別の形式を使用する場合は、このようなプレフィックスのパスを必ず設定してください: + +``` php +$routes->prefix('MyPrefix', ['path' => '/my_prefix'], function (RouteBuilder $routes) { + // ここに接続されているルートには、 ``/my_prefix`` というプレフィックスが付きます + $routes->connect('/{controller}'); +}); +``` + +このようにプラグインスコープの中で、プレフィックスを定義できます。 : + +``` php +$routes->plugin('DebugKit', function (RouteBuilder $routes) { + $routes->prefix('Admin', function ($routes) { + $routes->connect('/{controller}'); + }); +}); +``` + +上記は `/debug_kit/admin/{controller}` のようなルートテンプレートを作ります。 +接続されたルートは、 `plugin` と `prefix` というルート要素を持ちます。 + +プレフィックスを定義したときに、必要ならば複数のプレフィックスをネストできます。 : + +``` php +$routes->prefix('Manager', function (RouteBuilder $routes) { + $routes->prefix('Admin', function ($routes) { + $routes->connect('/{controller}/{action}'); + }); +}); +``` + +上記のコードは、 `/manager/admin/{controller}/{action}` のようなルートテンプレートを生成します。 +接続されたルートは `prefix` というルート要素を `Manager/Admin` に設定します。 + +現在のプレフィックスはコントローラーのメソッドから `$this->request->getParam('prefix')` +を通して利用可能です。 + +プレフィックスルートを使用する場合、 `prefix` オプションを設定し、 `prefix()` +メソッドで使用されるのと同じキャメルケース形式を使用することが重要です。 +以下は、リンクを HTML ヘルパーで作る方法です。: + +``` php +// プレフィックスルーティングする +echo $this->Html->link( + 'Manage articles', + ['prefix' => 'Manager/Admin', 'controller' => 'Articles', 'action' => 'add'] +); + +// プレフィックスルーティングをやめる +echo $this->Html->link( + 'View Post', + ['prefix' => false, 'controller' => 'Articles', 'action' => 'view', 5] +); +``` + +> [!NOTE] +> フォールバックルートを接続する *前* にプレフィックスルートを接続してください。 + +
    + +plugin routing + +
    + +### プレフィックスルートへのリンクの作成 + +プレフィックスキーをURL配列に追加することで、プレフィックスを指すリンクを作成できます: + +``` php +echo $this->Html->link( + 'New admin todo', + ['prefix' => 'Admin', 'controller' => 'TodoItems', 'action' => 'create'] +); +``` + +ネストを使用するときは、それらを連結する必要があります: + +``` php +echo $this->Html->link( + 'New todo', + ['prefix' => 'Admin/MyPrefix', 'controller' => 'TodoItems', 'action' => 'create'] +); +``` + +これは、名前空間 `` `App\Controller\Admin\MyPrefix `` およびファイルパス +`src/Controller/Admin/MyPrefix/TodoItemsController.php` を持つコントローラーにリンクします。 + +> [!NOTE] +> ルーティング結果が破線であっても、プレフィックスは常にここで大文字に変換されます。 +> 必要に応じて、ルート自体が屈折を行います。 + +### プラグインのルーティング + +`static` Cake\\Routing\\RouterBuilder::**plugin**($name, $options = [], $callback) + +[プラグイン](../plugins) のためのルートは `plugin()` メソッドを使って作成してください。 +このメソッドは、プラグインのルートのための新しいルーティングスコープを作成します。 : + +``` php +$routes->plugin('DebugKit', function (RouteBuilder $routes) { + // ここに接続されるルートは '/debug_kit' というプレフィックスが付き、 + // このプラグインのルート要素には 'DebugKit' がセットされています。 + $routes->connect('/{controller}'); +}); +``` + +プラグインスコープを作るときに、 `path` オプションでパス要素をカスタマイズできます。 : + +``` php +$routes->plugin('DebugKit', ['path' => '/debugger'], function (RouteBuilder $routes) { + // ここに接続されるルートは '/debugger' というプレフィックスが付き、 + // このプラグインのルート要素には 'DebugKit' がセットされています。 + $routes->connect('/{controller}'); +}); +``` + +スコープを使うときに、プレフィックススコープ内でプラグインスコープをネストできます。 : + +``` php +$routes->prefix('Admin', function (RouteBuilder $routes) { + $routes->plugin('DebugKit', function ($routes) { + $routes->connect('/{controller}'); + }); +}); +``` + +上記は、 `/admin/debug_kit/{controller}` のようなルートを作成します。 +これは `prefix` と `plugin` のルート要素の組み合わせになります。 +プラグインルートの構築について、詳しくは [Plugin Routes](../plugins#plugin-routes) にあります。 + +### プラグインルートへのリンクの作成 + +上記は、 `/admin/debug_kit/{controller}` のようなルーティングを作ります。 +これは、 `prefix` と `plugin` をルート要素として持ちます。 + +URL 配列に plugin キーを追加することによって、プラグインを指すリンクを作成できます。 : + +``` php +echo $this->Html->link( + 'New todo', + ['plugin' => 'Todo', 'controller' => 'TodoItems', 'action' => 'create'] +); +``` + +逆に、現在のリクエストがプラグインに対してのリクエストであり、 +プラグインではないリンクを作成する場合、次のようにできます。 : + +``` php +echo $this->Html->link( + 'New todo', + ['plugin' => null, 'controller' => 'Users', 'action' => 'profile'] +); +``` + +`plugin => null` を設定することで、プラグインの一部ではないリンクを作成したいことを +Router に伝えられます。 + +### SEO に親和性があるルーティング + +良い検索エンジンのランキングを得るために、URL の中でダッシュを使うのを好む開発者もいるでしょう。 +`DashedRoute` クラスは、アプリケーションの中で、プラグイン、コントローラー、および +キャメルケースで書かれたアクション名をダッシュ記法の URL にルーティングすることができます。 + +例えば、 `TodoItems` コントローラーの `showItems()` アクションを持つ +`ToDo` プラグインを使っていたとして、 `/to-do/todo-items/show-items` +でアクセスできるように、以下のルーター接続で可能になります。 : + +``` php +$routes->plugin('ToDo', ['path' => 'to-do'], function (RouteBuilder $routes) { + $routes->fallbacks('DashedRoute'); +}); +``` + +### 指定した HTTP メソッドとの照合 + +ルートは、HTTP 動詞へルパーを使用して指定した HTTP メソッドとマッチできます。 : + +``` php +$routes->scope('/', function (RouteBuilder $routes) { + // このルートは POST リクエスト上でのみマッチします。 + $routes->post( + '/reviews/start', + ['controller' => 'Reviews', 'action' => 'start'] + ); + + // 複数 HTTP メソッドとマッチします + $routes->connect( + '/reviews/start', + [ + 'controller' => 'Reviews', + 'action' => 'start', + ] + )->setMethods(['POST', 'PUT']); +}); +``` + +配列を使うことで複数の HTTP メソッドとマッチできます。 `_method` パラメーターは +ルーティングキーなので、 URL の解析と URL の生成の両方に使われます。 +メソッド固有のルートの URL を生成するには、URL を生成する際に +`_method` キーを含める必要があります。 : + +``` php +$url = Router::url([ + 'controller' => 'Reviews', + 'action' => 'start', + '_method' => 'POST', +]); +``` + +### 指定したホスト名との照合 + +ルートは、指定のホストのみとマッチするように `_host` オプションを使用できます。 +任意のサブドメインとマッチするために `*.` ワイルドカードを使用できます。 : + +``` php +$routes->scope('/', function (RouteBuilder $routes) { + // このルートは http://images.example.com のみマッチします。 + $routes->connect( + '/images/default-logo.png', + ['controller' => 'Images', 'action' => 'default'] + )->setHost('images.example.com'); + + // このルートは http://*.example.com のみマッチします。 + $routes->connect( + '/images/old-logo.png', + ['controller' => 'Images', 'action' => 'oldLogo'] + )->setHost('*.example.com'); +}); +``` + +`_host` オプションは URL 生成でも使用されます。 `_host` オプションで正確なドメインを +指定する場合、そのドメインは生成された URL に含まれます。しかし、もしワイルドカードを +使用する場合、URL の生成時に `_host` パラメーターを指定する必要があります。 : + +``` php +// このルートを持つ場合、 +$routes->connect( + '/images/old-logo.png', + ['controller' => 'Images', 'action' => 'oldLogo'] +)->setHost('images.example.com'); + +// url を生成するために指定が必要です。 +echo Router::url([ + 'controller' => 'Images', + 'action' => 'oldLogo', + '_host' => 'images.example.com', +]); +``` + +
    + +file extensions + +
    + +### ファイル拡張子のルーティング + +`static` Cake\\Routing\\RouterBuilder::**extensions**(stringnull $extensions, $merge = true) + +異なるファイルの拡張子をルートで処理するために、スコープレベルだけでなくグローバルでも +拡張子を定義できます。グローバルな拡張子を定義するには、スタティックな +`Router::extensions()` メソッドを介して保存できます。 : + +``` php +Router::extensions(['json', 'xml']); +// ... +``` + +これは、スコープに関係なく、 **以後に** 接続された **全て** のルートに影響します。 + +拡張子を特定のスコープに制限するために、 `Cake\Routing\RouteBuilder::setExtensions()` +メソッドを使用して定義することができます。 : + +``` php +$routes->scope('/', function (RouteBuilder $routes) { + $routes->setExtensions(['json', 'xml']); +}); +``` + +これは、 `setExtensions()` が呼ばれた **後の** スコープの中で接続されている +全てのルートのために名前付き拡張子を有効にします。それは、ネストされたスコープの中で +接続されているルートも含まれます。グローバルの `Router::extensions()` メソッドと +同様に、呼び出し前に接続されたルートは、拡張子を継承しません。 + +> [!NOTE] +> 拡張子がセットされた **後** でのみ拡張子がルーティングに適用されるので、 +> 拡張子を設定することはスコープの中で一番最初にやるべきことです。 +> +> また、再度開かれたスコープは、前回開いたスコープで定義した拡張子を **継承しない** ことにも +> 注意してください。 + +拡張子を使うことで、一致したファイルの拡張子を除去し、残りをパースするように +伝えられます。もし /page/title-of-page.html のような URL を生成したいなら、 +以下を使ってルートを設定します。 : + +``` php +$routes->scope('/page', function (RouteBuilder $routes) { + $routes->setExtensions(['json', 'xml', 'html']); + $routes->connect( + '/{title}', + ['controller' => 'Pages', 'action' => 'view'] + )->setPass(['title']); +}); +``` + +そして、ルートに対応するリンクを作成するために、次のようにします。 : + +``` php +$this->Html->link( + 'Link title', + ['controller' => 'Pages', 'action' => 'view', 'title' => 'super-article', '_ext' => 'html'] +); +``` + +拡張子が [リクエストハンドリング](../controllers/components/request-handling) で使われ、それによって +コンテンツタイプに合わせた自動的なビューの切り替えを行います。 + +### スコープ付きミドルウェアの接続 + +ミドルウェアをアプリケーション全体に適用することができますが、特定のルーティングスコープに +ミドルウェアを適用すると、ミドルウェアが必要な場所にのみ適用できるため、適用の方法や範囲の +配慮がいらないミドルウェアにすることができます。 + +> [!NOTE] +> 適用されるスコープ付きミドルウェアは [RoutingMiddleware](../controllers/middleware#routing-middleware) +> によって実行され、通常、アプリケーションのミドルウェアキューの最後に配置されます。 + +ミドルウェアをスコープに適用する前に、ルートコレクションに登録する必要があります。 : + +``` php +// config/routes.php の中で +use Cake\Http\Middleware\CsrfProtectionMiddleware; +use Cake\Http\Middleware\EncryptedCookieMiddleware; + +$routes->scope('/', function (RouteBuilder $routes) { + $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware()); + $routes->registerMiddleware('cookies', new EncryptedCookieMiddleware()); +}); +``` + +一度登録されると、スコープ付きミドルウェアは特定のスコープに適用されます。 : + +``` php +$routes->scope('/cms', function (RouteBuilder $routes) { + // CSRF & cookies ミドルウェアを有効化 + $routes->applyMiddleware('csrf', 'cookies'); + $routes->get('/articles/{action}/*', ['controller' => 'Articles']); +}); +``` + +ネストされたスコープがある状況では、内部スコープは、 +スコープ内に適用されたミドルウェアを継承します。 : + +``` php +$routes->scope('/api', function (RouteBuilder $routes) { + $routes->applyMiddleware('ratelimit', 'auth.api'); + $routes->scope('/v1', function (RouteBuilder $routes) { + $routes->applyMiddleware('v1compat'); + // ここにルートを定義。 + }); +}); +``` + +上記の例では、 `/v1` で定義されたルートは 'ratelimit'、 'auth.api'、および 'v1compat' +ミドルウェアが適用されます。スコープを再度開くと、各スコープ内のルートに適用されたミドルウェアが +分離されます。 : + +``` php +$routes->scope('/blog', function (RouteBuilder $routes) { + $routes->applyMiddleware('auth'); + // ここに blog の認証が必要なアクションを接続 +}); +$routes->scope('/blog', function ($routes) { + // ここに blog の公開アクションを接続 +}); +``` + +上記の例では、 `/blog` スコープの2つの用途はミドルウェアを共有しません。 +ただし、これらのスコープは両方とも、そのスコープ内で定義されたミドルウェアを継承します。 + +### ミドルウェアのグループ化 + +ルートコードを `DRY (Do not Repeat Yourself)` に保つ助けになるよう、 +ミドルウェアをグループにまとめることができます。一度まとめられたグループは、 +ミドルウェアのように適用することができます。 : + +``` php +$routes->registerMiddleware('cookie', new EncryptedCookieMiddleware()); +$routes->registerMiddleware('auth', new AuthenticationMiddleware()); +$routes->registerMiddleware('csrf', new CsrfProtectionMiddleware()); +$routes->middlewareGroup('web', ['cookie', 'auth', 'csrf']); + +// グループの適用 +$routes->applyMiddleware('web'); +``` + +## RESTful なルーティング + +Router はコントローラーへの RESTful なルートを簡単に生成します。 +RESTful なルートはアプリケーションの API エンドポイントを作るときに有効です。 +recipe コントローラーに REST アクセスできるようにしたい場合、このようにします。 : + +``` php +// config/routes.php 内で... + +$routes->scope('/', function (RouteBuilder $routes) { + $routes->setExtensions(['json']); + $routes->resources('Recipes'); +}); +``` + +最初の行は、簡単に REST アクセス可能にするために、いくつかのデフォルトルートをセットしています。 +アクセス対象のメソッドには、最終的に受け取りたいフォーマット (例えば xml, json, rss) の指定が +必要です。これらのルートは、HTTP リクエストメソッドに対応しています。 + +| HTTP format | URL.format | 対応するコントローラーアクション | +|-------------|---------------------|----------------------------------| +| GET | /recipes.format | RecipesController::index() | +| GET | /recipes/123.format | RecipesController::view(123) | +| POST | /recipes.format | RecipesController::add() | +| PUT | /recipes/123.format | RecipesController::edit(123) | +| PATCH | /recipes/123.format | RecipesController::edit(123) | +| DELETE | /recipes/123.format | RecipesController::delete(123) | + +使用されている HTTP メソッドは、いくつかの異なるソースから検出されます。 +優先順位の順のソースは次のとおりです。 + +1. `_method` POST 変数 +2. `X_HTTP_METHOD_OVERRIDE` ヘッダー +3. `REQUEST_METHOD` ヘッダー + +`_method` POST 変数の値を使う方法は、ブラウザーを使った REST クライアントの場合 +(または POST でできる何か)に便利です。エミュレートしたい HTTP リクエストの名前を +`_method` の値にセットするだけです。 + +### ネストされたリソースのルートを作成 + +スコープの中で一度リソースに接続すると、サブリソース (リソースの下層) にもルートを接続できます。 +サブリソースのルートは、オリジナルのリソース名と id パラメーターの後に追加されます。例えば: + +``` php +$routes->scope('/api', function (RouteBuilder $routes) { + $routes->resources('Articles', function (RouteBuilder $routes) { + $routes->resources('Comments'); + }); +}); +``` + +これで `articles` と `comments` 両方のリソースルートを生成します。 +この comments のルートは次のようになります。 : + +``` text +/api/articles/{article_id}/comments +/api/articles/{article_id}/comments/{id} +``` + +`CommentsController` の `article_id` を次のように取得できます。 : + +``` php +$this->request->getParam('article_id'); +``` + +デフォルトでは、リソースルートは、スコープに含まれる同じプレフィックスにマップします。 +もし、ネストしたリソースのコントローラーとそうでないリソースのコントローラー両方を持つ場合、 +プレフィックスを利用して各コンテキスト内で異なるコントローラーを使用できます。 : + +``` php +$routes->scope('/api', function (RouteBuilder $routes) { + $routes->resources('Articles', function (RouteBuilder $routes) { + $routes->resources('Comments', ['prefix' => 'Articles']); + }); +}); +``` + +上記は、「Comments」リソースを `App\Controller\Articles\CommentsController` に +マップします。コントローラーを分けることで、あなたのコントローラーのロジックをシンプルに保つことが +できます。このやり方で作成されたプレフィックスは、 [Prefix Routing](#prefix-routing) と互換性があります。 + +> [!NOTE] +> あなたが望む深さまでリソースをネストできますが、 +> 2段階以上の深さにネストさせることはお勧めしません。 + +### ルートの作成を制限 + +デフォルトでは CakePHP は、各リソースに対して6つのルートを接続します。 +特定のリソースに対して、特定のルートのみを接続させたい場合、 `only` +オプションを使います。 : + +``` php +$routes->resources('Articles', [ + 'only' => ['index', 'view'] +]); +``` + +これで読み込み専用のリソースルートが作られます。このルート名は `create`, +`update`, `view`, `index`, と `delete` です。 + +### 使用するコントローラーアクションの変更 + +ルートを接続するときに使われるコントローラーのアクション名を変更したい場合があるでしょう。 +例えば、 `edit()` アクションを `put()` で呼びたいときに、 +`actions` キーをアクション名のリネームに使います。 : + +``` php +$routes->resources('Articles', [ + 'actions' => ['update' => 'put', 'create' => 'add'] +]); +``` + +上記は `put()` を `edit()` アクションの代わりに使い、 `add()` +を `create()` の代わりに使います。 + +### 追加のリソースへのルートをマップする + +`map` オプションを使用して、追加のリソースメソッドをマップできます。 : + +``` php +$routes->resources('Articles', [ + 'map' => [ + 'deleteAll' => [ + 'action' => 'deleteAll', + 'method' => 'DELETE' + ] + ] +]); +// これは /articles/deleteAll へ接続します。 +``` + +デフォルトルートに加えて、これは /articles/delete_all へのルートに接続します。 +デフォルトでは、パスセグメントはキー名に一致します。'path' キーを、パスをカスタマイズするための +リソースの定義の中で使えます。 : + +``` php +$routes->resources('Articles', [ + 'map' => [ + 'updateAll' => [ + 'action' => 'updateAll', + 'method' => 'PUT', + 'path' => '/update-many' + ], + ] +]); +// これは /articles/update_many に接続します。 +``` + +'only' と 'map' を定義した場合、マップされたメソッドが 'only' リストにもあるか確かめましょう。. + +### プレフィックス付きのリソースルーティング + +リソースルートは、ルーティングプレフィックスでコントローラに接続できます。 +プレフィックス付きスコープ内で、または `prefix` オプションを使用してルートを接続します。: + +``` php +$routes->resources('Articles', [ + 'prefix' => 'Api', +]); +``` + +### リソースルートのためのカスタムルートクラス + +`resources()` の `$options` 配列の `connectOptions` キーで `connect()` +を使った設定ができます。 : + +``` php +$routes->scope('/', function (RouteBuilder $routes) { + $routes->resources('Books', [ + 'connectOptions' => [ + 'routeClass' => 'ApiRoute', + ] + ]; +}); +``` + +### リソースルートのための URL 語形変化 + +デフォルトで、複数語のコントローラーの URL フラグメントは、コントローラー名のアンダースコアー形式です。 +例えば、 `BlogPosts` の URL フラグメントは、 **/blog_posts** になります。 + +`inflect` オプションを使って別の変化形を指定できます。 : + +``` php +$routes->scope('/', function (RouteBuilder $routes) { + $routes->resources('BlogPosts', [ + 'inflect' => 'dasherize' // ``Inflector::dasherize()`` を使用 + ]); +}); +``` + +上記は、 **/blog-posts/\*** スタイルの URL を生成します。 + +> [!NOTE] +> CakePHP 3.1 から、公式の app スケルトンは、 `DashedRoute` をデフォルトルートクラス +> として使用します。 URL の一貫性を保つために、リソースルートを接続する際に +> `'inflect' => 'dasherize'` オプションを使用することを推奨します。 + +### パス要素の変更 + +デフォルトでは、リソースルートは、URL セグメントのリソース名の語形変化された形式を使用します。 +`path` オプションでカスタム URL セグメントを設定することができます。 : + +``` php +$routes->scope('/', function (RouteBuilder $routes) { + $routes->resources('BlogPosts', ['path' => 'posts']); +}); +``` + +
    + +passed arguments + +
    + +## 渡された引数 + +渡された引数は、リクエストされた際に使用される追加の引数、もしくはパスセグメントです。 +これらはしばしば、コントローラーメソッドにパラメーターを渡すために使われます。 : + + http://localhost/calendars/view/recent/mark + +上記の例では、 `recent` と `mark` の両方が `CalendarsController::view()` +に引数として渡されます。渡された引数は3つの方法でコントローラーに渡されます。 +1番目は、引数としてアクションを呼ばれたときに渡し、2番目は、 +`$this->request->getParam('pass')` で数字をインデックスとする配列で呼べるようになります。 +カスタムルートを使用するときに、渡された引数を呼ぶために特定のパラメーターを強制することができます。 + +前述の URL にアクセスして、次のようなコントローラーアクションがあるとすると、 : + +``` php +class CalendarsController extends AppController +{ + public function view($arg1, $arg2) + { + debug(func_get_args()); + } +} +``` + +次の出力を得ます。 : + +``` text +Array +( + [0] => recent + [1] => mark +) +``` + +コントローラーとビューとヘルパーの `$this->request->getParam('pass')` でも、 +これと同じデータが利用可能です。pass 配列中の値は、 +呼ばれた URL に現れる順番をもとにした数字のインデックスになります。 : + +``` php +debug($this->request->getParam('pass')); +``` + +上記の出力は以下になります。 : + +``` text +Array +( + [0] => recent + [1] => mark +) +``` + +`ルーティング配列` を使って URL を生成するとき、配列に文字列キーなしの値として +引数を加えます。 : + +``` php +['controller' => 'Articles', 'action' => 'view', 5] +``` + +`5` は引数として渡されるときには数字キーを持ちます。 + +## URL の生成 + +`static` Cake\\Routing\\RouterBuilder::**url**($url = null, $full = false) + +URL の生成やリバースルーティングは、すべてのコードの変更なしに URL の構造を簡単に変更する +CakePHP の機能です。URL を定義するために `ルーティング配列` を使用することで、 +あとで変更を加えても、生成された URL は自動的に更新されます。 + +次のように URL を文字列で作成し、 : + +``` php +$this->Html->link('View', '/articles/view/' . $id); +``` + +そして、あとで `/articles` は本当は 'posts' と呼ぶべきであると判断した場合、 +アプリケーション全体で URL を変更する必要があります。一方、次のように link を定義し、 : + +``` php +$this->Html->link( + 'View', + ['controller' => 'Articles', 'action' => 'view', $id] +); +``` + +そして、URL を変更すると判断した場合、ルートを定義することで変更できます。 +これは、受け取る URL のマッピングと生成される URL の両方を変更します。 + +URL 配列を使うとき、特別なキーを使用して、文字列パラメーターによるクエリーと +ドキュメントフラグメントを定義できます。 : + +``` php +$routes->url([ + 'controller' => 'Articles', + 'action' => 'index', + '?' => ['page' => 1], + '#' => 'top' +]); + +// このような URL が生成されます +/articles/index?page=1#top +``` + +Router はルーティング配列の中の未知のパラメーターをクエリー文字列パラメーターに変換します。 +`?` は CakePHP の古いバージョンとの後方互換性のために提供します。 + +URL を生成するときに、特別なルート要素が使用できます。 + +- `_ext` [File Extensions](#file-extensions) (拡張子)ルーティングに使います。 +- `_base` 生成された URL からベースパスを削除するには `false` をセットしてください。 + アプリケーションがルートディレクトリーにない場合、 'cake relative' な URL の生成に使います。 +- `_scheme` `webcal` や `ftp` のように異なるスキーマのリンクを作成するために設定します。 + 現在のスキーマにデフォルト設定されています。 +- `_host` リンクのためのホストを設定します。デフォルトは、現在のホストです。 +- `_port` 非標準なポートのリンクを作成するときにポートを設定します。 +- `_method` URL が存在する HTTP メソッドを定義します。 +- `_full` `true` にすると、 `FULL_BASE_URL` 定数 が + 生成された URL の前に加えられます。 +- `_https` `true` にすると普通の URL から https に変換します。 + `false` にすると、強制的に http になります。 +- `_name` ルートの名前。名前付きルートをセットアップするとき、それを指定するためのキーとして使います。 + +## リダイレクトルーティング + +リダイレクトルーティングは受け取るルートに対して HTTP ステータスの 30x リダイレクトを発行し、 +異なる URL に転送することができます。これは、リソースが移動し、同じ内容の 2 つの URL を +公開したくないことをクライアントアプリケーションに通知する場合に便利です。 + +リダイレクトルートは通常のルートとは異なり、条件に一致した場合、 +実際にヘッダーリダイレクトを実行します。 +リダイレクトは、アプリケーション内部や外部へ遷移します。 : + +``` php +$routes->scope('/', function (RouteBuilder $routes) { + $routes->redirect( + '/home/*', + ['controller' => 'Articles', 'action' => 'view'], + ['persist' => true] + // もしくは $id を引数として受け取る view アクションの + // デフォルトルーティングは ['persist'=>['id']] のようにする + ); +}); +``` + +`/home/*` から `/articles/view` へのリダイレクトし `/articles/view` にパラメーターを渡します。 +リダイレクト先として配列を使うことで、URL 文字列を定義するために、他のルートを使用できます。 +文字列 URL を遷移先として使用することで外部にリダイレクトできます。 : + +``` php +$routes->scope('/', function (RouteBuilder $routes) { + $routes->redirect('/articles/*', 'https://google.com', ['status' => 302]); +}); +``` + +これは、 `/articles/*` から `https://google.com` へ HTTP 302 ステータスのリダイレクトをします。 + +## エンティティールーティング + +エンティティールーティングでは、エンティティー、配列またはオブジェクトを使用して、 +`ArrayAccess` をルーティングパラメーターのソースとして実装できます。これにより、 +ルートをより簡単にリファクタリングし、少ないコードで URL を生成することができます。 +たとえば、次のようなルートで開始するとします。 : + +``` php +$routes->get( + '/view/{id}', + ['controller' => 'Articles', 'action' => 'view'], + 'articles:view' +); +``` + +次のようにして、このルートへの URL を生成できます。 : + +``` php +// $article は、ローカルスコープ内のエンティティーです。 +Router::url(['_name' => 'articles:view', 'id' => $article->id]); +``` + +後で、SEO 目的のために URL に記事のスラッグを付加したくなることもあるでしょう。これを行うには、 +`articles:view` ルートへの URL を生成しているところを全て更新する必要があります。 +これには時間がかかることがあります。エンティティールートを使用する場合、article エンティティー全体を +URL 生成に渡すことで、URL がより多くのパラメーターが必要となったとしても、 +すべての更新をスキップできます。 : + +``` php +use Cake\Routing\Route\EntityRoute; + +// このスコープの rest のためのエンティティールートを作成します。 +$routes->setRouteClass(EntityRoute::class); + +// 以前と同じようにルートを作成します。 +$routes->get( + '/view/{id}', + ['controller' => 'Articles', 'action' => 'view'], + 'articles:view' +); +``` + +これで、 `_entity` キーを使って URL を生成することができます。 : + +``` php +Router::url(['_name' => 'articles:view', '_entity' => $article]); +``` + +これは、提供されたエンティティーから `id` プロパティーと `slug` プロパティーの両方を抽出します。 + +## カスタムルートクラス + +カスタムルートクラスは、個々のルートがリクエストをパースし、リバースルーティングを扱えるようにします。 +Route クラスには、いくつかの規約があります。 + +- ルートクラスは、アプリケーションやプラグイン内にある `Routing\Route` 名前空間で + 見つけられるはずです。 +- ルートクラス `Cake\Routing\Route` を拡張します。 +- ルートクラスは `match()` と `parse()` の一方もしくは両方を使います。 + +`parse()` メソッドは、受け取った URL をパースするために使用されます。 +これは、コントローラーとアクションに分解されるリクエストパラメーターの配列を生成する必要があります。 +このメソッドは、一致しなかった時に `false` を返します。 + +`match()` メソッドは URL パラメーターの配列に一致するか確かめるために使い、 +URL を文字列で生成します。URL パラメーターがルートに一致しない時は、 `false` を返します。 + +カスタムルートクラスを `routeClass` オプションを使って設定することができます。 : + +``` php +$routes->connect( + '/{slug}', + ['controller' => 'Articles', 'action' => 'view'], + ['routeClass' => 'SlugRoute'] +); + +// また、スコープの中で routeClass を設定することもできます。 +$routes->scope('/', function ($routes) { + $routes->setRouteClass('SlugRoute'); + $routes->connect( + '/{slug}', + ['controller' => 'Articles', 'action' => 'view'] + ); +}); +``` + +このルートは `SlugRoute` のインスタンスを生成し、カスタムパラメーター処理を実装することができます。 +標準的な `プラグイン記法` を使ってプラグインルートクラスを使用できます。 + +### デフォルトルートクラス + +`static` Cake\\Routing\\RouterBuilder::**defaultRouteClass**($routeClass = null) + +デフォルト `Route` 以外のすべての他のルートクラスをデフォルトの基づくルートに +使いたいときには、 `Router::defaultRouteClass()` を呼ぶことによって すべてのルートを +セットアップする前に、それぞれのルートのための特定の `routeClass` オプションを持つことを +避けます。例えば、下記を使います。 : + +``` php +use Cake\Routing\Route\InflectedRoute; + +Router::defaultRouteClass(InflectedRoute::class); +``` + +この後に接続されたすべてのルートに `InflectedRoute` ルートクラスが使用されます。 +引数なしにこのメソッドを呼ぶと、現在のデフォルトルートクラスを返します。 + +### フォールバックメソッド + +`method` Cake\\Routing\\RouterBuilder::**fallbacks**($routeClass = null) + +fallbacks メソッドはデフォルトルートをショートカットする簡単な方法です。 +このメソッドは、渡されたルーティングクラスによって定義されたルールを使用します。もし、 +クラスが定義されていない場合、 `Router::defaultRouteClass()` で返されたクラスが使用されます。 + +フォールバックを次のように呼びます。 : + +``` php +use Cake\Routing\Route\DashedRoute; + +$routes->fallbacks(DashedRoute::class); +``` + +次の明示的な呼び出しと同等です。 : + +``` php +use Cake\Routing\Route\DashedRoute; + +$routes->connect('/{controller}', ['action' => 'index'], ['routeClass' => DashedRoute::class]); +$routes->connect('/{controller}/{action}/*', [], ['routeClass' => DashedRoute::class]); +``` + +> [!NOTE] +> フォールバックを使ったデフォルトルートクラス (`Route`) もしくは、 +> `:plugin` や `:controller` ルート要素をもつ任意のルートを使用すると +> URL の大文字小文字の区別がなくなります。 + +## 永続的な URL パラメーターの生成 + +URL フィルター関数で URL の生成プロセスをホックできます。 +フィルター関数は、 ルーティングに一致する *前* に呼ばれます。 +これは、ルーティングする前に URL を用意します。 + +コールバックフィルター関数は以下のパラメーターを持ちます。 + +- `$params` 生成されている URL パラメーター。 +- `$request` 現在のリクエスト + +URL フィルター関数は *常に* フィルターされていなくても、パラメーターを返します。 + +URL フィルターは永続的なパラメーターなどを簡単に扱う機能を提供します。 : + +``` php +Router::addUrlFilter(function ($params, $request) { + if ($request->getParam('lang') && !isset($params['lang'])) { + $params['lang'] = $request->getParam('lang'); + } + + return $params; +}); +``` + +フィルター関数は、それらが接続されている順番に適用されます。 + +別のユースケースでは、実行時に特定のルートを変更しています。 (プラグインルートの例) : + +``` php +Router::addUrlFilter(function ($params, $request) { + if (empty($params['plugin']) || $params['plugin'] !== 'MyPlugin' || empty($params['controller'])) { + return $params; + } + if ($params['controller'] === 'Languages' && $params['action'] === 'view') { + $params['controller'] = 'Locations'; + $params['action'] = 'index'; + $params['language'] = $params[0]; + unset($params[0]); + } + + return $params; +}); +``` + +これは以下のルートを : + +``` php +Router::url(['plugin' => 'MyPlugin', 'controller' => 'Languages', 'action' => 'view', 'es']); +``` + +このように置き換えます。 : + +``` php +Router::url(['plugin' => 'MyPlugin', 'controller' => 'Locations', 'action' => 'index', 'language' => 'es']); +``` + +> [!WARNING] +> もし [Routing Middleware](../controllers/middleware#routing-middleware) のキャッシュ機能を利用しているのなら、 +> フィルターはキャッシュされたデータの一部ではないため、 +> アプリケーションの `bootstrap()` でフィルター関数を定義しなければなりません。 + +## URL 内での名前付きパラメーターの扱い + +CakePHP 3.0 から名前付きパラメーターが削除されたとしても、 +アプリケーションは URL にそれを含んだ状態で公開されています。 +名前付きパラメーターを含んだ URL の受け付けを継続できます。 + +コントローラーの `beforeFilter()` メソッドで、 `parseNamedParams()` +呼ぶことで渡された引数のすべての名前付きパラメーターを展開できます。 : + +``` php +public function beforeFilter(Event $event) +{ + parent::beforeFilter($event); + Router::parseNamedParams($this->request); +} +``` + +これは、 `$this->request->getParam('named')` にすべての渡された引数にある +名前付きパラメーターを移します。すべての名前付きパラメーターとして変換された引数は +渡された引数のリストから除去されます。 diff --git a/docs/ja/development/sessions.md b/docs/ja/development/sessions.md new file mode 100644 index 0000000000..51b5f3b9a6 --- /dev/null +++ b/docs/ja/development/sessions.md @@ -0,0 +1,450 @@ +# セッション + +CakePHP は PHP のネイティブ `session` 拡張上に、ユーティリティ機能のスイートとラッパーを +提供します。セッションはリクエストにまたがるユニークユーザーの識別と各ユーザーごとの永続的データの +格納を可能にします。クッキーとは異なり、セッションデータはクライアント側では利用されません。 +CakePHP において、 `$_SESSION` の利用は通常避けています。代わりに Session クラスの利用を +推奨しています。 + +## セッションの設定 + +セッションの設定は、通常 **/config/app.php** で定義されます。 +また、いくつかのオプションが使用可能です: + +- `Session.timeout` - CakePHP のセッションハンドラがセッションを破棄するまでの時間を + *分* 単位で指定します。 +- `Session.defaults` - セッションの設定の基礎としてビルトインのデフォルト設定を使用出来ます。 + ビルトインの defaults に関しては、以下をご覧ください。 +- `Session.handler` - カスタムセッションハンドラーの定義が可能です。コアデータベースと + キャッシュセッションハンドラーが使います。このオプションは以前のバージョンの `Session.save` を + 置き換えます。詳しくはセッションハンドラーの追加情報をご覧下さい。 +- `Session.ini` - 追加のセッション ini セッティングを config に加えることが出来ます。これは + `Session.handler` と合わせて以前のバージョンのカスタムセッションハンドリング機能を置き換えます。 +- `Session.cookie` - 使用するクッキー名。デフォルトは、php.iniの、`session.name` で設定された値です。 +- `Session.cookiePath` - セッションクッキーを設定するための url パス。 + php.ini の設定 `session.cookie_path` にマップします。デフォルトは、アプリのベースパス。 + +アプリケーションが SSL プロトコル上にある時、CakePHP のデフォルトは、 +`session.cookie_secure` が `true` です。SSL と 非 SSL の両方のプロトコルで +アプリケーションを動かす場合、セッション消失の問題が発生するかも知れません。SSL と 非 SSL の +ドメイン両方でセッションにアクセスする必要がある場合、これを無効にします。 : + +``` php +Configure::write('Session', [ + 'defaults' => 'php', + 'ini' => [ + 'session.cookie_secure' => false + ] +]); +``` + +v4.0 以降、CakePHP はセッション Cookie の +[SameSite](https://owasp.org/www-community/SameSite) 属性をデフォルトで `Lax` に設定し、 +CSRF 攻撃から保護することもできるようになりました。 +このデフォルト値は、`session.cookie_samesite` php.ini config: で変更することができます。 : + +``` php +Configure::write('Session', [ + 'defaults' => 'php', + 'ini' => [ + 'session.cookie_samesite' => 'Strict' + ] +]); +``` + +セッションクッキーパスは、デフォルトでアプリケーションのベースパスになります。これを +変更するためには、 `session.cookie_path` ini の値を使用できます。例えば、 +すべてのサブドメインにまたがってセッションを永続化させたい場合、以下のようにします。 : + +``` php +Configure::write('Session', [ + 'defaults' => 'php', + 'ini' => [ + 'session.cookie_path' => '/', + 'session.cookie_domain' => '.yourdomain.com' + ] +]); +``` + +デフォルトでは、 PHP は `Session.timeout` の設定に関わらず、ブラウザーを閉じると +すぐに有効期限切れになるセッションクッキーを設定します。クッキーのタイムアウトは +`session.cookie_lifetime` ini の値で制御します。以下のように設定できます。 : + +``` php +Configure::write('Session', [ + 'defaults' => 'php', + 'ini' => [ + // サイト上のページに訪問せず 30 分経つとクッキーを無効にします。 + 'session.cookie_lifetime' => 1800 + ] +]); +``` + +`Session.timeout` と `session.cookie_lifetime` の値の間の違いは、 +後者はクライアントがクッキーを正しく伝えていることに依存していることです。 +もしあなたがより厳密なタイムアウトチェックを必要な場合、クライアントの情報を信頼せず、 +`Session.timeout` を使用してください。 + +`Session.timeout` は、ユーザーが無活動の合計時間 (例えば、セッションが使われるページを +見ていない時間)に相当し、そして、ユーザーがサイト上に留まることのできる合計時間(分)を +制限しないことに注意してください。 + +## ビルトインセッションハンドラーと設定 + +CakePHP にはいくつかビルトインなセッションの設定があります。これらをセッションの設定の基本として +使用するか、完全にカスタマイズしたソリューションを作成するかは自由です。デフォルトを使用する場合、 +単純に 'defaults' キーに使用したいデフォルト名をセットします。セッション config で宣言をすれば +サブセッティングだけを上書きすることも出来ます。 : + +``` php +Configure::write('Session', [ + 'defaults' => 'php' +]); +``` + +上記はビルトインの 'php' 設定を使用します。下記のように全てまたは部分的に +設定を上書きすることも出来ます。 : + +``` php +Configure::write('Session', [ + 'defaults' => 'php', + 'cookie' => 'my_app', + 'timeout' => 4320 // 3日間 +]); +``` + +上記は 'php' 設定のタイムアウトとクッキー名を上書きします。ビルトイン設定は: + +- `php` - php.ini ファイルの標準セッティングに従ってセッションを保存します。 +- `cake` - セッションをファイルとして `app/tmp/sessions` に保存します。ホストの設定が + ホームディレクトリー以外の書き込みを禁止している場合、有効なオプションです。 +- `database` ビルトインのデータベースセッションを使用します。詳しくは下記をご覧下さい。 +- `cache` - ビルトインのキャッシュセッションを使用します。詳しくは下記をご覧下さい。 + +### セッションハンドラー + +セッションハンドラーはセッション config 配列でも定義出来ます。 'handler.engine' 設定キーを +定義すると、クラス名の命名やハンドラーインスタンスの提供ができます。クラスやオブジェクトは、 +ネイティブ PHP の `SessionHandlerInterface` を実装しなければなりません。 +このインターフェースを実装することによって、 `Session` がハンドラーのメソッドと +自動的にマップすることができます。コアの Cache や Database セッションハンドラーは、 +セッションに保存する際にこのメソッドを使用します。ハンドラーのための追加の設定は、 +handler 配列内に配置されます。ハンドラー内からこれらの値を読むことができます。 : + +``` text +'Session' => [ + 'handler' => [ + 'engine' => 'DatabaseSession', + 'model' => 'CustomSessions' + ] +] +``` + +上記は、どのようにアプリケーションのモデルを使ってデータベースセッションハンドラーを +設定できるかを示しています。クラス名をあなたの handler.engine に使用した時、 +CakePHP は、 `Http\Session` 名前空間内にクラスがあることを期待します。 +例えば、 `AppSessionHandler` クラスを持っていた場合、ファイルは、 +**src/Http/Session/AppSessionHandler.php** に置いてください。そして、 +クラス名は、 `App\Http\Session\AppSessionHandler` にしてください。 +プラグインの中のセッションハンドラーを使うこともできます。その場合、エンジンを +`MyPlugin.PluginSessionHandler` のように設定します。 + +### データーベースセッション + +もし、セッションデータを保存するためにデータベースを使用する必要がある場合、 +以下のように設定してください。 : + +``` text +'Session' => [ + 'defaults' => 'database' +] +``` + +この設定は、以下の項目を持つデータベーステーブルが必要になります。 : + +``` sql +CREATE TABLE `sessions` ( + `id` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL, + `created` datetime DEFAULT CURRENT_TIMESTAMP, -- Optional + `modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- Optional + `data` blob DEFAULT NULL, -- for PostgreSQL use bytea instead of blob + `expires` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +[アプリケーションスケルトン](https://github.com/cakephp/app) の中の +`config/schema/sessions.sql` に、sessions テーブルのためのスキーマのコピーがあります。 + +セッションの保存を処理するための独自の `Table` クラスを使用することもできます。 : + +``` text +'Session' => [ + 'defaults' => 'database', + 'handler' => [ + 'engine' => 'DatabaseSession', + 'model' => 'CustomSessions' + ] +] +``` + +上記は、 Session にビルドインの 'database' の defaults を使用することを伝え、 +データベースにセッション情報を保存するために `CustomSessions` と呼ばれる Table に +委任することを指定します。 + +### キャッシュセッション + +キャッシュクラスはセッションの格納にも使用されます。これはキャッシュ内のセッションを +APCu または Memcached のように格納することを可能にします。キャッシュセッションの +使用ではいくつか注意する点があります。もし、キャッシュ容量を使い果たした場合、 +セッションは、レコードが追い出されるように有効期限切れになり始めます。 + +キャッシュベースのセッションを使うために Session の config を以下のように設定します。 : + +``` php +Configure::write('Session', [ + 'defaults' => 'cache', + 'handler' => [ + 'config' => 'session' + ] +]); +``` + +これは Session に `CacheSession` クラスをセッション保存先として +委任する設定です。 'config' キーをキャッシュの設定に使用できます。 +デフォルトのキャッシュ設定は `'default'` です。 + +## ini ディレクティブの設定 + +ビルドイン defaults は、セッション設定のための共通の基盤を提供することを試みます。 +必要に応じて特定の ini フラグを微調整することもあります。 CakePHP ではデフォルト設定にしろ、 +カスタム設定にしろ、両者の ini セッティングをカスタマイズ出来ます。セッションセッティングの +`ini` キーで、個別の設定値を指定することが可能です。例えば `session.gc_divisor` のような +セッティングをコントロールするのに使えます。 : + +``` php +Configure::write('Session', [ + 'defaults' => 'php', + 'ini' => [ + 'session.cookie_name' => 'MyCookie', + 'session.cookie_lifetime' => 1800, // 30分有効 + 'session.gc_divisor' => 1000, + 'session.cookie_httponly' => true + ] +]); +``` + +## カスタムセッションハンドラーの作成 + +カスタムセッションハンドラーの作成は CakePHP で容易に出来ます。この例で、セッションを +キャッシュ (APC) とデータベースの両方に格納するセッションハンドラーを作成します。 +これは APC による、キャッシュ限度を超過した際の消失について心配が不要な、最善で高速な +IO をもたらします。 + +まずカスタムクラスを作成し **src/Http/Session/ComboSession.php** +に保存する必要があります。クラスは以下のようになります。 : + +``` php +namespace App\Http\Session; + +use Cake\Cache\Cache; +use Cake\Core\Configure; +use Cake\Http\Session\DatabaseSession; + +class ComboSession extends DatabaseSession +{ + protected $cacheKey; + + public function __construct() + { + $this->cacheKey = Configure::read('Session.handler.cache'); + parent::__construct(); + } + + // セッションからデータ読込み + public function read($id): string + { + $result = Cache::read($id, $this->cacheKey); + if ($result) { + return $result; + } + + return parent::read($id); + } + + // セッションへのデータ書込み + public function write($id, $data): bool + { + Cache::write($id, $data, $this->cacheKey); + + return parent::write($id, $data); + } + + // セッションの破棄 + public function destroy($id): bool + { + Cache::delete($id, $this->cacheKey); + + return parent::destroy($id); + } + + // 有効期限切れセッションの削除 + public function gc($expires = null): bool + { + return parent::gc($expires); + } +} +``` + +このクラスはビルトインの `DatabaseSession` を継承しそのロジックや振る舞いを重複して +定義することを避けています。それぞれのオペレーションを `Cake\Cache\Cache` +オペレーションでラップします。これで高速なキャッシュからセッションを取得しつつ、 +キャッシュ限度の考慮を不要にしています。このセッションハンドラーを使うのもまた簡単です。 +**app.php** のセッションブロックを以下のように設定します。 : + +``` text +'Session' => [ + 'defaults' => 'database', + 'handler' => [ + 'engine' => 'ComboSession', + 'model' => 'Session', + 'cache' => 'apc' + ] +], +// apc キャッシュ config を追加すること +'Cache' => [ + 'apc' => ['engine' => 'Apc'] +] +``` + +これでアプリケーションはカスタムセッションハンドラーを使ったセッションデータの読み書きを行います。 + +`class` **Session** + +## セッションオブジェクトへのアクセス + +リクエストオブジェクトにアクセスできる任意の場所でセッションデータにアクセスすることができます。 +これは、以下でセッションにアクセス可能であることを意味します。 + +- Controllers +- Views +- Helpers +- Cells +- Components + +基本的なセッションの使用例は以下の通りです。 : + +``` php +$name = $this->request->getSession()->read('User.name'); + +// 複数回セッションにアクセスする場合、 +// ローカル変数にしたくなるでしょう。 +$session = $this->request->getSession(); +$name = $session->read('User.name') +``` + +ヘルパーでは、 `$this->getView()->getRequest()` を使用して、リクエストオブジェクトを取得します。 +コンポーネントでは、 `$this->getController()->getRequest()` を使用します。 + +## セッションデータの読込みと書込み + +`method` Session::**read**($key, $default = null) + +`Hash::extract()` 互換の構文を使ってセッションから値を読込みます。 : + +``` php +$session->read('Config.language', 'en'); +``` + +`method` Session::**readOrFail**($key) + +Null値でない戻り値に対する便宜的なラッパーと同じです。 : + +``` php +$session->readOrFail('Config.language'); +``` + +これは、このキーが設定されなければならないことが分かっていて、 +コード自体でその存在を確認する必要がない場合に便利です。 + +`method` Session::**write**($key, $value) + +`$key` は、ドット区切りで `$value` の書込み先を指定します。 : + +``` php +$session->write('Config.language', 'en'); +``` + +以下のように1つもしくは複数のハッシュを指定することもできます。 : + +``` php +$session->write([ + 'Config.theme' => 'blue', + 'Config.language' => 'en', +]); +``` + +`method` Session::**delete**($key) + +セッションからデータ削除が必要なら `delete()` が使用できます。 : + +``` php +$session->delete('Some.value'); +``` + +
    + + + +
    + +セッションからデータの読込みと削除が必要なら `consume()` が使用できます。 : + +``` php +$session->consume('Some.value'); +``` + +`method` Session::**check**($key) + +セッションにデータが存在するかどうかを知りたいなら `check()` が使用できます。 : + +``` php +if ($session->check('Config.language')) { + // Config.language が存在し null ではない。 +} +``` + +## セッションの破棄 + +`method` Session::**destroy**() + +ユーザーがログアウトするときにセッションの破棄は便利です。セッションを破棄するために +`destroy()` メソッドを使用してください。 : + +``` php +$session->destroy(); +``` + +セッションの破棄は、セッション内の全てのサーバー側データを削除しますが、セッションクッキーの +**削除はしません** 。 + +## セッション ID の切替え + +`method` Session::**renew**() + +ユーザーがログインやログアウトした時、 `Authentication Plugin` は自動的にセッション ID を更新しますが、 +セッション ID を手動で切り替えたい時もあるでしょう。そのためには、 `renew()` メソッドを +使います。 : + +``` php +$session->renew(); +``` + +## フラッシュメッセージ + +フラッシュメッセージは、エンドユーザーに一度だけ表示する短いメッセージです。それらは、 +エラーメッセージの表示や、アクションが上手くいったことを確認するためにしばしば用いられます。 + +フラッシュメッセージのセットや表示には、 +[フラッシュ](../controllers/components/flash) と +[Flash](../views/helpers/flash) を使いましょう。 diff --git a/docs/ja/development/testing.md b/docs/ja/development/testing.md new file mode 100644 index 0000000000..ce9e2d4806 --- /dev/null +++ b/docs/ja/development/testing.md @@ -0,0 +1,1952 @@ +# テスト + +
    + +(Japanese) このファイルは においてCIのエラーを回避するために修正されましたが、きちんと翻訳が更新された訳ではありません。翻訳を頑張りましょう。 + +
    + +CakePHP には [PHPUnit](https://phpunit.de) をベースとした高度なインテグレーションが組み込まれており、PHPUnit 本体が持つ機能に加えて、CakePHPでテストをスマートに管理するための便利な拡張機能を備えています。このセクションでは、PHPUnit のインストールからユニットテストの +始め方、そしてCakePHP が提供する拡張機能について説明します。 + +## PHPUnit のインストール + +CakePHP のテストフレームワークは PHPUnit をベースとしています。PHPUnit は PHP ユニットテストフレームワークのデファクトスタンダードであり、思いどおりのコードを安全に書くための豊富な機能を提供します。 +PHPUnit は [Composer](https://getcomposer.org) または [PHAR パッケージ](https://phpunit.de/#download) +のいずれかを使用してインストールできます。 + +### Composer による PHPUnit のインストール + +Composer で PHPUnit をインストールする場合: + +``` bash +$ php composer.phar require --dev phpunit/phpunit:"^8.5" +``` + +コマンドラインで上記のように実行すると、 `composer.json` の `require-dev` セクションに +依存関係を追加し、すべての依存関係と一緒に PHPUnit をインストールします。 + +これで、PHPUnit を以下のように実行することができます。 + +``` bash +$ vendor/bin/phpunit +``` + +### PHAR ファイルを使用する場合 + +**phpunit.phar** ファイルをダウンロードすると、テストを実行するために使用することができます。 + +``` bash +php phpunit.phar +``` + +> [!TIP] +> 次のようにすると、都合よく phpunit.phar をグローバルに Unix や Linux で利用できます。 +> +> ``` bash +> chmod +x phpunit.phar +> sudo mv phpunit.phar /usr/local/bin/phpunit +> phpunit --version +> ``` +> +> [Windows 上で PHPUnit の PHAR をグローバルにインストールする方法](https://phpunit.de/manual/current/ja/installation.html#installation.phar.windows) +> に関する手順については、PHPUnit のドキュメントを参照してください。 + +## テスト用データベースのセットアップ + +テストを実行する前に **config/app.php** ファイルで debug が有効になっていることを +忘れないでください。テストを実行する前に **config/app.php** に `test` データソース設定を +追加する必要があります。この設定は、CakePHP でフィクスチャーのテーブルとデータのために使用されます。 : + +``` php +'Datasources' => [ + 'test' => [ + 'datasource' => 'Cake\Database\Driver\Mysql', + 'persistent' => false, + 'host' => 'dbhost', + 'username' => 'dblogin', + 'password' => 'dbpassword', + 'database' => 'test_database' + ], +], +``` + +> [!NOTE] +> トラブルを防ぐため、テストデータベースはメインのデータベースとは別に用意することをお勧めします。 + +## テストのセットアップの確認 + +PHPUnit をインストールして `test` データソースを設定した後、 独自のテストを作成し +実行する準備ができていることを、アプリケーションのテストを実行することにより確認できます。 + +``` bash +# phpunit.phar について +$ php phpunit.phar + +# Composer でインストールされた phpunit +$ vendor/bin/phpunit +``` + +上記を実行するとテストが実行されます(テストが作成されている場合)。 + +特定のテストを実行したい場合は、パラメーターとしてテストのパスを指定します。 +例えば、ArticlesTable クラスのテストケースがある場合、次のように実行します。 + +``` bash +$ vendor/bin/phpunit tests/TestCase/Model/Table/ArticlesTableTest +``` + +実行したテストや成功したテスト・失敗したテストの数など、 各種情報がカラーで表示されます。 + +> [!NOTE] +> Windows システムの場合、おそらくカラー表示はされません。 + +## テストケースの規約 + +CakePHP が全般的にそうであるように、テストケースにもいくつか規約があります。 +以下のとおりです。 + +1. テストを含むPHPファイルは、 `tests/TestCase/[Type]` ディレクトリーに置きます。 +2. ファイル名のサフィックスは .php ではなく **Test.php** とします。 +3. テストを含むクラスは `Cake\TestSuite\TestCase` 、 `Cake\TestSuite\IntegrationTestCase` または `\PHPUnit\Framework\TestCase` を継承する必要があります。 +4. 他のクラス名と同様に、テストケースのクラス名はファイル名と一致する必要があります。 + **RouterTest.php** は、 `class RouterTest extends TestCase` が含まれている必要があります。 +5. テストを含むメソッド (つまり、アサーションを含むメソッド) の名前は `testPublished()` のように `test` で始める必要があります。 `@test` というアノテーションをメソッドにマークすることでテストメソッドとすることもできます。 + +## 最初のテストケースを作成 + +一例として、とても簡単な、ヘルパーメソッドのためのテストケースを作成します。 +これからテストのために作成するメソッドは HTML でプログレスバーを描画するものです。 +ヘルパーは次のようになります。 : + +``` php +namespace App\View\Helper; + +use Cake\View\Helper; + +class ProgressHelper extends Helper +{ + public function bar($value) + { + $width = round($value / 100, 2) * 100; + + return sprintf( + '
    +
    +
    ', $width); + } +} +``` + +作成したヘルパーを保存したら、 **tests/TestCase/View/Helper/ProgressHelperTest.php** +としてテストケースのファイルを作成します。このファイルにまず、以下のように書き込みます。: + +``` php +namespace App\Test\TestCase\View\Helper; + +use App\View\Helper\ProgressHelper; +use Cake\TestSuite\TestCase; +use Cake\View\View; + +class ProgressHelperTest extends TestCase +{ + public function setUp(): void + { + } + + public function testBar(): void + { + } +} +``` + +空のメソッドが2つあります。次にメソッドの中身を書きます。最初は `setUp()` です。 +このメソッドはこのテストケースクラスのテストメソッドが 呼び出される前に毎回呼び出されます。 +セットアップメソッドはテストに必要なオブジェクトの初期化や設定を行います。 +今回のセットアップメソッドには次のように書き加えます。 : + +``` php +public function setUp(): void +{ + parent::setUp(); + $View = new View(); + $this->Progress = new ProgressHelper($View); +} +``` + +親メソッドを必ずロードしてください。 `TestCase::setUp()` は、 +`Cake\Core\Configure` の値をバックアップしたり、 +`Cake\Core\App` にパスを保存したりといった、いくつかの作業をしているからです。 + +次に、テストメソッドの内容を記述します。期待した結果を +出力できるかどうかをテストするため「アサーション」を使います。 : + +``` php +public function testBar(): void +{ + $result = $this->Progress->bar(90); + $this->assertStringContainsString('width: 90%', $result); + $this->assertStringContainsString('progress-bar', $result); + + $result = $this->Progress->bar(33.3333333); + $this->assertStringContainsString('width: 33%', $result); +} +``` + +`assertStringContainsString()` というアサーションを用いることで、ヘルパーが返した値に期待した文字列が +含まれていることをテストできます。期待した文字列が含まれていなければテストは失敗し、 +コードが正しくないことがわかります。 + +テストケースを使うことにより、 既知の入力セットと期待される出力結果との関係を 簡単に記述することが +できます。つまり、書いたコードが期待した動作を満たしているかどうかを自動的にテストできます。これにより、新たなバグの発生を未然に検知し、私達は自信を持って開発を進めていくことができるようになります。 + +> [!NOTE] +> EventManager は、各テストメソッドごとにリフレッシュされます。 +> これは、一度に複数のテストを実行した際、ブートストラップは一度だけ実行されるため、 +> config/bootstrap.php に登録されたイベントリスナーは失われることを意味します。 + +## テストの実行 + +PHPUnit をインストールし、テストケースをいくつか書いたら、テストを何度も実行したくなるでしょう。 +すべての変更をコミットする前に、何も壊れていないことを確認するために、テストを実行することを +お勧めします。 + +`phpunit` を使うことで、アプリケーションのテストを実行できます。 +アプリケーションのテストを実行するには、シンプルに実行することができます。 + +``` bash +# composer でインストールされたファイルを実行する場合 +vendor/bin/phpunit + +# phar 形式のファイルを実行する場合 +php phpunit.phar +``` + +[GitHub から CakePHP ソース](https://github.com/cakephp/cakephp) をクローンして +CakePHP のユニットテストを実行したい場合、 `phpunit` を実行する前に、すべての依存関係が +インストールされているように、以下の `Composer` コマンドを実行することを忘れないでください。 + +``` bash +composer install +``` + +アプリケーションのルートディレクトリーから以下を行います。アプリケーションのソースの一部である +プラグインのテストを実行するには、まず `cd` でプラグインディレクトリーに移動し、その後、 +PHPUnit のインストール方法に合わせて `phpunit` コマンドを使用してください。 + +``` bash +cd plugins + +# composer でインストールされた phpunit を使用 +../vendor/bin/phpunit + +# phar 形式のファイルを使用 +php ../phpunit.phar +``` + +スタンドアロンのプラグインのテストを実行するには、最初に別のディレクトリーにプロジェクトを +インストールして、その依存関係をインストールする必要があります。 + +``` bash +git clone git://github.com/cakephp/debug_kit.git +cd debug_kit +php ~/composer.phar install +vendor/bin/phpunit +``` + +### テストケースのフィルタリング + +たくさんのテストケースがあると、その中からサブセットだけをテストしたいときや、失敗したテストだけを +実行したいときがあると思います。コマンドラインからテストメソッドをフィルタリングするときはオプションを +使用します。 + +``` bash +$ phpunit --filter testSave tests/TestCase/Model/Table/ArticlesTableTest +``` + +テストメソッドを実行するためフィルタリングとして、filter パラメーターは大文字と小文字を区別する +正規表現を使用します。 + +### コードカバレッジの生成 + +PHPUnit に組み込まれたコードカバレッジツールを用いて、コードカバレッジのレポートを +HTML ファイル形式で生成することができます。 +テストケースのカバレッジを生成するには以下のようにします。 + +``` bash +$ phpunit --coverage-html webroot/coverage tests/TestCase/Model/Table/ArticlesTableTest +``` + +カバレッジ結果のHTMLファイルは、アプリケーションの webroot ディレクトリー内に生成されます。 +`http://localhost/your_app/coverage` にアクセスすると、結果を表示することができます。 + +また、カバレッジを生成するために xdebug の代わりに +`phpdbg` を使用できます。カバレッジの生成は `phpdbg` の方が高速です。 + +``` bash +$ phpdbg -qrr phpunit --coverage-html webroot/coverage tests/TestCase/Model/Table/ArticlesTableTest +``` + +### プラグインテストのためのテストスイート + +アプリケーションは、複数のプラグインで構成されることもあります。 +各プラグインのテストは手間がかかるものですが、アプリケーションの **phpunit.xml.dist** +ファイルに `` セクションを追加すると、アプリケーションを構成する各プラグインの +テストを手軽に実行することができます。 + +``` xml + + + ./tests/TestCase/ + + + + + ./plugins/Forum/tests/TestCase/ + + +``` + +`phpunit` を使用すると、 `` 要素に追加されたテストスイートは自動的に実行されます。 + +もし、 composer でインストールされたプラグインのフィクスチャーを使用するために +`` を使用している場合、プラグインの `composer.json` ファイルに +フィクスチャーの名前空間を autoload セクションに追加してください。例: + +``` json +"autoload-dev": { + "psr-4": { + "PluginName\\Test\\Fixture\\": "tests/Fixture/" + } +}, +``` + +## テストケースのライフサイクルコールバック + +テストケースは以下のようにいくつかのライフサイクルコールバックを持っており、 +テストの際に使うことができます。 + +- `setUp` は、テストメソッドの前に毎回呼び出されます。 + テストされるオブジェクトの生成や、テストのためのデータの初期化に使われます。 + `parent::setUp()` を呼び出すことを忘れないでください。 +- `tearDown` は、テストメソッドの後に毎回呼び出されます。 + テストが完了した後のクリーンアップに使われます。 + `parent::tearDown()` を呼び出すことを忘れないでください。 +- `setupBeforeClass` はクラスのテストメソッドを実行する前に一度だけ呼ばれます。 + このメソッドは *static* でなければなりません。 +- `tearDownAfterClass` はクラスのテストメソッドをすべて実行した後に一度だけ呼ばれます。 + このメソッドは *static* でなければなりません。 + +## フィクスチャー + +テストコードの挙動がデータベースやモデルに依存するとき、テストに使うためのテーブルを生成し、 +一時的なデータをロードするために **フィクスチャー** を使うことができます。 +フィクスチャーを使うことにより、 実際のアプリケーションに使われているデータを破壊することなく +テストができるというメリットがあります。 また、アプリケーションのためのコンテンツを実際に用意するより +先にコードをテストすることができます。 + +このとき、CakePHP は設定ファイル **config/app.php** にある `test` という名前の +データベース接続設定を使います。この接続が使えないときは例外が発生し、フィクスチャーを使うことが +できません。 + +CakePHP はフィクスチャーに基づいたテストケースを実行するにあたり、以下の動作をします。 + +1. 各フィクスチャーで必要なテーブルを作成します。 +2. フィクスチャーにデータが存在すれば、それをテーブルに投入します。 +3. テストメソッドを実行します。 +4. フィクスチャーのテーブルを空にします。 + +### テスト接続 + +デフォルトでは、CakePHP のアプリケーション内の各データベース接続は別名になります。 +アプリケーションのブートストラップで定義された (`test_` がつかない) 各データベース接続は、 +`test_` プレフィクスがついた別名を持つことになります。テストケースで誤って間違った接続を +使用しないことを、エイリアシングの接続が保証します。接続エイリアシングは、アプリケーションの +残りの部分には透過的です。例えば 'default' コネクションを使用している場合、 +代わりに、テストケースで `test` コネクションを取得します。 'replica' コネクションを使用する場合、テストスイートは 'test_replica' を使おうとします。 + +### PHPUnitの設定 + +フィクスチャーを使う前に、 `phpunit.xml` にフィクスチャExtensionが含まれていることを再確認する必要があります。 + +``` xml + + + + + +``` + +※CakePHP 4.3.0より以前はPHPUnitのフィクスチャExtensionではなくテストリスナー機能が使用されていたため、phpunit.xmlには下記のように書く必要があります。 + +``` xml + + + + + + + + + +``` + +※リスナーは非推奨であり、フィクスチャ構成を更新する必要があります。 + +### テスト用のデータベーススキーマ作成 + +CakePHPのマイグレーション機能・SQLダンプファイルのロード、または他のスキーマ管理ツールを使用して、テスト用のデータベーススキーマを生成できます。アプリケーションの `tests/bootstrap.php` ファイルにスキーマを作成する必要があります。 + +CakePHPの migrations プラグイン \ を使用してアプリケーションのスキーマを管理する場合は、 +それらのマイグレーションを利用してテストデータベーススキーマを +生成することもできます。: + +``` php +// in tests/bootstrap.php +use Migrations\TestSuite\Migrator; + +$migrator = new Migrator(); + +// Simple setup for with no plugins +$migrator->run(); + +// Run migrations for multiple plugins +$migrator->run(['plugin' => 'Contacts']); + +// Run the Documents migrations on the test_docs connection. +$migrator->run(['plugin' => 'Documents', 'connection' => 'test_docs']); +``` + +複数のマイグレーションを実行する必要がある場合は、次のように実行できます。: + +``` php +// Run migrations for plugin Contacts on +$migrator->runMany([ + // Run app migrations on test connection. + ['connection' => 'test'] + // Run Contacts migrations on test connection. + ['plugin' => 'Contacts'], + // Run Documents migrations on test_docs connection. + ['plugin' => 'Documents', 'connection' => 'test_docs'] +]); +``` + +`runMany()` を使うと、データベースを共有するプラグインが、各マイグレーションが実行される時にテーブルをドロップしないようになります。 + +マイグレーションプラグインは、適用されていないマイグレーションのみを実行し、カレントのマイグレーションヘッドが適用されたマイグレーションと異なる場合はマイグレーションをリセットします。 + +データソース構成のテストでマイグレーションを実行する方法を構成することもできます。詳細については、 [マイグレーションに関するドキュメント](../migrations) を参照してください。 + +SQLダンプファイルをロードしたい場合は、下記のメソッドを使用できます。: + +``` php +// in tests/bootstrap.php +use Cake\TestSuite\Fixture\SchemaLoader; + +// Load one or more SQL files. +(new SchemaLoader())->loadSqlFiles('path/to/schema.sql', 'test'); +``` + +各テスト実行の `SchemaLoader` 開始時に、コネクションに紐づく全のテーブルを削除し、提供されたスキーマファイルに基づいてテーブルを再構築します。 + +::: info Added in version 4.3.0 +SchemaLoaderが追加されました。 +::: + +### フィクスチャステートマネージャ + +デフォルトでは、CakePHPは、データベース内のすべてのテーブルを truncate することにより、各テストの最後にフィクスチャの状態をリセットします。この処理は、アプリケーションが大きくなるにつれてコストがかかる可能性があります。 `TransactionStrategy` を各テストメソッドに使用すると、テストの最後にロールバックされるトランザクション内で実行されます。これによりパフォーマンスが向上しますが、各テストの前に自動インクリメント値がリセットされないため、テストで静的フィクスチャデータに大きく依存しないようにする必要があります。 + +フィクスチャの状態管理は、テストケース内で定義できます。: + +``` php +use Cake\TestSuite\TestCase; +use Cake\TestSuite\Fixture\FixtureStrategyInterface; +use Cake\TestSuite\Fixture\TransactionStrategy; + +class ArticlesTableTest extends TestCase +{ + /** + * Create the fixtures strategy used for this test case. + * You can use a base class/trait to change multiple classes. + */ + protected function getFixtureStrategy(): FixtureStrategyInterface + { + return new TransactionStrategy(); + } +} +``` + +::: info Added in version 4.3.0 +::: + +### フィクスチャーの作成 + +フィクスチャは、テストのためにデータベースに挿入されるレコードを定義します。 + +それでは最初のフィクスチャーを作成してみましょう。この例ではArticleモデルのフィクスチャーを作成します。 +以下の内容で、 **tests/Fixture** ディレクトリーに **ArticlesFixture.php** という名前のファイルを +作成してください。 : + +``` php +namespace App\Test\Fixture; + +use Cake\TestSuite\Fixture\TestFixture; + +class ArticlesFixture extends TestFixture +{ + // (オプション) 異なるテストデータソースにフィクスチャーをロードするために、このプロパティーを設定 + public $connection = 'test'; + + public $records = [ + [ + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => '1', + 'created' => '2007-03-18 10:39:23', + 'modified' => '2007-03-18 10:41:31' + ], + [ + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => '1', + 'created' => '2007-03-18 10:41:23', + 'modified' => '2007-03-18 10:43:31' + ], + [ + 'title' => 'Third Article', + 'body' => 'Third Article Body', + 'published' => '1', + 'created' => '2007-03-18 10:43:23', + 'modified' => '2007-03-18 10:45:31' + ] + ]; + } +``` + +> [!NOTE] +> autoincrementカラムに手動で値を追加しないことをお勧めします。PostgreSQLおよびSQLServerでのシーケンス生成に干渉するためです。 + +`$connection` プロパティは、フィクスチャーが使用するデータソースを定義します。アプリケーションが +複数のデータソースを使用している場合、フィクスチャーはモデルのデータソースと一致しますが、 `test_` +プレフィックスを付ける必要があります。例えば、お使いのモデルが `mydb` データソースを使用している場合、 +フィクスチャーは、 `test_mydb` データソースになります。 +`test_mydb` 接続が存在しない場合、モデルはデフォルトの `test` データソースを使用します。 +テストを実行するときにテーブル名の衝突を避けるため、フィクスチャーのデータソースには `test` +のプレフィックスが必ず付きます。 + +フィクスチャテーブルの作成後に入力される一連のレコードを定義できます。 `$records` はレコードの配列データです。 `$records` 内の各項目は単一の行である必要があります。各行の中には、行の列と値の連想配列が必要です。複数レコードを一括挿入する際に用いる `$records` 配列内の各レコードは、同じキー構成が必要であることに注意してください。 + +::: info Changed in version 4.3.0 +4.3.0より前のフィクスチャは、テーブルのスキーマも定義していました。フィクスチャでスキーマを定義する必要がある場合は、fixture-schema を確認できます。 +::: + +### 動的データ + +フィクスチャレコードで関数またはその他の動的データを使用するには、フィクスチャの `init()` メソッドでレコードを定義できます。例えば、created と +modified のタイムスタンプに今日の日付を反映させたいのであれば、 +以下のようにするとよいでしょう。: + +``` php +namespace App\Test\Fixture; + +use Cake\TestSuite\Fixture\TestFixture; + +class ArticlesFixture extends TestFixture +{ + public function init(): void + { + $this->records = [ + [ + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => '1', + 'created' => date('Y-m-d H:i:s'), + 'modified' => date('Y-m-d H:i:s'), + ], + ]; + parent::init(); + } +} +``` + +> [!NOTE] +> `init()` をオーバーライドするときは、必ず `parent::init()` をコールしてください。 + +### テストケースにフィクスチャを読み込む + +各テストケースごとにフィクスチャを定義します。 +クエリを実行するすべてのモデルのフィクスチャを +ロードする必要があります。 +フィクスチャをロードするには、 +モデルで `$fixtures` プロパティを定義します。: + +``` php +class ArticlesTest extends TestCase +{ + protected $fixtures = ['app.Articles', 'app.Comments']; +} +``` + +4.1.0以降、フィクスチャを定義するために `getFixtures()` メソッドを使うことができます。: + +``` php +public function getFixtures(): array +{ + return [ + 'app.Articles', + 'app.Comments', + ]; +} +``` + +上記の例では、アプリケーションのFixtureディレクトリからArticleおよびCommentフィクスチャをロードします。 + +CakePHPコアまたはプラグインからフィクスチャをロードすることもできます。: + +``` php +class ArticlesTest extends TestCase +{ + protected $fixtures = [ + 'plugin.DebugKit.Articles', + 'plugin.MyVendorName/MyPlugin.Messages', + 'core.Comments' + ]; +} +``` + +`core` プレフィックスを使用すると、CakePHPコアからフィクスチャがロードされます。また、プラグイン名をプレフィックスとして使用すると、指定されたプラグインからフィクスチャがロードされます。 + +サブディレクトリを作成してフィクスチャを整理することができます。大規模なアプリケーションを使用している場合などに便利です。サブディレクトリ内のフィクスチャをロードするには、フィクスチャ名にサブディレクトリ名を含めるだけです。: + +``` php +class ArticlesTest extends CakeTestCase +{ + protected $fixtures = ['app.Blog/Articles', 'app.Blog/Comments']; +} +``` + +上記の例では、各フィクスチャが `tests/Fixture/Blog/` ディレクトリからロードされます。 + +### フィクスチャファクトリー + +アプリケーションが大規模になると、テストフィクスチャの量も肥大化し、システム全体の管理が困難になりがちです。\`フィクスチャファクトリープラグイン \\`\_ は、大規模システム管理のための有効な解決手段です。 + +このプラグインは、各テストの前にすべてのダーティテーブルを切り捨てるために、 [テストスイートライトプラグイン](https://github.com/vierge-noire/cakephp-test-suite-light) を使用します。 + +下記のcakeコマンドでフィクスチャファクトリーをbakeできます。: + + bin/cake bake fixture_factory -h + +[ファクトリー](https://github.com/vierge-noire/cakephp-fixture-factories/blob/main/docs/factories.md) のbakeが完了すると、すぐにテストフィクスチャを作成することができます。 + +データベースとの不要なインタラクションは、テストとアプリケーションの速度を低下させます。テストフィクスチャを永続化せずに作成できます。これは、DBとのインタラクションなしでメソッドをテストする場合に役立ちます。: + +``` php +$article = ArticleFactory::make()->getEntity(); +``` + +永続化したい場合は下記のように。: + +``` php +$article = ArticleFactory::make()->persist(); +``` + +ファクトリーは、関連するフィクスチャの生成にも役立ちます。記事が複数の著者に属していると仮定すると、 +たとえば、それぞれ5つの記事を持つ2人の +著者を作成できます。: + +``` php +$articles = ArticleFactory::make(5)->with('Authors', 2)->getEntities(); +``` + +フィクスチャファクトリはフィクスチャの作成または宣言を必要としません。それでも、それらはCakePHPに付属しているフィクスチャと完全に互換性があります。 [ここ](https://github.com/vierge-noire/cakephp-fixture-factories) に追加の洞察とドキュメントがあります。 + +### テストでルーティング設定を読み込む + +メール送信・コントローラー・コンポーネント、 +またはその他クラスのテストでURLを紐付ける必要がある場合は、 +ルーティング設定を読み込む必要があります。 `setUp()` または +それぞれのテストメソッドの中で、 `loadRoutes()` を記述します: + +``` php +public function setUp(): void +{ + parent::setUp(); + $this->loadRoutes(); +} +``` + +このメソッドは、 `Application` インスタンスの作成と、そのインスタンスでの `routes()` メソッドの呼び出しを行ないます。 +この `Application` インスタンスのコンストラクタには、 `loadRoutes($constructorArgs)` としてパラメータを渡すことができます。 + +### テストにおけるルーティングの作成 + +プラグインや拡張性のあるアプリケーションを開発する場合など、 テスト内で動的にルートを追加することが必要になることがあります。 +例えば、プラグインや拡張性のあるアプリケーションを開発する場合などです。 + +既存のアプリケーションのルートを読み込むのと同じように、これはテストメソッドの `setup()` の中で行うことができます。 +で、あるいは個々のテストメソッド自身で行うことができます。: + +``` php +use Cake\Routing\Route\DashedRoute; +use Cake\Routing\RouteBuilder; +use Cake\Routing\Router; +use Cake\TestSuite\TestCase; + +class PluginHelperTest extends TestCase +{ + protected RouteBuilder $routeBuilder; + + public function setUp(): void + { + parent::setUp(); + + $this->routeBuilder = Router::createRouteBuilder('/'); + $this->routeBuilder->scope('/', function (RouteBuilder $routes) { + $routes->setRouteClass(DashedRoute::class); + $routes->get( + '/test/view/{id}', + ['controller' => 'Tests', 'action' => 'view'] + ); + // ... + }); + + // ... + } +} +``` + +これは新しいルートビルダのインスタンスを作成し、接続されたルートをマージします。 +を、他のすべてのルートビルダーインスタンスで使われる同じルートコレクションにマージします。 +接続されたルートを、その環境に存在する、あるいはまだ作成されていない他のすべてのルートビルダーインスタンスで使用される同じルートコレクションにマージします。 + +### プラグインをロード + +プラグインをロードしたい場合は `loadPlugins()` メソッドを使用できます。: + +``` php +public function testMethodUsingPluginResources() +{ + $this->loadPlugins(['Company/Cms']); + // Test logic that requires Company/Cms to be loaded. +} +``` + +## テーブルクラスのテスト + +**src/Model/Table/ArticlesTable.php** に ArticlesTable クラスが定義されているとします。 : + +``` php +namespace App\Model\Table; + +use Cake\ORM\Table; +use Cake\ORM\Query; + +class ArticlesTable extends Table +{ + public function findPublished(Query $query, array $options): Query + { + $query->where([ + $this->alias() . '.published' => 1 + ]); + + return $query; + } +} +``` + +このテーブルクラスに対するテストを設定します。以下の内容で、 +**tests/TestCase/Table** ディレクトリーに **ArticlesTableTest.php** というファイルを +作成してください。 : + +``` php +namespace App\Test\TestCase\Model\Table; + +use App\Model\Table\ArticlesTable; +use Cake\TestSuite\TestCase; + +class ArticlesTableTest extends TestCase +{ + protected $fixtures = ['app.Articles']; +} +``` + +このテストケースの `$fixtures` 変数に、使用したいフィクスチャーを設定します。 +クエリーを実行するために要なフィクスチャーをすべて設定してください。 + +### テストメソッドの作成 + +次に、ArticlesTable の `published()` メソッドに対するテストを追加してみましょう。 +**tests/TestCase/Model/Table/ArticlesTableTest.php** ファイルを次のように編集してください。 : + +``` php +namespace App\Test\TestCase\Model\Table; + +use App\Model\Table\ArticlesTable; +use Cake\TestSuite\TestCase; + +class ArticlesTableTest extends TestCase +{ + protected $fixtures = ['app.Articles']; + + public function setUp(): void + { + parent::setUp(); + $this->Articles = $this->getTableLocator()->get('Articles'); + } + + public function testFindPublished(): void + { + $query = $this->Articles->find('published')->all(); + $this->assertInstanceOf('Cake\ORM\Query', $query); + $result = $query->enableHydration(false)->toArray(); + $expected = [ + ['id' => 1, 'title' => 'First Article'], + ['id' => 2, 'title' => 'Second Article'], + ['id' => 3, 'title' => 'Third Article'] + ]; + + $this->assertEquals($expected, $result); + } +} +``` + +`testFindPublished()` というメソッドがあります。 +`ArticlesTable` クラスのインスタンスを作成した後、 `find('published')` +メソッドを実行します。 `$expected` に、期待する適切な結果をセットします。 +(article テーブルに配置されるレコードを定義します。) `assertEquals()` メソッドを使用して、 +結果が期待どおりであることをテストします。テストケースを実行する方法の詳細については +[Running Tests](#running-tests) セクションをご覧ください。 + +フィクスチャファクトリを使用する場合は、テストは次のようになります。 +: + +``` php +namespace App\Test\TestCase\Model\Table; + +use App\Test\Factory\ArticleFactory; +use Cake\TestSuite\TestCase; + +class ArticlesTableTest extends TestCase +{ + public function testFindPublished(): void + { + // Persist 3 published articles + $articles = ArticleFactory::make(['published' => 1], 3)->persist(); + // Persist 2 unpublished articles + ArticleFactory::make(['published' => 0], 2)->persist(); + + $result = ArticleFactory::find('published')->find('list')->toArray(); + + $expected = [ + $articles[0]->id => $articles[0]->title, + $articles[1]->id => $articles[1]->title, + $articles[2]->id => $articles[2]->title, + ]; + + $this->assertEquals($expected, $result); + } +} +``` + +フィクスチャをロードする必要はありません。作成された5つの記事は、このテストにのみ存在します。staticメソッド `::find()` は、テーブル `ArticlesTable` やそのイベントを使用せずにデータベースを読み込みます。 + +### モデルメソッドのモック化 + +モデルメソッドのモックが必要になることが +あります。 `getMockForModel` を使用してtableクラスのテストモックを作成できます。 +通常のモックが持つ反映されたプロパティーの +問題を回避します。 : + +``` php +public function testSendingEmails(): void +{ + $model = $this->getMockForModel('EmailVerification', ['send']); + $model->expects($this->once()) + ->method('send') + ->will($this->returnValue(true)); + + $model->verifyEmail('test@example.com'); +} +``` + +`tearDown()` メソッドの中でモックを削除してください。 : + +``` php +TableRegistry::clear(); +``` + +## コントローラーの統合テスト + +ヘルパー、モデル、およびコンポーネントと同様にコントローラーもテストできますが、 +CakePHP では特別に `IntegrationTestTrait` トレイトを提供しています。コントローラーのテストケースに +このトレイトを用いると、レベルが高いテストが可能になります。 + +統合テストに不慣れな場合は、複数ユニットの一括テストを容易にするためのアプローチがあります。CakePHP の統合テスト機能は、アプリケーションによって処理される HTTP +リクエストをシミュレートします。例えば、コントローラーをテストすると、与えられたリクエストに関する +コンポーネント、モデル、そしてヘルパーを実行します。これはアプリケーションとその動作する部品の全てに、ハイレベルなテストを提供します。 + +典型的な ArticlesController、およびそれに対応するモデルがあるとします。 +コントローラーのコードは次のようになります。 : + +``` php +namespace App\Controller; + +use App\Controller\AppController; + +class ArticlesController extends AppController +{ + public function index($short = null) + { + if ($this->request->is('post')) { + $article = $this->Articles->newEntity($this->request->getData()); + if ($this->Articles->save($article)) { + // PRG パターンのためリダイレクト + return $this->redirect(['action' => 'index']); + } + } + if (!empty($short)) { + $result = $this->Articles->find('all', [ + 'fields' => ['id', 'title'] + ]) + ->all(); + } else { + $result = $this->Articles->find()->all(); + } + + $this->set([ + 'title' => 'Articles', + 'articles' => $result + ]); + } +} +``` + +**tests/TestCase/Controller** ディレクトリーに **ArticlesControllerTest.php** という名前の +ファイルを作成し、内部に以下を記述してください。 : + +``` php +namespace App\Test\TestCase\Controller; + +use Cake\TestSuite\IntegrationTestTrait; +use Cake\TestSuite\TestCase; + +class ArticlesControllerTest extends TestCase +{ + use IntegrationTestTrait; + + protected $fixtures = ['app.Articles']; + + public function testIndex(): void + { + $this->get('/articles'); + + $this->assertResponseOk(); + // さらにアサート + } + + public function testIndexQueryData(): void + { + $this->get('/articles?page=1'); + + $this->assertResponseOk(); + // さらにアサート + } + + public function testIndexShort(): void + { + $this->get('/articles/index/short'); + + $this->assertResponseOk(); + $this->assertResponseContains('Articles'); + // さらにアサート + } + + public function testIndexPostData(): void + { + $data = [ + 'user_id' => 1, + 'published' => 1, + 'slug' => 'new-article', + 'title' => 'New Article', + 'body' => 'New Body' + ]; + $this->post('/articles', $data); + + $this->assertResponseSuccess(); + $articles = $this->getTableLocator()->get('Articles'); + $query = $articles->find()->where(['title' => $data['title']]); + $this->assertEquals(1, $query->count()); + } +} +``` + +この例では、いくつかのリクエストを送信するメソッドと `IntegrationTestTrait` が提供するいくつかの +アサーションを示しています。任意のアサーションを行う前に、リクエストをディスパッチする必要が +あります。リクエストを送信するには、以下のいずれかのメソッドを使用することができます。 + +- `get()` GET リクエストを送信します。 +- `post()` POST リクエストを送信します。 +- `put()` PUT リクエストを送信します。 +- `delete()` DELETE リクエストを送信します。 +- `patch()` PATCH リクエストを送信します。 +- `options()` OPTIONS リクエストを送信します。 +- `head()` HEAD リクエストを送信します。 + +`get()` と `delete()` を除く全てのメソッドは、リクエストボディーを送信することを +可能にする二番目のパラメーターを受け入れます。リクエストをディスパッチした後、ユーザのリクエストに対して +正しく動作したことを確実にするために `IntegrationTestTrait` や、PHPUnit が提供するさまざまな +アサーションを使用することができます。 + +### リクエストの設定 + +`IntegrationTestTrait` トレイトを使用すると、テスト対象のアプリケーションに送信するリクエストを +設定することが容易にするために多くのヘルパーが付属しています。 : + +``` php +// クッキーのセット +$this->cookie('name', 'Uncle Bob'); + +// セッションデータのセット +$this->session(['Auth.User.id' => 1]); + +// ヘッダーの設定 +$this->configRequest([ + 'headers' => ['Accept' => 'application/json'] +]); +``` + +これらのヘルパーメソッドによって設定された状態は、 `tearDown()` メソッドでリセットされます。 + +### ステートレス認証と API のテスト + +Basic 認証のようなステートレス認証を使用する API をテストするために、実際の認証の +リクエストヘッダーをシミュレートする環境変数やヘッダーを注入するためにリクエストを設定できます。 + +Basic または Digest 認証をテストする際、自動的に +[PHP が作成する](https://php.net/manual/ja/features.http-auth.php) +環境変数を追加できます。これらの環境変数は、 に概説されている +認証アダプター内で使用されます。 : + +``` php +public function testBasicAuthentication(): void +{ + $this->configRequest([ + 'environment' => [ + 'PHP_AUTH_USER' => 'username', + 'PHP_AUTH_PW' => 'password', + ] + ]); + + $this->get('/api/posts'); + $this->assertResponseOk(); +} +``` + +OAuth2 のようなその他の認証方法をテストしている場合、Authorization ヘッダーを +直接セットできます。 : + +``` php +public function testOauthToken(): void +{ + $this->configRequest([ + 'headers' => [ + 'authorization' => 'Bearer: oauth-token' + ] + ]); + + $this->get('/api/posts'); + $this->assertResponseOk(); +} +``` + +`configRequest()` 内の headers キーは、アクションに必要な追加の HTTP ヘッダーを +設定するために使用されます。 + +### CsrfProtectionMiddleware や FormProtectionComponent で保護されたアクションのテスト + +`CsrfProtectionMiddleware` または `FormProtectionComponent` のいずれかで保護されたアクションをテストする場合、 +テストがトークンのミスマッチで失敗しないように自動トークン生成を有効にすることができます。 : + +``` php +public function testAdd(): void +{ + $this->enableCsrfToken(); + $this->enableSecurityToken(); + $this->post('/posts/add', ['title' => 'Exciting news!']); +} +``` + +また、トークンを使用するテストで debug を有効にすることは重要です。`FormProtectionComponent` が +「デバッグ用トークンがデバッグ以外の環境で使われている」と考えてしまうのを防ぐためです。 +`requireSecure()` のような他のメソッドでテストした時は、適切な環境変数をセットするために +`configRequest()` を利用できます。: + +``` php +// SSL 接続を装います。 +$this->configRequest([ + 'environment' => ['HTTPS' => 'on'] +]); +``` + +アクションでアンロックされたフィールドが必要な場合は、 +`setUnlockedFields()` で宣言することができます。 : + +``` php +$this->setUnlockedFields(['dynamic_field']); +``` + +### PSR-7 ミドルウェアの統合テスト + +統合テストは、PSR-7 アプリケーション全体や [ミドルウェア](../controllers/middleware) を +テストするために利用されます。デフォルトで `IntegrationTestTrait` は、 +`App\Application` クラスの存在を自動検知し、アプリケーションの統合テストを +自動的に有効にします。 + +`configApplication()` メソッドを使うことによって、使用するアプリケーションクラス名と +コンストラクターの引数をカスタマイズすることができます。 : + +``` php +public function setUp(): void +{ + $this->configApplication('App\App', [CONFIG]); +} +``` + +イベントやルートを含むプラグインを読み込むために [Application Bootstrap](../development/application#application-bootstrap) を +試してみてください。そうすることで、各テストケースごとにイベントやルートが接続されます。 +テスト中に手動でプラグインをロードしたい場合は `loadPlugins()` メソッドを使うことができます。 + +### 暗号化されたクッキーを使用したテスト + +アプリケーションで [Encrypted Cookie Middleware](../controllers/middleware#encrypted-cookie-middleware) を使用している場合、 +テストケースで暗号化クッキーを設定するためのヘルパーメソッドがあります。 : + +``` php +// AES とデフォルトキーを使ってクッキーをセット +$this->cookieEncrypted('my_cookie', '何か秘密の値'); + +// このアクションは、クッキーを変更するものとします。 +$this->get('/bookmarks/index'); + +$this->assertCookieEncrypted('更新された値', 'my_cookie'); +``` + +### フラッシュメッセージのテスト + +描画された HTML ではなく、セッション内にフラッシュメッセージが存在することをアサートする場合、 +テスト内で `enableRetainFlashMessages()` を使ってセッション内のフラッシュメッセージを保持し、 +アサーションを書くことができます。 : + +``` php +// Enable retention of flash messages instead of consuming them. +$this->enableRetainFlashMessages(); +$this->get('/bookmarks/delete/9999'); + +$this->assertSession('ブックマークは存在しません', 'Flash.flash.0.message'); + +// 'flash' キー内のフラッシュメッセージをアサート +$this->assertFlashMessage('Bookmark deleted', 'flash'); + +// 2つ目のフラッシュメッセージをアサート +$this->assertFlashMessageAt(1, 'Bookmark really deleted'); + +// 最初の位置の 'auth' キーにフラッシュメッセージをアサート +$this->assertFlashMessageAt(0, 'You are not allowed to enter this dungeon!', 'auth'); + +// フラッシュメッセージがエラーエレメントを使用していることをアサート +$this->assertFlashElement('Flash/error'); + +// 2つ目のフラッシュメッセージのエレメントをアサート +$this->assertFlashElementAt(1, 'Flash/error'); +``` + +### JSON を返すコントローラーのテスト + +JSON は、ウェブサービスの構築において、とても馴染み深く、かつ基本的なフォーマットです。 +CakePHP を用いたウェブサービスのエンドポイントのテストはとてもシンプルです。 +JSON を返すコントローラーの簡単な例を示します。 : + +``` php +class MarkersController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('RequestHandler'); + } + + public function view($id) + { + $marker = $this->Markers->get($id); + $this->set('marker', $marker); + $this->viewBuilder()->setOption('serialize', ['marker']); + } +} +``` + +今、 **tests/TestCase/Controller/MarkersControllerTest.php** ファイルを作成し、 +ウェブサービスが適切な応答を返していることを確認してください。 : + +``` php +class MarkersControllerTest extends IntegrationTestCase +{ + public function testGet(): void + { + $this->configRequest([ + 'headers' => ['Accept' => 'application/json'] + ]); + $result = $this->get('/markers/view/1.json'); + + // レスポンスが 200 であることを確認 + $this->assertResponseOk(); + + $expected = [ + ['id' => 1, 'lng' => 66, 'lat' => 45], + ]; + $expected = json_encode($expected, JSON_PRETTY_PRINT); + $this->assertEquals($expected, (string)$this->_response->getBody()); + } +} +``` + +CakePHP の組込み JsonView で、 `debug` が有効になっている場合、 `JSON_PRETTY_PRINT` +オプションを使用します。 + +### ファイルアップロードのテスト + +デフォルトの「[オブジェクトとしてアップロードされたファイル](../controllers/request-response#request-file-uploads)」モードを使用すると、ファイルのアップロードのシミュレーションは簡単です。 [\Psr\Http\Message\UploadedFileInterface](https://www.php-fig.org/psr/psr-7/#16-uploaded-files) (現在CakePHPで使用されているデフォルトの実装は `\Laminas\Diactoros\UploadedFile` )を実装するインスタンスを作成し、それらをテストリクエストデータに渡すだけ。 +CLI環境では、このようなオブジェクトはデフォルトで、ファイルが +HTTP経由でアップロードされたかどうかをテストするバリデーションに合格します。 +`$_FILES` にある配列スタイルのデータには同じことが当てはまらず、バリデーションは失敗します。 + +アップロードされたファイルオブジェクトが通常のリクエストでどのように +存在するかを正確にシミュレートするには、リクエストデータでそれらを +渡すだけでなく、 `files` オプションを介してテストリクエスト構成に +渡す必要があります。ただし、コードが `Cake\Http\ServerRequest::getUploadedFile()` または `Cake\Http\ServerRequest::getUploadedFiles()` メソッドを介して +アップロードされたファイルにアクセスしない限り、技術的には必要ありません。 + +記事にティザー画像と `複数の添付ファイル` の関連付けがあるとして、 +フォームはそれに応じて、1つの画像ファイルと複数の +添付ファイル/ファイルとして受け入れます。: + +``` php +Form->create($article, ['type' => 'file']) ?> +Form->control('title') ?> +Form->control('teaser_image', ['type' => 'file']) ?> +Form->control('attachments.0.attachment', ['type' => 'file']) ?> +Form->control('attachments.0.description']) ?> +Form->control('attachments.1.attachment', ['type' => 'file']) ?> +Form->control('attachments.1.description']) ?> +Form->button('Submit') ?> +Form->end() ?> +``` + +対応するリクエストをシミュレートするテストは、 +次のようになります。: + +``` php +public function testAddWithUploads(): void +{ + $teaserImage = new \Laminas\Diactoros\UploadedFile( + '/path/to/test/file.jpg', // stream or path to file representing the temp file + 12345, // the filesize in bytes + \UPLOAD_ERR_OK, // the upload/error status + 'teaser.jpg', // the filename as sent by the client + 'image/jpeg' // the mimetype as sent by the client + ); + + $textAttachment = new \Laminas\Diactoros\UploadedFile( + '/path/to/test/file.txt', + 12345, + \UPLOAD_ERR_OK, + 'attachment.txt', + 'text/plain' + ); + + $pdfAttachment = new \Laminas\Diactoros\UploadedFile( + '/path/to/test/file.pdf', + 12345, + \UPLOAD_ERR_OK, + 'attachment.pdf', + 'application/pdf' + ); + + // This is the data accessible via `$this->request->getUploadedFile()` + // and `$this->request->getUploadedFiles()`. + $this->configRequest([ + 'files' => [ + 'teaser_image' => $teaserImage, + 'attachments' => [ + 0 => [ + 'attachment' => $textAttachment, + ], + 1 => [ + 'attachment' => $pdfAttachment, + ], + ], + ], + ]); + + // This is the data accessible via `$this->request->getData()`. + $postData = [ + 'title' => 'New Article', + 'teaser_image' => $teaserImage, + 'attachments' => [ + 0 => [ + 'attachment' => $textAttachment, + 'description' => 'Text attachment', + ], + 1 => [ + 'attachment' => $pdfAttachment, + 'description' => 'PDF attachment', + ], + ], + ]; + $this->post('/articles/add', $postData); + + $this->assertResponseOk(); + $this->assertFlashMessage('The article was saved successfully'); + $this->assertFileExists('/path/to/uploads/teaser.jpg'); + $this->assertFileExists('/path/to/uploads/attachment.txt'); + $this->assertFileExists('/path/to/uploads/attachment.pdf'); +} +``` + +> [!TIP] +> ファイルを使用してテストリクエストを構成する場合は、POSTデータの構造と *必ず* 一致する必要があります(ただし、アップロードされたファイルオブジェクトのみが含まれます)。 + +同様に、 [アップロードエラー](https://www.php.net/manual/en/features.file-upload.errors.php) や、 +検証に合格しない無効なファイルをシミュレートできます。: + +``` php +public function testAddWithInvalidUploads(): void +{ + $missingTeaserImageUpload = new \Laminas\Diactoros\UploadedFile( + '', + 0, + \UPLOAD_ERR_NO_FILE, + '', + '' + ); + + $uploadFailureAttachment = new \Laminas\Diactoros\UploadedFile( + '/path/to/test/file.txt', + 1234567890, + \UPLOAD_ERR_INI_SIZE, + 'attachment.txt', + 'text/plain' + ); + + $invalidTypeAttachment = new \Laminas\Diactoros\UploadedFile( + '/path/to/test/file.exe', + 12345, + \UPLOAD_ERR_OK, + 'attachment.exe', + 'application/vnd.microsoft.portable-executable' + ); + + $this->configRequest([ + 'files' => [ + 'teaser_image' => $missingTeaserImageUpload, + 'attachments' => [ + 0 => [ + 'file' => $uploadFailureAttachment, + ], + 1 => [ + 'file' => $invalidTypeAttachment, + ], + ], + ], + ]); + + $postData = [ + 'title' => 'New Article', + 'teaser_image' => $missingTeaserImageUpload, + 'attachments' => [ + 0 => [ + 'file' => $uploadFailureAttachment, + 'description' => 'Upload failure attachment', + ], + 1 => [ + 'file' => $invalidTypeAttachment, + 'description' => 'Invalid type attachment', + ], + ], + ]; + $this->post('/articles/add', $postData); + + $this->assertResponseOk(); + $this->assertFlashMessage('The article could not be saved'); + $this->assertResponseContains('A teaser image is required'); + $this->assertResponseContains('Max allowed filesize exceeded'); + $this->assertResponseContains('Unsupported file type'); + $this->assertFileNotExists('/path/to/uploads/teaser.jpg'); + $this->assertFileNotExists('/path/to/uploads/attachment.txt'); + $this->assertFileNotExists('/path/to/uploads/attachment.exe'); +} +``` + +### テスト中のエラー処理ミドルウェアの無効化 + +アプリケーションにエラーが発生したために失敗したテストをデバッグする場合、 +エラー処理ミドルウェアを一時的に無効にして、根本的なエラーを目立たせることができます。 +これをするために `disableErrorHandlerMiddleware()` が +使用できます。 : + +``` php +public function testGetMissing(): void +{ + $this->disableErrorHandlerMiddleware(); + $this->get('/markers/not-there'); + $this->assertResponseCode(404); +} +``` + +上の例では、テストは失敗し、描画されたエラーページがチェックされる代わりに、 +基本的な例外メッセージとスタックトレースが表示されます。 + +### アサーションメソッド + +`IntegrationTestTrait` トレイトはレスポンスのテストがとても簡単になるアサーションメソッドを +多数提供しています。いくつかの例をあげます。 : + +``` php +// 2xx レスポンスコードをチェック +$this->assertResponseOk(); + +// 2xx/3xx レスポンスコードをチェック +$this->assertResponseSuccess(); + +// 4xx レスポンスコードをチェック +$this->assertResponseError(); + +// 5xx レスポンスコードをチェック +$this->assertResponseFailure(); + +// 指定したレスポンスコードをチェック。例: 200 +$this->assertResponseCode(200); + +// Location ヘッダーをチェック +$this->assertRedirect(['controller' => 'Articles', 'action' => 'index']); + +// Location ヘッダーが設定されていないことをチェック +$this->assertNoRedirect(); + +// Location ヘッダーの一部をチェック +$this->assertRedirectContains('/articles/edit/'); + +// Location ヘッダーが含まれていないことをチェック +$this->assertRedirectNotContains('/articles/edit/'); + +// レスポンスが空ではないことをアサート +$this->assertResponseNotEmpty(); + +// レスポンス内容が空であることをアサート +$this->assertResponseEmpty(); + +// レスポンス内容をアサート +$this->assertResponseEquals('Yeah!'); + +// レスポンス内容が等しくないことをアサート +$this->assertResponseNotEquals('No!'); + +// レスポンス内容の一部をアサート +$this->assertResponseContains('You won!'); +$this->assertResponseNotContains('You lost!'); + +// 返されたファイルをアサート +$this->assertFileResponse('/absolute/path/to/file.ext'); + +// レイアウトをアサート +$this->assertLayout('default'); + +// テンプレートが表示されたかどうかをアサート +$this->assertTemplate('index'); + +// セッション内のデータをアサート +$this->assertSession(1, 'Auth.User.id'); + +// レスポンスヘッダーをアサート +$this->assertHeader('Content-Type', 'application/json'); +$this->assertHeaderContains('Content-Type', 'html'); + +// content-typeのヘッダーにxmlが含まれていないことをアサート +$this->assertHeaderNotContains('Content-Type', 'xml'); + +// ビュー変数をアサート +$user = $this->viewVariable('user'); +$this->assertEquals('jose', $user->username); + +// レスポンス内のクッキーをアサート +$this->assertCookie('1', 'thingid'); + +// コンテンツタイプをチェック +$this->assertContentType('application/json'); +``` + +上記のアサーションメソッドに加えて、 +[TestSuite](https://api.cakephp.org/4.x/class-Cake.TestSuite.TestCase.html) と +[PHPUnit](https://phpunit.de/manual/current/ja/appendixes.assertions.html) の +中にある全てのアサーションを使用することができます。 + +### ファイルへのテスト結果を比較 + +例えば、ビューのレンダリングされた出力をテストする場合 - いくつかのタイプのテストにとっては、 +ファイルの内容とテストの結果を比較する方が簡単かもしれません。 `StringCompareTrait` は、 +この目的のために簡単なアサートメソッドを追加します。 + +使用方法は、トレイトを用いて比較元のパスを設定し、 `assertSameAsFile` を呼び出すことです。 : + +``` php +use Cake\TestSuite\StringCompareTrait; +use Cake\TestSuite\TestCase; + +class SomeTest extends TestCase +{ + use StringCompareTrait; + + public function setUp(): void + { + $this->_compareBasePath = APP . 'tests' . DS . 'comparisons' . DS; + parent::setUp(); + } + + public function testExample(): void + { + $result = ...; + $this->assertSameAsFile('example.php', $result); + } +} +``` + +上記の例では、 `APP/tests/comparisons/example.php` ファイルの内容と +`$result` を比較します。 + +それらが参照されているように、テストの比較ファイルが作成・更新され、環境変数 +`UPDATE_TEST_COMPARISON_FILES` を設定することで、 +テストファイルを更新/書き込みするために +仕組みが提供されています。 + +``` bash +phpunit +... +FAILURES! +Tests: 6, Assertions: 7, Failures: 1 + +UPDATE_TEST_COMPARISON_FILES=1 phpunit +... +OK (6 tests, 7 assertions) + +git status +... +# Changes not staged for commit: +# (use "git add ..." to update what will be committed) +# (use "git checkout -- ..." to discard changes in working directory) +# +# modified: tests/comparisons/example.php +``` + +## コンソールの統合テスト + +シェルとコマンドをテストについては [Console Integration Testing](../console-commands/commands#console-integration-testing) をご覧ください。 + +## Mocking Injected Dependencies + +See [Mocking Services In Tests](../development/dependency-injection#mocking-services-in-tests) for how to replace services injected with +the dependency injection container in your integration tests. + +## ビューのテスト + +一般的に、ほとんどのアプリケーションは、直接 HTML コードをテストしません。そのため、多くの場合、 +テストは壊れやすく、メンテナンスが困難になっています。 `IntegrationTestTrait` を +使用して機能テストを書くときに ‘view’ に `return` オプションを設定することで、 +レンダリングされたビューの内容を調べることができます。 `IntegrationTestTrait` を使用して +ビューのコンテンツをテストすることは可能ですが、より堅牢でメンテナンスしやすい統合/ビューテストは、 +[Selenium webdriver](https://www.selenium.dev/) のようなツールを使うことで実現できます + +## コンポーネントのテスト + +PagematronComponent というコンポーネントがアプリケーションにあったとしましょう。 +このコンポーネントは、このコンポーネントを使用している全てのコントローラーにおいて、 +ページネーションの limit 値を設定することができます。 +**src/Controller/Component/PagematronComponent.php** に置かれた +コンポーネントの例はこちらです。 : + +``` php +class PagematronComponent extends Component +{ + public $controller = null; + + public function setController($controller) + { + $this->controller = $controller; + // コントローラーが、ページネーションを使用していることを確認 + if (!isset($this->controller->paginate)) { + $this->controller->paginate = []; + } + } + + public function startup(EventInterface $event) + { + $this->setController($event->getSubject()); + } + + public function adjust($length = 'short'): void + { + switch ($length) { + case 'long': + $this->controller->paginate['limit'] = 100; + break; + case 'medium': + $this->controller->paginate['limit'] = 50; + break; + default: + $this->controller->paginate['limit'] = 20; + break; + } + } +} +``` + +コンポーネントの中の `adjust()` メソッドによって、ページネーションの +`limit` パラメーターが正しく設定されていることを保証するためのテストを +書くことができます。 +**tests/TestCase/Controller/Component/PagematronComponentTest.php** +ファイルを作成します。 : + +``` php +namespace App\Test\TestCase\Controller\Component; + +use App\Controller\Component\PagematronComponent; +use Cake\Controller\Controller; +use Cake\Controller\ComponentRegistry; +use Cake\Event\Event; +use Cake\Http\ServerRequest; +use Cake\Http\Response; +use Cake\TestSuite\TestCase; + +class PagematronComponentTest extends TestCase +{ + protected $component; + protected $controller; + + public function setUp(): void + { + parent::setUp(); + // コンポーネントと偽のテストコントローラーのセットアップ + $request = new ServerRequest(); + $response = new Response(); + $this->controller = $this->getMockBuilder('Cake\Controller\Controller') + ->setConstructorArgs([$request, $response]) + ->setMethods(null) + ->getMock(); + $registry = new ComponentRegistry($this->controller); + $this->component = new PagematronComponent($registry); + $event = new Event('Controller.startup', $this->controller); + $this->component->startup($event); + } + + public function testAdjust(): void + { + // 異なるパラメーター設定で、adjust メソッドをテスト + $this->component->adjust(); + $this->assertEquals(20, $this->controller->paginate['limit']); + + $this->component->adjust('medium'); + $this->assertEquals(50, $this->controller->paginate['limit']); + + $this->component->adjust('long'); + $this->assertEquals(100, $this->controller->paginate['limit']); + } + + public function tearDown(): void + { + parent::tearDown(); + // 完了後のクリーンアップ + unset($this->component, $this->controller); + } +} +``` + +## ヘルパーのテスト + +相当な量のロジックがヘルパークラスに存在するので、これらのクラスがテストケースによって +カバーされていることを確認することは重要です。 + +はじめに、テストのための例として、ヘルパーを作成します。 `CurrencyRendererHelper` には、 +ビューで通貨の表示を補助するための、 `yen()` という +シンプルなメソッドを記述します。 : + +``` php +// src/View/Helper/CurrencyRendererHelper.php +namespace App\View\Helper; + +use Cake\View\Helper; + +class CurrencyRendererHelper extends Helper +{ + public function yen($amount): string + { + return number_format($amount, 2, '.', ',') . '円'; + } +} +``` + +このメソッドは、小数点以下2桁まで表示し、小数点としてドット、3桁ごとの区切りとして +カンマを使用するフォーマットで数字を表し、さらに ’円’ という文字を数字の末尾に追加します。 + +それではテストを作成します。 : + +``` php +// tests/TestCase/View/Helper/CurrencyRendererHelperTest.php + +namespace App\Test\TestCase\View\Helper; + +use App\View\Helper\CurrencyRendererHelper; +use Cake\TestSuite\TestCase; +use Cake\View\View; + +class CurrencyRendererHelperTest extends TestCase +{ + public $helper = null; + + // ここでヘルパーをインスタンス化 + public function setUp(): void + { + parent::setUp(); + $View = new View(); + $this->helper = new CurrencyRendererHelper($View); + } + + // yen() 関数をテスト + public function testYen(): void + { + $this->assertEquals('5.30円', $this->helper->yen(5.30)); + + // 常に小数第2位まで持つべき + $this->assertEquals('1.00円', $this->helper->yen(1)); + $this->assertEquals('2.05円', $this->helper->yen(2.05)); + + // 桁区切りのテスト + $this->assertEquals( + '12,000.70円', + $this->helper->yen(12000.70) + ); + } +} +``` + +`yen()` を異なるパラメーターで呼び出すことで、このテストスイートは 期待した値と同じ値を +返しているかどうかをアサートすることができます。 + +ファイルに保存しテストを実行します。これにより、緑のバー(※Windowsは非対応)と 1つのテスト、4つのアサーションに +成功したことを指し示すメッセージを見ることができるでしょう。 + +他のヘルパーを使用するヘルパーをテストしている時、View クラスの `loadHelpers` メソッドを +モックにしてください。 + +## イベントのテスト + +[イベントシステム](../core-libraries/events) は、アプリケーションコードを分離する素晴らしい方法ですが、 +テストの際、これらのイベントを実行するテストケース内のイベントの結果をテストすることになりがちです。 +これは、 `assertEventFired` や `assertEventFiredWith` を代わりに使うことで削除ができる、 +余分な結合の一種です。 + +Orders を例に詳しく説明します。以下のテーブルを持っているとします。 : + +``` php +class OrdersTable extends Table +{ + public function place($order): bool + { + if ($this->save($order)) { + // CartsTable へ移されたカートの移動 + $event = new Event('Model.Order.afterPlace', $this, [ + 'order' => $order + ]); + $this->getEventManager()->dispatch($event); + + return true; + } + + return false; + } +} + +class CartsTable extends Table +{ + public function implementedEvents(): array + { + return [ + 'Model.Order.afterPlace' => 'removeFromCart' + ]; + } + + public function removeFromCart(EventInterface $event): void + { + $order = $event->getData('order'); + $this->delete($order->cart_id); + } +} +``` + +> [!NOTE] +> イベントの発生をアサートするために、イベントマネージャー上で最初に [Tracking Events](../core-libraries/events#tracking-events) +> を有効にする必要があります。 + +上記の `OrdersTable` をテストするために、 `setUp()` 内でトラッキングを有効にした後、 +イベントが発生することをアサートし、そして `$order` エンティティーがイベントデータに +渡されることをアサートします。 : + +``` php +namespace App\Test\TestCase\Model\Table; + +use App\Model\Table\OrdersTable; +use Cake\Event\EventList; +use Cake\TestSuite\TestCase; + +class OrdersTableTest extends TestCase +{ + protected $fixtures = ['app.Orders']; + + public function setUp(): void + { + parent::setUp(); + $this->Orders = $this->getTableLocator()->get('Orders'); + // イベントトラッキングの有効化 + $this->Orders->getEventManager()->setEventList(new EventList()); + } + + public function testPlace(): void + { + $order = new Order([ + 'user_id' => 1, + 'item' => 'Cake', + 'quantity' => 42, + ]); + + $this->assertTrue($this->Orders->place($order)); + + $this->assertEventFired('Model.Order.afterPlace', $this->Orders->getEventManager()); + $this->assertEventFiredWith('Model.Order.afterPlace', 'order', $order, $this->Orders->getEventManager()); + } +} +``` + +デフォルトでは、アサーションのためにグローバルな `EventManager` が利用されるため、 +グローバルイベントのテストは、イベントマネージャーに渡す必要はありません。 : + +``` php +$this->assertEventFired('My.Global.Event'); +$this->assertEventFiredWith('My.Global.Event', 'user', 1); +``` + +## メールのテスト + +メールのテストについては [Email Testing](../core-libraries/email#email-testing) をご覧ください。 + +## テストスイートの作成 + +いくつかのテストを同時に実行したいときはテストスイートを作成することができます。 +テストスイートは、いくつかの テストケースから構成されています。アプリケーションの **phpunit.xml** +ファイルにテストスイートを作成することによって実行することができます。簡単な例は次のようになります。 + +``` xml + + + src/Model + src/Service/UserServiceTest.php + src/Model/Cloud/ImagesTest.php + + +``` + +## プラグインのテスト作成 + +プラグインのテストは、プラグインフォルダー内のディレクトリーに作成されます。 : + + /src + /plugins + /Blog + /tests + /TestCase + /Fixture + +それらは通常のテストと同じように動作しますが、別のクラスをインポートする場合、プラグインの命名規則を +使用することを覚えておく必要があります。これは、このマニュアルのプラグインの章から `BlogPost` +モデルのテストケースの一例です。他のテストとの違いは、 'Blog.BlogPost' がインポートされている +最初の行です。プラグインフィクスチャーに `plugin.Blog.BlogPosts` とプレフィックスをつける +必要があります。 : + +``` php +namespace Blog\Test\TestCase\Model\Table; + +use Blog\Model\Table\BlogPostsTable; +use Cake\TestSuite\TestCase; + +class BlogPostsTableTest extends TestCase +{ + // /plugins/Blog/tests/Fixture/ 内のプラグインのフィクスチャーをロード + protected $fixtures = ['plugin.Blog.BlogPosts']; + + public function testSomething(): void + { + // 何らかのテスト + } +} +``` + +アプリのテストにおいてプラグインのフィクスチャーを使用したい場合は、 `$fixtures` 配列に +`plugin.pluginName.fixtureName` 構文を使用して参照することができます。 +さらに、ベンダーのプラグイン名またはフィクスチャーのディレクトリーを使用する場合は、 +`plugin.vendorName/pluginName.folderName/fixtureName` を使用できます: + +フィクスチャーを使用する前に、 `phpunit.xml` に +[fixture listener](#fixture-phpunit-configuration) +が設定されていることを確認してください。 + +また、フィクスチャーがロード可能であることを確認する必要があります。次のように **composer.json** +ファイル内に存在することを確認してください。 : + +``` json +"autoload-dev": { + "psr-4": { + "MyPlugin\\Test\\": "plugins/MyPlugin/tests/" + } +} +``` + +> [!NOTE] +> 新しいオートロードのマッピングを追加するときに `composer.phar dumpautoload` を +> 実行することを忘れないでください。 + +## Bake でのテストの生成 + +スキャフォールディングを生成するために [bake](../bake/usage) を使う場合、 +テストのスタブも生成します。テストケースのスケルトンを再生成する必要がある場合、または、 +書いたコードのテストスケルトンを生成する場合、 `bake` を使用することができます。 + +``` bash +bin/cake bake test +``` + +`` は以下のいずれかである必要があります。 + +1. Entity +2. Table +3. Controller +4. Component +5. Behavior +6. Helper +7. Shell +8. Task +9. ShellHelper +10. Cell +11. Form +12. Mailer +13. Command + +`` は作成したいテストの雛形のオブジェクトの名前です。 diff --git a/docs/ja/elasticsearch.md b/docs/ja/elasticsearch.md new file mode 100644 index 0000000000..c31af6c514 --- /dev/null +++ b/docs/ja/elasticsearch.md @@ -0,0 +1,3 @@ +# ElasticSearch + +このページは [移動しました](https://book.cakephp.org/elasticsearch/3/ja/) 。 diff --git a/docs/ja/epub-contents.md b/docs/ja/epub-contents.md new file mode 100644 index 0000000000..66a82374a4 --- /dev/null +++ b/docs/ja/epub-contents.md @@ -0,0 +1,55 @@ +orphan + +# コンテンツ + +- [CakePHP 概要](intro) +- [クイックスタートガイド](quickstart) +- [移行ガイド](appendices/migration-guides) +- [チュートリアルと例](tutorials-and-examples) +- [貢献](contributing) +- [インストール](installation) +- [構成設定](development/configuration) +- [ルーティング](development/routing) +- [リクエストとレスポンスオブジェクト](controllers/request-response) +- [コントローラー](controllers) +- [ビュー](views) +- [データベースアクセス & ORM](orm) +- [キャッシュ](core-libraries/caching) +- [Bake コンソール](bake) +- [コンソールコマンド](console-commands) +- [デバッグ](development/debugging) +- [デプロイ](deployment) +- [Mailer](core-libraries/email) +- [エラーと例外の処理](development/errors) +- [イベントシステム](core-libraries/events) +- [国際化と地域化](core-libraries/internationalization-and-localization) +- [ロギング](core-libraries/logging) +- [モデルのないフォーム](core-libraries/form) +- [Pagination](controllers/pagination) +- [プラグイン](plugins) +- [REST](development/rest) +- [セキュリティ](security) +- [セッション](development/sessions) +- [テスト](development/testing) +- [バリデーション](core-libraries/validation) +- [Appクラス](core-libraries/app) +- [コレクション](core-libraries/collections) +- [Folder & File](core-libraries/file-folder) +- [Hash](core-libraries/hash) +- [Http Client](core-libraries/httpclient) +- [Inflector](core-libraries/inflector) +- [Number](core-libraries/number) +- [レジストリーオブジェクト](core-libraries/registry-objects) +- [Text](core-libraries/text) +- [日付と時刻](core-libraries/time) +- [Xml](core-libraries/xml) +- [定数および関数](core-libraries/global-constants-and-functions) +- [Chronos](chronos) +- [Debug Kit](debug-kit) +- [Migrations](migrations) +- [ElasticSearch](elasticsearch) +- [付録](appendices) + +
    + +
    diff --git a/docs/ja/index.md b/docs/ja/index.md new file mode 100644 index 0000000000..c09a65eada --- /dev/null +++ b/docs/ja/index.md @@ -0,0 +1,47 @@ +# ようこそ + +CakePHP 5 は、 PHP |phpversion| (最小は PHP |minphpversion|) 上で動作するウェブ開発フレームワークです。 CakePHP の基礎に入門するには [CakePHP 概要](intro) を読んでください。 + +CakePHP クックブックは、オープンに開発されている、コミュニティーで編集可能なドキュメントのプロジェクトです。 +ドキュメンテーションの品質、正当性、正確性の高さを維持することを期待しています。 +右端に固定された鉛筆アイコンボタンに注目してください。 +このボタンは、あなたがドキュメンテーションの追加・削除・修正などの貢献が簡単にできるように、 +該当ページの GitHub のオンラインエディターへ誘導します。 + +
    + +**公式ドキュメントがどこででも読める!** + +![Read the Book - CakePHP](/read-the-book.jpg) + +どこでも CakePHP のレシピをお楽しみいただけます。PDF と EPUB をご用意しましたので、 +多くのデバイス上でオフラインでドキュメントを読むことができます。 + +- [PDF (英語)](../_downloads/en/CakePHPBook.pdf) +- [EPUB](../_downloads/ja/CakePHP.epub) +- [オリジナルソース](https://github.com/cakephp/docs) + +
    + +## 助けを得るには + +もし何か困っていたら、 [いくつもの助けを得られる場所](intro/where-to-get-help) +があります。 + +## はじめに + +新しいフレームワークを学ぶことは、不安と同時にワクワクします。あなたの役に立つために、 +共通のタスクをやり遂げるための例やレシピをまとめたクックブックを作成しました。 +もし、あなたが初心者の場合、 CakePHP は何を提供できて、どのように動作するのかの +クイックツアーを体験するために [クイックスタートガイド](quickstart) から始めましょう。 + +クイックスタートチュートリアルを終えた後、 CakePHP アプリケーションの重要な要素の復習ができます。 + +- [CakePHP のリクエストサイクル](intro#request-cycle) +- CakePHP が使用する [規約](intro/conventions) +- [コントローラー](controllers) は、リクエストを処理し、あなたのモデルと + あなたのアプリケーションが作成したレスポンスを調整します。 +- [ビュー](views) は、あなたのアプリケーションのプレゼンテーション層です。 + あなたのアプリケーションに必要な HTML, JSON, その他の出力を作成する強力な道具になります。 +- [モデル](orm) は、アプリケーションの重要な要素です。 + バリデーションやあなたのアプリケーションのドメインロジックを処理します。 diff --git a/docs/ja/installation.md b/docs/ja/installation.md new file mode 100644 index 0000000000..4885315228 --- /dev/null +++ b/docs/ja/installation.md @@ -0,0 +1,626 @@ +# インストール + +CakePHP は素早く簡単にインストールできます。 +最小構成で必要なものは、ウェブサーバーと CakePHP のコピー、それだけです! +本項では主に(最も一般的である) Apache でのセットアップに主眼を置いていますが、 +CakePHP は nginx や lighttpd や Microsoft IIS のような様々なウェブサーバーで動きます。 + +## システム要件 + +- HTTP サーバー。例: Apache。mod_rewrite が推奨されますが、必須ではありません。 +- PHP |minphpversion| 以上 (PHP |phpversion| も含む) - mbstring PHP エクステンション - intl PHP エクステンション - simplexml PHP エクステンション - PDO PHP エクステンション + +> [!NOTE] +> Laragon / XAMPP / WAMP のいずれでも、mbstring 拡張が初期インストール状態で +> 動きます。 +> +> Laragon / XAMPP では intl 拡張は同梱されていますが、 **php.ini** の `extension=php_intl.dll` +>   のコメントを外して コントロールパネルからサーバーの再起動を行う必要はあります。 +> +> WAMP では intl 拡張は最初からアクティブになっているのですが動作しません。 +> 動作させるためには php フォルダー(初期状態では **C:\wamp\bin\php\php{version}** )にある +> **icu\*.dll** というファイルを全て、apache の bin ディレクトリー +> ( **C:\wamp\bin\apache\apache{version}\bin** )にコピーしてから、 +> 全てのサービスを再起動すれば動くようになります。 + +データベースエンジンは必ずしも必要ではありませんが、ほとんどのアプリケーションは +これを活用することが想像できます。 +CakePHP は種々のデータベース・ストレージのエンジンをサポートしています: + +- MySQL (5.7 以上) +- MariaDB (10.1 以上) +- PostgreSQL (9.6 以上) +- Microsoft SQL Server (2012 以上) +- SQLite 3 + +> [!NOTE] +> 組み込みのドライバーは全て PDO を必要とします。 +> 正しい PDO 拡張モジュールがインストールされているか必ず確かめてください。 + +## CakePHP のインストール + +始める前に、最新の PHP バージョンであることを確認してください。 + +``` bash +php -v +``` + +PHP |minphpversion| (CLI) 以上がインストールされていなければなりません。 ウェブサーバー版の PHP もまた |minphpversion| 以上でなければりませんし、 コマンドラインインターフェース (CLI) 版と同じバージョンを使用してください。 + +### Composer のインストール + +CakePHP の公式のインストール方法として、依存性管理ツール +[Composer](https://getcomposer.org) を使用します。 + +- Linux や macOS に Composer をインストール + + 1. [公式の Composer ドキュメント](https://getcomposer.org/download/) に書かれた + インストーラースクリプトを実行し、Composer をインストールするために指示に従ってください。 + + 2. composer.phar を指定したパスのディレクトリーに移すために以下のコマンドを実行してください。 : + + mv composer.phar /usr/local/bin/composer + +- Windows に Composer をインストール + + Windows 環境なら、 [こちら](https://github.com/composer/windows-setup/releases/) から + Windows インストーラーをダウンロードできます。Composer の Windows インストーラーについての詳細は、 + [README](https://github.com/composer/windows-setup) をご覧ください。 + +### CakePHP プロジェクトを作成 + +以上で、Composer をダウンロードとインストールしましたので、 my_app_name フォルダーに +CakePHP の新しいアプリケーションを作成してください。下記の composer コマンドを実行して作成します。 + +``` bash +php composer.phar create-project --prefer-dist cakephp/app:"4.*" my_app_name +``` + +または Composer にパスが通っているのであれば下記のコマンドも使えます。 + +``` bash +composer self-update && composer create-project --prefer-dist cakephp/app:"4.*" my_app_name +``` + +一度 Composer がアプリケーションの雛形とコアライブラリーをダウンロードしたら、 +インストールした CakePHP アプリケーションを Composer から操作できるように +しておくべきです。 +必ず composer.json と composer.lock ファイルは残しておきましょう。 + +これでインストールした CakePHP アプリケーションにアクセスして、デフォルトの +ホームページを見ることができるようになりました。このページの内容を変更するには +**templates/Pages/home.php** を編集してください。 + +composer によるインストールが推奨されますが、 +[Github](https://github.com/cakephp/cakephp/tags) +にはプリインストール版もあります。 +このファイルにはアプリケーションの雛形と全てのベンダーパッケージが同梱されています。 +また、 `composer.phar` も入っていますので、あなたのさらなる使用のために必要なものは +全てそろっているのです。 + +### CakePHP の変更に合わせて最新の状態に保つ + +デフォルトではあなたのアプリケーションの **composer.json** は下記のようになっています。 : + +``` json +"require": { + "cakephp/cakephp": "4.0.*" +} +``` + +`php composer.phar update` を実行するたびに、このマイナーバージョンの +パッチリリースが手に入ります。代わりに `^4.0` に変更して、 `4.x` ブランチの +最新の安定版マイナーリリースを手に入れることができます。 + +### Oven を使用したインストール + +CakePHP を手軽にインストールするための別の方法は、 [Oven](https://github.com/CakeDC/oven) です。 +これは、必要なシステム要件をチェック、CakePHP アプリケーションのスケルトンをインストール、そして、 +開発環境をセットアップするシンプルな PHP スクリプトです。 + +インストールが完了すれば、あなたの CakePHP アプリケーションはすぐに使えます! + +> [!NOTE] +> 重要: これはデプロイスクリプトではありません。はじめて CakePHP をインストールする開発者を助け、 +> 開発環境を素早くセットアップすることが狙いです。本番環境では、ファイルのパーミッション、 +> バーチャルホストの設定など、いくつかの要因を考慮する必要があります。 + +## パーミッション + +CakePHP は、幾つかの操作のために **tmp** ディレクトリーを使用します。 +モデルの定義や、ビューのキャッシュ、セッション情報などです。 +**logs** ディレクトリーは、デフォルトの `FileLog` エンジンがログファイルを +出力するために使われます。 + +そのため、 CakePHP をインストールしたら **logs**, **tmp** ディレクトリーと +その全てのサブディレクトリーに、ウェブサーバーの実行ユーザーによる書き込み権限があることを +必ず確認してください。composer によるインストール処理では、なるべく早く動かせるように +**tmp** フォルダーとそのサブフォルダーに全ユーザーが書き込みできるようにしますが、 +これをウェブサーバーの実行ユーザーだけが書き込みできるようにパーミッション設定を変更すれば、 +より良いセキュリティ状態にすることができます。 + +よくある課題として、 **logs** と **tmp** ディレクトリーとサブディレクトリーは、ウェブサーバーと +コマンドラインユーザーの両方で書き込み権限が必要、ということがあります。 +UNIX システム上で ウェブサーバーユーザーとコマンドラインユーザーが異なる場合、 +パーミッションのプロパティー設定を確保するために、あなたのプロジェクトのアプリケーション +ディレクトリーで一度だけ以下のコマンドを実行してください。 + +``` bash +HTTPDUSER=`ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\ -f1` +setfacl -R -m u:${HTTPDUSER}:rwx tmp +setfacl -R -d -m u:${HTTPDUSER}:rwx tmp +setfacl -R -m u:${HTTPDUSER}:rwx logs +setfacl -R -d -m u:${HTTPDUSER}:rwx logs +``` + +CakePHP コンソールツールを使用するためには、 `bin/cake` ファイルが +実行可能である必要があります。 \*nix または macOS 上では、以下を実行します。 + +``` bash +chmod +x bin/cake +``` + +Windows 上では、 **.bat** ファイルはすでに実行可能なはずです。もし、Vagrant または、 +そのほかの仮想化環境を使用している場合、共有ディレクトリーが実行可能なパーミッションで +共有される必要があります。 (設定方法は仮想化環境のドキュメントを参照してください。) + +もし、なんらかの理由で、 `bin/cake` ファイルのパーミッションを変更できない場合、 +CakePHP コンソールは、以下のように実行できます。 + +``` bash +php bin/cake.php +``` + +## 開発サーバー + +開発用インストールは、CakePHP を最も速くインストールする方法です。 +この例では、CakePHP のコンソールを使って PHP の組み込みウェブサーバーを起動して、 +あなたのアプリケーションに **http://host:port** という形式でアクセスできるように +します。app ディレクトリーで下記のコマンドを実行しましょう。 + +``` bash +bin/cake server +``` + +引数のないデフォルト状態では、 **http://localhost:8765/** であなたのアプリケーションに +アクセスできます。 + +もしあなたの環境で **localhost** や 8765番ポートが使用済みなら、CakePHP のコンソールから +下記のような引数を使って特定のホスト名やポート番号でウェブサーバーを起動することができます。 + +``` bash +bin/cake server -H 192.168.13.37 -p 5673 +``` + +こうすればあなたのアプリケーションには **http://192.168.13.37:5673/** でアクセスできます。 + +これだけです! +あなたの CakePHP アプリケーションは ウェブサーバーを設定することなく動きます。 + +> [!NOTE] +> サーバーが他のホストから到達できない場合、 `bin/cake server -H 0.0.0.0` を試してください。 + +> [!WARNING] +> 開発サーバーは公開環境に使用するべきでは *ありません* 。 +> これはあくまでも基本的な開発サーバーと位置付けられています。 + +もしあなたが本物のウェブサーバーを使いたいのであれば、インストールした CakePHP のファイルを +(隠しファイルも含めて)ウェブサーバーのドキュメントルート配下に移動させます。 +これでブラウザーから移動先のディレクトリーを指定すれば、あなたのアプリケーションに +アクセスすることができます。 + +## 公開用 + +公開用インストールは、さらに柔軟に CakePHP をセットアップする方法です。 +この方法を使えば、全てのドメインで1つの CakePHP アプリケーションを使う事も可能です。 +今回の例では、あなたがファイルシステムのどこに CakePHP をインストールしたとしても、 + といったようにアクセスできるようになるでしょう。 +Apache ウェブサーバーでこの方法を使う場合は、 `DocumentRoot` を変更する権限が必要に +なるかもしれないことに注意が必要です。 + +これまでに紹介したいずれかの方法で、あなたが指定したディレクトリー(ここでは +「/cake_install」を指定したとしましょう)にアプリケーションをインストールしたら、 +あなたのファイルシステムには下記のような環境ができているでしょう。 : + +``` text +/cake_install/ + bin/ + config/ + logs/ + plugins/ + resources/ + src/ + templates/ + tests/ + tmp/ + vendor/ + webroot/ (このディレクトリーが DocumentRoot になります) + .gitignore + .htaccess + .travis.yml + composer.json + index.php + phpunit.xml.dist + README.md +``` + +Apache を利用している開発者は、当該ドメインの `DocumentRoot` ディレクティブに +下記のように指定します。 + +``` apache +DocumentRoot /cake_install/webroot +``` + +あなたのウェブサーバーが正しく設定されていれば、これで から +あなたの CakePHP アプリケーションにアクセスできるようになります。 + +## 始動 + +さぁ、CakePHP の動作を見てみましょう。あなたが選んだ方法に応じて、ブラウザーから + あるいは にアクセスしてください。 +これで CakePHP のデフォルトのホーム画面と、データベースへの接続状態を表すメッセージが +表示されるでしょう。 + +おつかれさまです! これでもう [最初の CakePHP アプリケーション作成](quickstart) +の準備ができました。 + + + +## URL Rewriting + +### Apache + +CakePHP は、展開した状態では mod_rewrite を使用するようになっており、自分のシステムで +うまく動作するまで苦労するユーザーもいます。 + +ここでは、正しく動作させるために行うことをいくつか示します。 +まず始めに httpd.conf を見てください(ユーザーやサイト個別の httpd.conf ではなく、 +必ずシステムの httpd.conf を編集してください)。 + +これらのファイルはディストリビューションや Apache のバージョンによって大きく異なります。 +詳細については を見てもよいかも +しれません。 + +1. 適切な DocumentRoot に対して .htaccess による設定の上書きを許可するよう、 + AllowOverride に All が設定されている事を確認します。 + これは下記のように書かれているでしょう。 + + ``` apache + # Each directory to which Apache has access can be configured with respect + # to which services and features are allowed and/or disabled in that + # directory (and its subdirectories). + # + # First, we configure the "default" to be a very restrictive set of + # features. + + Options FollowSymLinks + AllowOverride All + # Order deny,allow + # Deny from all + + ``` + +2. 下記のように mod_rewrite が正しくロードされている事を確認します。 + + ``` apache + LoadModule rewrite_module libexec/apache2/mod_rewrite.so + ``` + + 多くのシステムでこれらはデフォルトではコメントアウトされているでしょうから、 + 先頭の「#」の文字を削除する必要があります。 + + 変更した後は、設定変更を反映するために Apache を再起動してください。 + + .htaccess ファイルが正しいディレクトリーにあることを確認してください。 + 一部のOSでは、ファイル名が「.」から始まるファイルは隠しファイルとみなされ、 + コピーされないでしょう。 + +3. サイトのダウンロードページや Git リポジトリーからコピーした CakePHP が正しく + 解凍できているか、 .htaccess ファイルをチェックします。 + + CakePHP のアプリケーションディレクトリー(あなたが Bake でコピーした一番上の + ディレクトリー)にはこのように書いてあります。 + + ``` apache + + RewriteEngine on + RewriteRule ^$ webroot/ [L] + RewriteRule (.*) webroot/$1 [L] + + ``` + + webroot ディレクトリーにはこのように書いてあります。 + + ``` apache + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + + ``` + + まだあなたの CakePHP サイトで mod_rewrite の問題が起きているなら、 + 仮想ホスト (virtualhosts) の設定の変更を試してみるといいかもしれません。 + Ubuntu 上なら、\*\*/etc/apache2/sites-available/default\*\* (場所は + ディストリビューションによる)のファイルを編集してください。 + このファイルの中で `AllowOverride None` が `AllowOverride All` + に変更されているかを確かめてください。 つまり以下のようになるでしょう。 + + ``` apache + + Options FollowSymLinks + AllowOverride All + + + Options FollowSymLinks + AllowOverride All + Order Allow,Deny + Allow from all + + ``` + + macOS 上での別解は、仮想ホストをフォルダーに向けさせるのに、 + [virtualhostx](https://clickontyler.com/virtualhostx/) + ツールを使うことが挙げられます。 + + 多くのホスティングサービス (GoDaddy、1and1) では、ウェブサーバーが + 既に mod_rewrite を使っているユーザーディレクトリーから配信されます。 + CakePHP をユーザーディレクトリー () または + 既に mod_rewrite を活用しているその他の URL 構造にインストールしているなら、 + RewriteBase ステートメントを CakePHP が使う .htaccess ファイル + (/.htaccess、/app/.htaccess、/app/webroot/.htaccess) に追加する必要があります。 + + これは RewriteEngine ディレクティブと同じセクションに追加でき、 + 例えば webroot の .htaccess ファイルは以下のようになります。 + + ``` apache + + RewriteEngine On + RewriteBase /path/to/app + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + + ``` + + この変更の詳細はあなたの環境構成に依存しますので、CakePHP と関係ない内容が + 含まれることがあります。 + 詳しくは Apache のオンラインドキュメントを参照するようにしてください。 + +4. (オプション) 公開環境の設定では、必要ないリクエストは CakePHP で処理されないようにしましょう。 + webroot の .htaccess ファイルを次のように修正してください。 + + ``` apache + + RewriteEngine On + RewriteBase /path/to/app/ + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_URI} !^/(webroot/)?(img|css|js)/(.*)$ + RewriteRule ^ index.php [L] + + ``` + + 上の例は、正しくないアセットを index.php へ送信せず、ウェブサーバーの 404 ページを表示します。 + + また、HTML で 404 ページを作成することもできますし、 `ErrorDocument` ディレクティブへ + 追記することで、CakePHP のビルトインの 404 ページを使うこともできます。 + + ``` apache + ErrorDocument 404 /404-not-found + ``` + +### nginx + +nginx は Apache のような .htaccess ファイルを利用しませんので、 +サイトの設定で URLの書き換えルールを作成する必要があります。 +これは大抵 `/etc/nginx/sites-available/your_virtual_host_conf_file` に記載します。 +あなたの環境構成に応じて、このファイルを書き換えなければなりませんが、 +少なくとも PHP を FastCGI として稼働させる必要はあるでしょう。 +下記の設定は、リクエストを `webroot/index.php` にリダイレクトします。 + +``` nginx +location / { + try_files $uri $uri/ /index.php?$args; +} +``` + +server ディレクティブの例は、次の通りです。 + +``` nginx +server { + listen 80; + listen [::]:80; + server_name www.example.com; + return 301 http://example.com$request_uri; +} + +server { + listen 80; + listen [::]:80; + server_name example.com; + + root /var/www/example.com/public/webroot; + index index.php; + + access_log /var/www/example.com/log/access.log; + error_log /var/www/example.com/log/error.log; + + location / { + try_files $uri $uri/ /index.php?$args; + } + + location ~ \.php$ { + try_files $uri =404; + include fastcgi_params; + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_intercept_errors on; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + } +} +``` + +> [!NOTE] +> 最近の PHP-FPM の設定では、アドレス 127.0.0.1 の TCP 9000 ポートの代わりに unix php-fpm +> ソケットを待ち受けるように設定します。もし、上記の設定で 502 bad gateway エラーになった場合、 +> TCP ポートの代わりに unix ソケットパスを使用するために `fastcgi_pass` を更新してください +> (例: fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;)。 + +### NGINX Unit + +[NGINX Unit](https://unit.nginx.org) is dynamically configurable in runtime; +the following configuration relies on `webroot/index.php`, also serving other +`.php` scripts if present via `cakephp_direct`: + +``` json +{ + "listeners": { + "*:80": { + "pass": "routes/cakephp" + } + }, + + "routes": { + "cakephp": [ + { + "match": { + "uri": [ + "*.php", + "*.php/*" + ] + }, + + "action": { + "pass": "applications/cakephp_direct" + } + }, + { + "action": { + "share": "/path/to/cakephp/webroot/", + "fallback": { + "pass": "applications/cakephp_index" + } + } + } + ] + }, + + "applications": { + "cakephp_direct": { + "type": "php", + "root": "/path/to/cakephp/webroot/", + "user": "www-data" + }, + + "cakephp_index": { + "type": "php", + "root": "/path/to/cakephp/webroot/", + "user": "www-data", + "script": "index.php" + } + } +} +``` + +To enable this config (assuming it's saved as `cakephp.json`): + +``` bash +# curl -X PUT --data-binary @cakephp.json --unix-socket \ + /path/to/control.unit.sock http://localhost/config +``` + +### IIS7 (Windows hosts) + +IIS7 はネイティブで .htaccess ファイルをサポートしていません。 +このサポートを追加できるアドオンがありますが、CakePHP のネイティブな書き換えを使うように +IIS に htaccess のルールをインポートすることもできます。 +これをするには、以下のステップを踏んでください: + +1. URL [Rewrite Module 2.0](https://www.iis.net/downloads/microsoft/url-rewrite) + をインストールするために、\`Microsoftの Web Platform Installer \\`\_ + を使うか、直接ダウンロードします。([32ビット](https://download.microsoft.com/download/D/8/1/D81E5DD6-1ABB-46B0-9B4B-21894E18B77F/rewrite_x86_en-US.msi) / + [64ビット](https://download.microsoft.com/download/1/2/8/128E2E22-C1B9-44A4-BE2A-5859ED1D4592/rewrite_amd64_en-US.msi)) +2. CakePHP のルートフォルダーに web.config という名前の新しいファイルを作成してください。 +3. メモ帳か XML が編集可能なエディターを使って、以下のコードを今作った web.config ファイルに + コピーしてください。 + +``` xml + + + + + + + + + + + + + + + + + + + + + +``` + +いったん IIS で利用可能な書き換えルールを含む web.config ファイルができたら、 +CakePHP のリンク、CSS、JavaScript、再ルーティング (rerouting) は正しく動作するでしょう。 + +### Lighttpd + +Lighttpd does not make use of **.htaccess** files like Apache, so it is +necessary to add a `url.rewrite-once` configuration in **conf/lighttpd.conf**. +Ensure the following is present in your lighthttpd configuration: + +``` php +server.modules += ( + "mod_alias", + "mod_cgi", + "mod_rewrite" +) + +# Directory Alias +alias.url = ( "/TestCake" => "C:/Users/Nicola/Documents/TestCake" ) + +# CGI Php +cgi.assign = ( ".php" => "c:/php/php-cgi.exe" ) + +# Rewrite Cake Php (on /TestCake path) +url.rewrite-once = ( + "^/TestCake/(css|files|img|js|stats)/(.*)$" => "/TestCake/webroot/$1/$2", + "^/TestCake/(.*)$" => "/TestCake/webroot/index.php/$1" +) +``` + +The above lines include PHP CGI configuration and example application +configuration for an application on the `/TestCake` path. + +### URL リライティングを使わない場合 + +もしあなたのサーバーで mod_rewrite (かそれと互換性のあるモジュール) を使いたくなかったり +使えない場合は、 CakePHP の組み込みのままの URL を使う必要があります。 +**config/app.php** の下記のコメントを解除します。 : + +``` text +'App' => [ + // ... + // 'baseUrl' => env('SCRIPT_NAME'), +] +``` + +そして、下記の .htaccess ファイルを削除します。 : + + /.htaccess + webroot/.htaccess + +これで URL は www.example.com/controllername/actionname/param ではなく +www.example.com/index.php/controllername/actionname/param という書式になるでしょう。 diff --git a/docs/ja/intro.md b/docs/ja/intro.md new file mode 100644 index 0000000000..473e3f122e --- /dev/null +++ b/docs/ja/intro.md @@ -0,0 +1,146 @@ +# CakePHP 概要 + +CakePHP はウェブ開発を単純に簡単にできるように開発されました。 オールインワンの +ツールボックスは色々なパーツが一緒に動いたり、バラバラに動いたりできるようにします。 + +この概要の目的は、CakePHP の一般的なコンセプトとそのコンセプトがどのように CakePHP +の中で働くのかを紹介することです。 プロジェクトをすぐに始めたいなら、 [チュートリアルから始める](tutorials-and-examples/cms/installation) か [直接ドキュメントを見て下さい](topics) 。 + +## 設定より規約 + +CakePHP は基礎的な構造をクラス名、ファイル名、DB のテーブル名や他の規約から決定します。 +規約を学ぶことで、不必要な設定や他の一般的なアプリと同じ構造をいちいち書かなくて済むので、 +簡単に色々なプロジェクトを進められます。この [規約](intro/conventions) は、 +いろいろな CakePHP で使う規約をカバーしています。 + +## モデル層 + +モデル層はビジネスロジックを実装するアプリケーションの部品を表します。アプリケーションにおいて +データを取得し、それを最初の意味ある形に変換する役割を担います。これには、加工、検証 (*validating*) 、 +関連付け (*associating*) 、あるいはデータの処理に関係のあるその他のタスクが含まれます。 + +ソーシャルネットワークの場合では、モデル層はユーザーのデータの保存、友人の繋がりの保存、ユーザーの +写真の保管と取得、新しい友人候補の検出などのタスクを引き受けることでしょう。このとき、 +モデルオブジェクトは「友達 (*Friend*)」、「ユーザー (*User*)」、「コメント (*Comment*)」、あるいは +「写真 (*Photo*)」と考えることができます。もし `users` テーブルからデータを読み出したいのであれば +次のようにできます。 : + +``` php +use Cake\ORM\Locator\LocatorAwareTrait; + +$users = $this->getTableLocator()->get('Users'); +$resultset = $users->find()->all(); +foreach ($resultset as $row) { + echo $row->username; +} +``` + +データの処理を始める前に一切コードを書く必要がなかったことに気付いたかもしれません。 +規約を使うことによって、CakePHP はテーブルとエンティティーのクラスを定義していない場合には +標準のクラスを使用します。 + +新しいユーザーを作成し、それを (検証して) 保存したい場合には次のようにします。 : + +``` php +use Cake\ORM\Locator\LocatorAwareTrait; + +$users = $this->getTableLocator()->get('Users'); +$user = $users->newEntity(['email' => 'mark@example.com']); +$users->save($user); +``` + +## ビュー層 + +ビュー層は、モデルから来たデータをレンダリングします。ビューはモデルオブジェクトとは別に存在します。 +そして、扱っている情報に対してレスポンシブルなアプリケーションが必要としている表示インターフェイスを +すべて提供可能です。 + +例えば、このビューはモデルのデータを利用して HTML ビューテンプレートや他で利用するための +XML 形式の結果をレンダリングできます。 : + +``` php +// ビューテンプレートファイルで 'element' をそれぞれのユーザーに対してレンダリングする + +
  • + element('user_info', ['user' => $user]) ?> +
  • + +``` + +このビュー層は [View Templates](views#view-templates) や [View Elements](views#view-elements) や [ビューセル](views/cells) +のようなしくみで表示のためのロジックを再利用可能にして、 沢山の表示を拡張するための機能を提供します。 + +ビュー層は HTML やテキストのレンダリングを制御出来るだけではなく、一般的な JSON や XML、 +加えてプラグインで追加可能なアーキテクチャによるフォーマットなら何にでも対応します。 + +## コントローラーレイヤー + +コントローラー層はユーザーからのリクエストを扱います。これはモデル層とビュー層の助けを借りてレスポンスを +レンダリングして返す責任を負います。 + +コントローラーは、タスクを終える為の全ての必要とされるリソースが正しいワーカーに委譲されることに注意を払う +マネージャーと見ることができます。 +クライアントからの要求を待ち、認証と承認のルールによる検証を行い、データの取得または処理をモデルに委譲し、 +クライアントが受け入れる適切な表示上のデータの種類を採択し、最終的にその描画処理をビュー層に委譲します。 +例えば、ユーザー登録ではこのようになります。 : + +``` php +public function add() +{ + $user = $this->Users->newEmptyEntity(); + if ($this->request->is('post')) { + $user = $this->Users->patchEntity($user, $this->request->getData()); + if ($this->Users->save($user, ['validate' => 'registration'])) { + $this->Flash->success(__('You are now registered.')); + } else { + $this->Flash->error(__('There were some problems.')); + } + } + $this->set('user', $user); +} +``` + +明示的にビューをレンダリングしないことに気付くかもしれません。 CakePHP は規約によって正しいビューを選択し、 +`set()` で用意したビューデータでそのビューをレンダリングします。 + +## CakePHP のリクエストサイクル + +色々なレイヤーに親しんでいただきました。次は、リクエストサイクルがどのように働くのか見て行きましょう: + +
    +Flow diagram showing a typical CakePHP request +
    + +典型的な CakePHP のリクエストサイクルはユーザーがアプリケーション内でページまたはリソースにリクエストを +投げるところから始まります。上位レベルの各リクエストは以下のステップを実行します。 + +1. ウェブサーバーが **webroot/index.php** へのリクエストを制御するルールを書き換えます。 +2. あなたのアプリケーションがロードされ、 `HttpServer` にひも付きます。 +3. あなたのアプリケーションのミドルウェアが初期化されます。 +4. リクエストとレスポンスは、あなたのアプリケーションで使用する PSR-7 ミドルウェアを経由して + ディスパッチされます。これは、一般的にエラートラップとルーティングを含みます。 +5. ミドルウェアからレスポンスが返らない場合やリクエストがルーティング情報を含む場合、 + コントローラーとアクションが選択されます。 +6. コントローラーのアクションが呼ばれ、コントローラーが要求されたモデルとコンポーネントと通信します。 +7. コントローラーが出力を生成するためにレスポンスの生成をビューに委任します。 +8. ビューがヘルパーとセルを使ってボディーとヘッダーを生成して返す。 +9. レスポンスは、 [ミドルウェア](controllers/middleware) を経由して送信されます。 +10. `HttpServer` は、ウェブサーバーにレスポンスを送ります。 + +## さっそく始めましょう + +この文章があなたの興味を惹くことを願っています。CakePHP には他にもとてもいい特徴があります。 + +- Memcached, Redis や他のバックエンドと統合された [キャッシュ](core-libraries/caching) + フレームワーク。 +- 強力な [コード生成ツール bake](bake/usage) ですぐに簡単なモックを作ってプロジェクトを始める。 +- [統合されたテストフレームワーク](development/testing) でコードが完璧に動いているか確かめられる。 + +次の明白なステップは [download CakePHP](installation) で, +[チュートリアルとなにかすごいものを作る](tutorials-and-examples/cms/installation) を読んで下さい。 + +## 付録 + +- [情報の探し方](intro/where-to-get-help) +- [CakePHP の規約](intro/conventions) +- [CakePHP のフォルダー構成](intro/cakephp-folder-structure) diff --git a/docs/ja/intro/cakephp-folder-structure.md b/docs/ja/intro/cakephp-folder-structure.md new file mode 100644 index 0000000000..b1e3f17096 --- /dev/null +++ b/docs/ja/intro/cakephp-folder-structure.md @@ -0,0 +1,70 @@ +# CakePHP のフォルダー構成 + +CakePHP アプリケーションスケルトンをダウンロードすると、次のようないくつかのトップレベルのフォルダーが +あるはずです。 + +- *bin* フォルダーには実行可能な Cake コンソールを保持します。 + +- *config* フォルダーは、CakePHP が使用する [構成設定](../development/configuration) + ファイルが入る場所です。データーベース接続の詳細、ブートストラップ、 + コアの設定ファイルなどがここに格納されます。 + +- *plugins* フォルダーは、あなたのアプリケーションが使う [プラグイン](../plugins) が格納されます。 + +- *logs* フォルダーは、通常、ログ設定に応じたログファイルが含まれます。 + +- *src* フォルダーは、あなたのアプリケーションのファイルが配置される場所です。 + +- *templates* フォルダーは、次のプレゼンテーションファイルが格納されます。: + エレメント、エラーページ、レイアウト、ビューテンプレートファイル。 + +- *resources* フォルダーには、様々な種類のリソースファイルのサブフォルダーがあります。 + *locales* サブフォルダーには、国際化のための言語ファイルを格納します。 + +- *tests* フォルダーは、あなたのアプリケーションのテストケースを置く場所です。 + +- *tmp* フォルダーは、CakePHP が一時的なデータを格納する場所です。 + 格納する実際のデータは、CakePHP の設定方法によって異なりますが、このフォルダーは、通常、 + 翻訳メッセージ、モデルの詳細、および時にはセッション情報を格納するために使用されます。 + +- *vendor* フォルダーは、CakePHP と他のアプリケーションの依存ライブラリーが [Composer](https://getcomposer.org) によってインストールされる場所です。これらのファイルを + 編集することは推奨されません。次回の更新時に Composer があなたの変更を上書きしてしまうからです。 + +- *webroot* ディレクトリーは、あなたのアプリケーションのパブリックドキュメントルートです。 + それはあなたが公開したいすべてのファイルを含みます。 + + *tmp* フォルダーと *logs* フォルダーは存在していてかつ書き込み可能な状態にしておいてください。 + そうでない場合、あなたのアプリケーションのパフォーマンスに影響を及ぼす場合があります。 + これらのディレクトリーが書き込み可能ではない場合、デバッグモードでは CakePHP は警告を出します。 + +## src フォルダー + +アプリケーション開発の大部分は、CakePHP の *src* フォルダー内で行われます。 +*src* 内のフォルダーを少し詳しく見てみましょう。 + +Command +アプリケーションのコンソールコマンドを含みます。 +更に学ぶためには、 [コマンドオブジェクト](../console-commands/commands) をご覧ください。 + +Console +Composer によって実行されるインストールスクリプトを含みます。 + +Controller +アプリケーションの [コントローラー](../controllers) とコンポーネントを含みます。 + +Middleware +アプリケーションの [ミドルウェア](../controllers/middleware) を格納します。 + +Model +アプリケーションのテーブル、エンティティー、ビヘイビアーを含みます。 + +Shell +アプリケーションのシェルタスクを含みます。 +更に学ぶためには、 [シェル](../console-commands/shells) をご覧ください。 + +View +表示用のクラスが、ここに配置されます。ビュー、セル、ヘルパーなどです。 + +> [!NOTE] +> `Shell` フォルダーは、デフォルトでは存在しません。 +> 必要に応じて追加することができます。 diff --git a/docs/ja/intro/conventions.md b/docs/ja/intro/conventions.md new file mode 100644 index 0000000000..751d3eeef2 --- /dev/null +++ b/docs/ja/intro/conventions.md @@ -0,0 +1,266 @@ +# CakePHP の規約 + +私たちは「設定より規約」(*convention over configuration*) という考え方に賛成です。 +CakePHP の規約を習得するには少し時間がかかりますが、長い目で見ると時間を節約していることになります。 +規約に従うと自由に使える機能が増えますし、設定ファイルを調べまわってメンテナンスするという悪夢からも +開放されます。 規約によって開発が統一感を持つため、開発者が加わってすぐに手伝うということがやりやすく +なります。 + +## コントローラーの規約 + +コントローラーのクラス名は複数形でパスカルケースで、最後に `Controller` が付きます。 +`UsersController` 、 `ArticleCategoriesController` は規約に合ったコントローラー名の例 +となります。 + +コントローラーにある public メソッドは、アクションとしてブラウザーからアクセス可能になります。 +例えば、 `/users/view` は `UsersController` の `view()` メソッドにアクセスします。 +protected メソッドや private メソッドはルーティングしてアクセスすることはできません。 + +### コントローラー名と URL + +前節の通り、ひとつの単語からなる名前のコントローラーは、簡単に小文字の URL パスにマップできます。 +例えば、 `UsersController` (ファイル名は **UsersController.php**)には、 +`http://example.com/users` としてアクセスできます。 + +複数語のコントローラーをあなたの好きなようにルーティングできますが、 +`DashedRoute` クラスを使用すると URL は小文字とダッシュを用いる規約であり、 +`ArticleCategoriesController::viewAll()` アクションにアクセスするための正しい形式は +`/article-categories/view-all` となります。 + +`$this->Html->link` を使用してリンクを作成した時、URL 配列に以下の規約を使用できます。 : + +``` php +$this->Html->link('link-title', [ + 'prefix' => 'MyPrefix', // パスカルケース + 'plugin' => 'MyPlugin', // パスカルケース + 'controller' => 'ControllerName', // パスカルケース + 'action' => 'actionName' // キャメルバック +] +``` + +CakePHP の URL とパラメーターの取り扱いに関するより詳細な情報は、 +[Routes Configuration](../development/routing#routes-configuration) をご覧ください。 + +## ファイルとクラス名の規約 + +通常、ファイル名はクラス名と一致し、オートローディングのために PSR-4 +標準に準拠してください。以下に、クラス名とファイル名の例を挙げます。 + +- `LatestArticlesController` というコントローラークラスは、 + **LatestArticlesController.php** というファイル名にします。 +- `MyHandyComponent` というコンポーネントクラスは、 + **MyHandyComponent.php** というファイル名にします。 +- `OptionValuesTable` という Table クラスは、 + **OptionValuesTable.php** というファイル名にします。 +- `OptionValue` という Entity クラスは、 + **OptionValue.php** というファイル名にします。 +- `EspeciallyFunkableBehavior` というビヘイビアークラスは、 + **EspeciallyFunkableBehavior.php** というファイル名にします。 +- `SuperSimpleView` というビュークラスは、 + **SuperSimpleView.php** というファイル名にします。 +- `BestEverHelper` というヘルパークラスは、 + **BestEverHelper.php** というファイル名にします。 + +各ファイルは、 app フォルダー内の適切なフォルダー・名前空間の中に配置します。 + +## データベースの規約 + +CakePHP のモデルに対応するテーブル名は、複数形でアンダースコア記法です。上記の例で言えば、 +テーブル名はそれぞれ、 `users` 、 `article_categories` 、 `user_favorite_pages` +になります。 + +二個以上の単語で構成されるフィールド/カラムの名前は、 +`first_name` のようにアンダースコア記法になります。 + +hasMany, blongsTo, hasOne 中の外部キーは、デフォルトで関連するモデルの(単数形の)名前に +`_id` を付けたものとして認識されます。ユーザーが記事を複数持っている (*Users hasMany Articles*) +としたら、 `articles` テーブルは、 `user_id` を外部キーとして `users` テーブルのデータを +参照します。 `article_categories` のような複数の単語のテーブルでは、外部キーは +`article_category_id` のようになるでしょう。 + +モデル間の BelongsToMany の関係で使用される join テーブルは、結合するテーブルに合わせて、 +アルファベット順に (`tags_articles` ではなく、 `articles_tags`) 並べた名前にしてください。 +そうでなければ bake コマンドは動作しません。連結用テーブルにカラムを追加する必要がある場合は、 +そのテーブル用に別のエンティティークラスやテーブルクラスを作成する必要があります。 + +主キーとしてオートインクリメントな整数型を使用することに加えて UUID カラムも使用できます。 +`Table::save()` メソッドを使って新規レコードを保存するとき、CakePHP はユニークな +36 文字の UUID (`Cake\Utilitiy\Text::uuid`) を用いようとします。 + +## モデルの規約 + +Table クラスの名前は複数形でパスカルケースで、最後に `Table` が付きます。 `UsersTable`, +`ArticleCategoriesTable`, `UserFavoritePagesTable` などは `users`, +`article_categories`, `user_favorite_pages` テーブルに対応するテーブルクラス名の例です。 + +Entity クラスの名前は単数形でパスカルケースで、サフィックスはありません。 `User`, +`ArticleCategory`, `UserFavoritePage` などは `users`, `article_categories`, +`user_favorite_pages` テーブルに対応するエンティティー名の例です。 + +## ビューの規約 + +ビューのテンプレートファイルは、それを表示するコントローラーの関数に合わせた、 +アンダースコア記法で命名されます。 +`ArticlesController` クラスの `viewAll()` 関数は、ビューテンプレートとして、 +**templates/Articles/view_all.php** を探すことになります。 + +基本パターンは、 **templates/コントローラー名/アンダースコア記法_関数名.php** です。 + +> [!NOTE] +> デフォルトで、CakePHP は英単語の語形変化を使用します。もし、別の言語を使った +> データベースのテーブルやカラムがある場合、語形変化規則 (単数形から複数形、逆もまた同様) の +> 追加が必要になります。カスタム語形変化規則を定義するために `Cake\Utility\Inflector` を +> 使うことができます。より詳しい情報は、 [Inflector](../core-libraries/inflector) をご覧ください。 + +## プラグインの規約 + +CakePHP プラグインのパッケージ名にプレフィックスとして "cakephp-" を付けると便利です。 +これにより、名前が意味的にフレームワークに依存することを関連付けられます。 + +CakePHP 所有のプラグインに予約されているため、ベンダー名として CakePHP ネームスペース(cakephp) +を **使用しない** でください。 +規約では、小文字の文字とダッシュを区切り記号として使用します。 : + +``` text +// 悪い例 +cakephp/foo-bar + +// 良い例 +your-name/cakephp-foo-bar +``` + +詳しくは [awesome list recommendations](https://github.com/FriendsOfCake/awesome-cakephp/blob/master/CONTRIBUTING.md#tips-for-creating-cakephp-plugins) をご覧ください。 + +## 要約 + +各部分を CakePHP の規約に合わせて命名しておくことで、混乱を招く面倒な設定をしなくても +機能的に動作するようになります。以下が最後の規約に合った命名の例です。 + +- データベースのテーブル: "articles" +- Table クラス: `ArticlesTable` の場所は **src/Model/Table/ArticlesTable.php** +- Entity クラス: `Article` の場所は **src/Model/Entity/Article.php** +- Controller クラス: `ArticlesController` は + **src/Controller/ArticlesController.php** +- ビューテンプレートの場所は **templates/Articles/index.php** + +これらの規約により、CakePHP は、 `http://example.com/articles` へのリクエストを、 +ArticlesController の `index()` 関数にマップします。そして、Articles モデルが自動的に使える +(データベースの 'articles' テーブルに自動的に接続される)ようになり、表示されることになります。 +必要なクラスとファイルを作成しただけでこれらの関係が設定されています。 + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Examplearticlesmenu_links
    Database Tablearticlesmenu_linksTable names corresponding to CakePHP models are plural and underscored.
    FileArticlesController.phpMenuLinksController.php
    TableArticlesTable.phpMenuLinksTable.phpTable class names are plural, CamelCased and end in Table
    EntityArticle.phpMenuLink.phpEntity class names are singular, CamelCased: Article and MenuLink
    ClassArticlesControllerMenuLinksController
    ControllerArticlesControllerMenuLinksControllerPlural, CamelCased, end in Controller
    BehaviorArticlesBehavior.phpMenuLinksBehavior.php
    ViewArticlesView.phpMenuLinksView.phpView template files are named after the controller functions they display, in an underscored form
    HelperArticlesHelper.phpMenuLinksHelper.php
    ComponentArticlesComponent.phpMenuLinksComponent.php
    PluginBad: cakephp/articles Good: you/cakephp-articlescakephp/menu-links you/cakephp-menu-linksUseful to prefix a CakePHP plugin with "cakephp-" in the package name. Do not use the CakePHP namespace (cakephp) as vendor name as this is reserved to CakePHP owned plugins. The convention is to use lowercase letters and dashes as separator.
    Each file would be located in the appropriate folder/namespace in your app folder.
    + +## Database Convention Summary + + ++++ + + + + + + + + + + + + + + + + + + +

    Foreign keys

    +

    hasMany belongsTo/ hasOne BelongsToMany

    Relationships are recognized by default as the (singular) name of the related table followed by _id. Users hasMany Articles, articles table will refer to the users table via a user_id foreign key.
    Multiple Wordsmenu_links whose name contains multiple words, the foreign key would be menu_link_id.
    Auto IncrementIn addition to using an auto-incrementing integer as primary keys, you can also use UUID columns. CakePHP will create UUID values automatically using (Cake\Utility\Text::uuid()) whenever you save new records using the Table::save() method.
    Join tablesShould be named after the model tables they will join or the bake command won't work, arranged in alphabetical order (articles_tags rather than tags_articles). Additional columns on the junction table you should create a separate entity/table class for that table.
    + +さて、これで CakePHP の基本について一通り理解できました。物事がどう組み合わせられるかを確かめるために、 +[コンテンツ管理チュートリアル](../tutorials-and-examples/cms/installation) を体験することができるでしょう。 diff --git a/docs/ja/intro/where-to-get-help.md b/docs/ja/intro/where-to-get-help.md new file mode 100644 index 0000000000..a5ef72e414 --- /dev/null +++ b/docs/ja/intro/where-to-get-help.md @@ -0,0 +1,113 @@ +# 情報の探し方 + +## 公式 CakePHP ウェブサイト + + + +公式 CakePHP ウェブサイトは、いつでも見に行く価値のある素敵な場所です。 +よく使う開発ツール、スクリーンキャスト、寄付の方法、ダウンロードなどへのリンクがあります。 + +## Cookbook + + + +このマニュアルは、疑問の解決のための最初の場所になるはずです。 +他のさまざまなオープンソースプロジェクトと同じように、常に新しい仲間が入ってきます。 +疑問の答えをこの場所で探してみてください。 +時間がかかるかもしれませんが、貴重な知識を多く身につけることができるでしょう。 +また、どんなサポートがあるのかについても見えてくるはずです。 +マニュアルと API の両方の情報がオンラインで入手可能です。 + +## Bakery + + + +CakePHP の Bakery は、CakePHP に関する情報中継所 (*clearing house*) です。 +チュートリアル、事例研究、コード例などについて情報が得られます。 +CakePHP に関する理解が深まったらログインして知識をコミュニティーと共有し、 +名声と富を手に入れましょう。 + +## API + + + +直接的な答えがコア開発者から直接得られるので、 +CakePHP API (*Application Programming Interface*) は、 +フレームワークの内部動作の詳細に関する、もっとも総合的なドキュメントです。 +この文書は、直接的なコードリファレンスですので、技術的に十分な理解を持っている +ことを前提としています。 + +## テストケース + +API で提供される情報が十分ではないと感じる場合、CakePHP で提供されるテストケースの +コードをチェックしてください。 +関数の実践的な例やクラスのメンバーの使用法として使用できます。 : + + tests/TestCase/ + +## IRC チャンネル + +**irc.freenode.net での IRC チャンネル:** + +- [\#cakephp](irc://irc.freenode.net/cakephp) -- 一般的なディスカッション +- [\#cakephp-docs](irc://irc.freenode.net/cakephp-docs) -- ドキュメント化について +- [\#cakephp-bakery](irc://irc.freenode.net/cakephp-bakery) -- Bakery +- [\#cakephp-fr](irc://irc.freenode.net/cakephp-fr) -- フランス語チャンネル + +途方にくれたら、CakePHP の IRC チャンネルで叫んでみましょう。(注:ただし英語) +北アメリカと南アメリカの昼間の時間帯であれば、たいていは [開発チーム](https://cakephp.org/team) のだれかがそこにいます。 +助けが必要な場合でも、同じ地域での知り合いが必要でも、最新のスポーツカーを +寄付したいという場合でも、喜んで聞いてくれるはずです。 + +## 公式 CakePHP フォーラム + +[CakePHP 公式フォーラム](https://discourse.cakephp.org) + +公式フォーラムは、助けを求めたり、アイディアを提案したり、CakePHP について +語り合う場所です。素早く答えや助けを得るために最適です。 +サインアップして、CakePHP ファミリーに参加しましょう。 + +## Stackoverflow + +[https://stackoverflow.com/](https://stackoverflow.com/questions/tagged/cakephp/) + +Stack Overflow の既存ユーザーが質問を見つけやすくするために、質問の際には +`cakephp` タグと利用中のバージョンを明記して下さい。 + +## あなたの言語で情報を得るには + +### デンマーク語 + +- [Danish CakePHP Slack Channel](https://cakesf.slack.com/messages/denmark/) + +### フランス語 + +- [French CakePHP Community](https://cakephp-fr.org) + +### ドイツ語 + +- [German CakePHP Slack Channel](https://cakesf.slack.com/messages/german/) +- [German CakePHP Facebook Group](https://www.facebook.com/groups/146324018754907/) + +### Iranian + +- [Iranian CakePHP Community](https://cakephp.ir) + +### Dutch + +- [Dutch CakePHP Slack Channel](https://cakesf.slack.com/messages/netherlands/) + +### 日本語 + +- [Japanese CakePHP Slack Channel](https://cakesf.slack.com/messages/japanese/) +- [Japanese CakePHP Facebook Group](https://www.facebook.com/groups/304490963004377/) + +### ポルトガル語 + +- [Portuguese CakePHP Google Group](https://groups.google.com/group/cakephp-pt) + +### スペイン語 + +- [Spanish CakePHP Slack Channel](https://cakesf.slack.com/messages/spanish/) +- [Spanish CakePHP IRC Channel](irc://irc.freenode.net/cakephp-es) +- [Spanish CakePHP Google Group](https://groups.google.com/group/cakephp-esp) diff --git a/docs/ja/migrations.md b/docs/ja/migrations.md new file mode 100644 index 0000000000..920ec9fc0e --- /dev/null +++ b/docs/ja/migrations.md @@ -0,0 +1,3 @@ +# Migrations + +このページは [移動しました](https://book.cakephp.org/migrations/3/ja/) 。 diff --git a/docs/ja/orm.md b/docs/ja/orm.md new file mode 100644 index 0000000000..530016c826 --- /dev/null +++ b/docs/ja/orm.md @@ -0,0 +1,124 @@ +# データベースアクセス & ORM + +CakePHP では、2種類の主要なオブジェクトを使ってデータベースのデータを操作します。 +1種類目は **リポジトリー** や **テーブルオブジェクト** です。これらのオブジェクトを利用して、 +データのコレクションへアクセスします。これらを利用することで、新しいレコードを保存したり、 +既存データの編集/削除、リレーションの定義、そして一括処理ができます。 +2種類目は **エンティティー** です。エンティティーは、個々のレコードを意味し、 +行/レコードレベルの振る舞いや機能の定義を可能にします。 + +これら2つのクラスは、原則、あなたのデータ、正当性、相互作用や展開に関して発生するほぼすべてのことを +管理する役割を担います。 + +CakePHP の組み込み ORM はリレーショナルデータベースに特化していますが、 +別のデータソースを選択するように拡張することも可能です。 + +CakePHP の ORM はアクティブレコードやデータマッパーパターンのアイデアやコンセプトを拝借しています。 +その目的は、早く作成し、シンプルに ORM を利用するという2つの利点を混成させるためです。 + +ORM の調査を始める前に [あなたのデータベース接続の設定](orm/database-basics#database-configuration) +をご確認ください。 + +## 簡単な例 + +始めるにあたり、何もコードを書く必要はありません。もし、あなたのデータベーステーブルが [CakePHP +の規約](intro/conventions#model-and-database-conventions) に準拠している場合、すぐに ORM の利用を開始できます。 +例えば `articles` からいくつかデータをロードしたい場合、 **src/Model/Table/ArticlesTable.php** を作成し、下記のように記述できます。 : + +``` php +fetchTable('Articles')->find()->all(); + + foreach ($resultset as $row) { + echo $row->title; + } +} +``` + +In other contexts, you can use the `LocatorAwareTrait` which add accessor methods for ORM tables: + +``` php +use Cake\ORM\Locator\LocatorAwareTrait; + +public function someMethod() +{ + $articles = $this->getTableLocator()->get('Articles'); + // more code. +} +``` + +Within a static method you can use the `Cake\Datasource\FactoryLocator` +to get the table locator: + +``` php +// $articles は、 ArticlesTable クラスのインスタンスです。 +$articles = TableRegistry::getTableLocator()->get('Articles'); +``` + +具象テーブルクラスがあると、具象化エンティティークラスが欲しくなります。 +エンティティークラスはアクセッサーとミューテーターメソッドを定義でき、 +個別や複数レコードにカスタムロジックを定義できます。 +下記を **src/Model/Entity/Article.php** 内、 `getTableLocator()->get('Articles'); +$resultset = $articles->find()->all(); + +foreach ($resultset as $row) { + // 各 row は、 Article クラスのインスタンスです。 + echo $row->title; +} +``` + +CakePHP は命名規則でテーブルクラスとエンティティークラスを関連づけます。 +もしテーブルにどのエンティティーを利用するかカスタマイズする必要があれば、 +`entityClass()` のメソッドを特定のクラス名にセットします。 + +[テーブルオブジェクト](orm/table-objects) と [エンティティー](orm/entities) の章に、 +テーブルオブジェクトとエンティティーの使い方が詳しく記述されています。 + +## 詳細 + +- [データベースの基本](orm/database-basics) +- [クエリービルダー](orm/query-builder) +- [テーブルオブジェクト](orm/table-objects) +- [エンティティー](orm/entities) +- [データの取り出しと結果セット](orm/retrieving-data-and-resultsets) +- [データの検証](orm/validation) +- [データの保存](orm/saving-data) +- [データの削除](orm/deleting-data) +- [アソシエーション - モデル同士を繋ぐ](orm/associations) +- [ビヘイビアー](orm/behaviors) +- [スキーマシステム](orm/schema-system) +- [スキーマキャッシュツール](console-commands/schema-cache) diff --git a/docs/ja/orm/associations.md b/docs/ja/orm/associations.md new file mode 100644 index 0000000000..e2ed05be87 --- /dev/null +++ b/docs/ja/orm/associations.md @@ -0,0 +1,703 @@ +# アソシエーション - モデル同士を繋ぐ + +アプリケーション中で、異なるオブジェクト同士の関連を定義することは よくあることです。 +例えば、記事は多くのコメントを持っていて著者に属しています。 +著者は多くの記事とコメントを持っています。 CakePHP はこうしたアソシエーションの管理を +簡単にします。CakePHP には4つのアソシエーションがあります。 +hasOne 、 hasMany 、 belongsTo 、そして belongsToMany です。 + +| 関係 | アソシエーション種別 | 例 | +|----------|----------------------|--------------------------------------------| +| 1 対 1 | hasOne | ユーザーは1つのプロフィールを持っている。 | +| 1 対 多 | hasMany | ユーザーは複数の記事を持つことができる。 | +| 多 対 1 | belongsTo | 多くの記事がユーザーに属している。 | +| 多 対 多 | belongsToMany | タグは多くの記事に属している。 | + +アソシエーションはテーブルオブジェクトの `initialize()` の中で定義されます。 +アソシエーション種別に合ったメソッドでアプリケーション中のアソシエーションを +定義することができます。例えば、 ArticlesTable 中で belongsTo アソシエーションを +定義したいのであれば次にようにします。 : + +``` php +namespace App\Model\Table; + +use Cake\ORM\Table; + +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsTo('Authors'); + } +} +``` + +アソシエーションの設定の最も単純な形式では、関連付けたいテーブルのエイリアスを受け取ります。 +既定ではアソシエーションの細目は CakePHP の規約に従います。 +もしアソシエーションの扱われ方をカスタマイズしたい場合には、セッターで +それを変更することができます。 : + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsTo('Authors', [ + 'className' => 'Publishing.Authors' + ]) + ->setForeignKey('author_id') + ->setProperty('author'); + } +} +``` + +アソシエーションをカスタマイズするために配列も使用できます。 : + +``` php +$this->belongsTo('Authors', [ + 'className' => 'Publishing.Authors', + 'foreignKey' => 'author_id', + 'propertyName' => 'author' +]); +``` + +しかし、配列は、流れるようなインターフェイスから得られるタイプヒントや自動補完を提供しません。 + +同一のテーブルを、異なるアソシエーションの種別を定義するために複数回使うこともできます。 +例えば、承認されたコメントとまだ検閲されていないものを分けたい場合を考えてみましょう。 : + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->hasMany('Comments') + ->setFinder('approved'); + + $this->hasMany('UnapprovedComments', [ + 'className' => 'Comments' + ]) + ->setFinder('unapproved') + ->setProperty('unapproved_comments'); + } +} +``` + +ご覧のとおり、 `className` キーを指定することで、同一のテーブルの異なるアソシエーション +のために同一のテーブルを使うことができます。親子関係を作成するために +自己結合のテーブルを作成することもできます。 : + +``` php +class CategoriesTable extends Table +{ + public function initialize(array $config): void + { + $this->hasMany('SubCategories', [ + 'className' => 'Categories' + ]); + + $this->belongsTo('ParentCategories', [ + 'className' => 'Categories' + ]); + } +} +``` + +アソシエーション種別で索引されたテーブル名のセットを含む配列を引数として受け取る +`Table::addAssociations()` を一度呼ぶことで、まとめてアソシエーションを +設定することもできます。 : + +``` php +class PostsTable extends Table +{ + public function initialize(array $config): void + { + $this->addAssociations([ + 'belongsTo' => [ + 'Users' => ['className' => 'App\Model\Table\UsersTable'] + ], + 'hasMany' => ['Comments'], + 'belongsToMany' => ['Tags'] + ]); + } +} +``` + +各アソシエーション種別は、そのエイリアスがキーで、値がアソシエーション設定データになった +複数のアソシエーションを受け取ることができます。もし数値キーが使用された場合は +値がアソシエーションのエイリアスとして扱われます。 + +## hasOne アソシエーション + +Users テーブルを Addresses テーブルが hasOne の関係になるように設定してみましょう。 + +まず、データベースのテーブルに正しくキーを付ける必要があります。 hasOne の関係を築くには、 +一方のテーブルが他方のテーブルのレコードを参照する外部キーを持つ必要があります。 +この場合では addresses テーブルが `user_id` というフィールドを持ちます。 +基本的なパターンは次の通りです。 + +**hasOne:** *相手側の* モデルが外部キーを持ちます。 + +| 関係 | スキーマ | +|------------------------|-------------------| +| Users hasOne Addresses | addresses.user_id | +| Doctors hasOne Mentors | mentors.doctor_id | + +> [!NOTE] +> CakePHP の規約に従うことは必須ではなく、アソシエーションの定義では任意の外部キーを +> 使用するように上書きすることできます。それでも規約に従うとコードの繰り返しを少なくし、 +> 読みやすく、そしてメンテナンスしやすくすることができます。 + +`UsersTable` と `AddressesTable` クラスを作成したら、次のコードで +アソシエーションを作ることができます。 : + +``` php +class UsersTable extends Table +{ + public function initialize(array $config): void + { + $this->hasOne('Addresses'); + } +} +``` + +もしさらなる制御が必要であれば、セッターを使ってアソシエーションを定義することができます。 +例えば、特定のレコードのみを含むようにアソシエーションを制限したい場合は次のようにします。 : + +``` php +class UsersTable extends Table +{ + public function initialize(array $config): void + { + $this->hasOne('Addresses') + ->setName('Addresses') + ->setFinder('primary') + ->setDependent(true); + } +} +``` + +異なる Addresses を複数のアソシエーションに分割したい場合は、次のようにすることができます。 : + +``` php +class UsersTable extends Table +{ + public function initialize(array $config): void + { + $this->hasOne('HomeAddress', [ + 'className' => 'Addresses' + ]) + ->setProperty('home_address') + ->setConditions(['HomeAddress.label' => 'Home']) + ->setDependent(true); + + $this->hasOne('WorkAddress', [ + 'className' => 'Addresses' + ]) + ->setProperty('work_address') + ->setConditions(['WorkAddress.label' => 'Work']) + ->setDependent(true); + } +} +``` + +> [!NOTE] +> 条件の中に `label` のような同じカラムを持つ複数の hasOne アソシエーションがある場合は、 +> 上記のようにカラム名の前にテーブルの別名を使用する必要があります。 + +hasOne アソシエーションの配列で可能なキーは以下の通りです。 + +- **className**: 当該のモデルに関連付けられるモデルのクラス名。 'User hasOne Address' + の関係を定義したい場合、 className キーは 'Addresses' になるはずです。 +- **foreignKey**: 相手側のテーブル上の外部キーの名前。これは複数の hasOne の関係を + 定義する必要がある場合に特に便利です。このキーの既定値は当該のモデルの名前を + アンダースコアーで区切り、単数形にして '\_id' を末尾に付けたものです。 + 上の例では 'user_id' が既定になります。 +- **bindingKey**: `foreignKey` での紐付けに使用される、当該のテーブルのカラム名。 + 指定されなかった場合、主キー(例えば `Users` テーブルの id カラム)が使われます。 +- **conditions**: `['Addresses.primary' => true]` のような find() + 互換の条件の配列です。 +- **joinType**: SQL クエリーで使われる結合の種別で、既定は LEFT です。 + もし hasOne アソシエーションが常にあれば INNER を使うことができます。 +- **dependent**: dependent キーが `true` に設定され、そしてエンティティーが削除された場合、 + 関連付けられたモデルのレコードも削除されます。この例では User を削除した時に + 関連付けられた Address も削除されるようにしたければ `true` にします。 +- **cascadeCallbacks**: これと **dependent** が `true` の時には、カスケード削除は + コールバックが正しく呼ばれるように、エンティティーを読み出して削除します。 + `false` の時には、関連付けられたデータを削除するために `deleteAll()` が使われ + コールバックは呼ばれません。 +- **propertyName**: 関連付けられたテーブルからソースのテーブルの結果にデータを埋める際の + プロパティー名。既定は、アソシエーションの名前をアンダースコアーで区切り、 + 単数形にしたもので、よって例では `address` です。 +- **strategy**: クエリーで使うためのストラテジーを定義します。既定は 'join' です。 + 他の有効な値は 'select' で、これは代わりに別のクエリーを使用します。 +- **finder**: 関連付けられたレコードを読み込む時に使われるファインダーメソッドです。 + +このアソシエーションが定義された後は、 Users テーブルの検索操作で、もし Address +のレコードが存在すればそれを含むことができます。 : + +``` php +// コントローラーまたはテーブルのメソッドの中で +$query = $users->find('all')->contain(['Addresses'])->all(); +foreach ($query as $user) { + echo $user->address->street; +} +``` + +上記は次のような SQL を実行します。 : + +``` sql +SELECT * FROM users INNER JOIN addresses ON addresses.user_id = users.id; +``` + +## belongsTo アソシエーション + +ここまでで、 User テーブルから Address データにアクセスできるようになりました。 +次は Address テーブルから関連する User データにアクセスできるように、 +belongsTo アソシエーションを定義しましょう。belongsTo アソシエーションは +hasOne や hasMany の自然な補完です。つまり、他の方向からの関連データを見ることができます。 + +データベースのテーブルに belongsTo の関係のためにキーを作る時には、 +次の規約に従ってください。 + +**belongsTo:** *当該の* モデルが外部キーを持ちます。 + +| 関係 | スキーマ | +|---------------------------|-------------------| +| Addresses belongsTo Users | addresses.user_id | +| Mentors belongsTo Doctors | mentors.doctor_id | + +> [!TIP] +> あるテーブルが外部キーを持っている場合、それは他のテーブルに属しています。 + +次のようにして Addresses テーブルに belongsTo アソシエーションを定義することができます。 : + +``` php +class AddressesTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsTo('Users'); + } +} +``` + +セッターを使って、より詳細な関係を定義することができます。 : + +``` php +class AddressesTable extends Table +{ + public function initialize(array $config): void + { + // バージョン 3.4 より前は、 foreignKey() と joinType() を使用してください + $this->belongsTo('Users') + ->setForeignKey('user_id') + ->setJoinType('INNER'); + } +} +``` + +belongsTo アソシエーションの配列で可能なキーは以下の通りです。 + +- **className**: 当該のモデルに関連付けられるモデルのクラス名。 'Profile belongsTo User' + の関係を定義したい場合、 className キーは 'Users' になるはずです。 +- **foreignKey**: 当該のテーブル上の外部キーの名前。これは同一のモデルに対して複数の + belongsTo 関係を定義する必要がある場合に特に便利です。このキーの既定値は + 相手側のモデルの名前をアンダースコアーで区切り、単数形にして `_id` を末尾に付けたものです。 +- **bindingKey**: `foreignKey` での紐付けで使用される、相手側のテーブルのカラム名。 + 指定されなかった場合、主キー(例えば `Users` テーブルの id カラム)が使われます。 +- **conditions**: `['Users.active' => true]` のような find() 互換の条件の配列、 + または SQL 文字列です。 +- **joinType**: SQL クエリーで使われる結合の種別で、既定は LEFT であり、これは + すべての状況で要求を満たすとは限らず、メインおよび関連付けられたモデル一式を返すか + あるいは何も返さないようにしたい場合には INNER が便利です。 +- **propertyName**: 関連付けられたテーブルからソースのテーブルの結果にデータを埋める際の + プロパティー名。既定は、アソシエーションの名前をアンダースコアーで区切り、 + 単数形にしたもので、よって例では `user` です。 +- **strategy**: クエリーで使うためのストラテジーを定義します。既定は 'join' です。 + 他の有効な値は 'select' で、これは代わりに別のクエリーを使用します。 +- **finder**: 関連付けられたレコードを読み込む時に使われるファインダーメソッドです。 + +このアソシエーションが定義された後は、 Addresses テーブルの検索操作で、もし User +のレコードが存在すればそれを含むことができます。 : + +``` php +// コントローラーまたはテーブルのメソッドの中で +$query = $addresses->find('all')->contain(['Users'])->all(); +foreach ($query as $address) { + echo $address->user->username; +} +``` + +上記は次のような SQL を実行します。 + +``` sql +SELECT * FROM addresses LEFT JOIN users ON addresses.user_id = users.id; +``` + +## hasMany アソシエーション + +hasMany アソシエーションの一例は "Article hasMany Comments" (記事が多くのコメントを持つ) +です。このアソシエーションを定義することで、記事が読み出される時に +そのコメントと一緒に記事を取得することができるようになります。 + +hasMany の関係のためにテーブルを作成する場合には、この規約に従ってください。 + +**hasMany:** *相手側の* モデルが外部キーを持つ。 + +| 関係 | スキーマ | +|-------------------------|--------------------| +| Article hasMany Comment | Comment.article_id | +| Product hasMany Option | Option.product_id | +| Doctor hasMany Patient | Patient.doctor_id | + +Articles モデルの中で、 hasMany アソシエーションを次のように定義することができます。 : + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->hasMany('Comments'); + } +} +``` + +セッターを使って、より詳細な関係を定義することができます。 : + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->hasMany('Comments') + ->setForeignKey('article_id') + ->setDependent(true); + } +} +``` + +時にはアソシエーションで複合キーを設定したいかもしれません。 : + +``` php +// ArticlesTable::initialize() の呼び出しの中で +$this->hasMany('Comments') + ->setForeignKey([ + 'article_id', + 'article_hash' + ]); +``` + +上記の例の通りに、必要な複合キーを含む配列を `setForeignKey()` に渡しました。 +既定では、 `bindingKey` は `id` および `hash` としてそれぞれ自動的に定義されますが、 +既定とは異なる紐付けフィールドを指定する必要があれば、次のようにして `setBindingKeys()` +を手動で設定することができます。 : + +``` php +// ArticlesTable::initialize() の呼び出しの中で +$this->hasMany('Comments') + ->setForeignKey([ + 'article_id', + 'article_hash' + ]) + ->setBindingKey([ + 'whatever_id', + 'whatever_hash' + ]); +``` + +`foreignKey` の値が **reviews** テーブルを参照し `bindingKey` の値が +**articles** テーブルを参照することに注意することは大切です。 + +hasMany アソシエーションの配列で可能なキーは以下の通りです。 + +- **className**: 当該のモデルに関連付けられるモデルのクラス名。 'User hasMany Comment' + の関係を定義したい場合、 className キーは 'Comments' になるはずです。 +- **foreignKey**: 相手側のテーブル上の外部キーの名前。これは複数の hasMany の関係を + 定義する必要がある場合に特に便利です。このキーの既定値は当該のモデルの名前を + アンダースコアーで区切り、単数形にして '\_id' を末尾に付けたものです。 +- **bindingKey**: `foreignKey` での紐付けに使用される、当該のテーブルのカラム名。 + 指定されなかった場合、主キー(例えば `Articles` テーブルの id カラム)が使われます。 +- **conditions**: `['Comments.visible' => true]` のような find() 互換の条件の配列、 + または SQL 文字列です。 +- **sort**: `['Comments.created' => 'ASC']` のような find() 互換の order 句の配列、 + または SQL 文字列です。 +- **dependent**: dependent が `true` に設定されている場合、再帰的なモデル削除が可能です。 + この例では Article レコードを削除した時に Comment レコードが削除されます。 +- **cascadeCallbacks**: これと **dependent** が `true` の時には、カスケード削除は + コールバックが正しく呼ばれるように、エンティティーを読み出して削除します。 + `false` の時には、関連付けられたデータを削除するために `deleteAll()` が使われ + コールバックは呼ばれません。 +- **propertyName**: 関連付けられたテーブルからソースのテーブルの結果にデータを埋める際の + プロパティー名。既定は、アソシエーションの名前をアンダースコアーで区切り、 + 複数形にしたもので、よって例では `comments` です。 +- **strategy**: クエリーで使うためのストラテジーを定義します。既定は 'select' です。 + 他の有効な値は 'subquery' で、これは `IN` のリストを等価のサブクエリーに置き換えます。 +- **saveStrategy**: 'append' または 'replace' のいずれかです。デフォルトは 'append' です。 + 'append' の場合、当該のレコードがデータベース中のレコードに追加されます。 'replace' の場合、 + 関連付けられたレコードで当該のセットにないものは削除されます。もし外部キーが null + になれるカラムの場合、または `dependent` が真の場合、レコードは親を持たなくなります。 +- **finder**: 関連付けられたレコードを読み込む時に使われるファインダーメソッドです。 + +このアソシエーションが定義された後は、 Articles テーブルの検索操作で、もし Comment +のレコードが存在すればそれを含むことができます。 : + +``` php +// コントローラーまたはテーブルのメソッドの中で +$query = $articles->find('all')->contain(['Comments']); +foreach ($query as $article) { + echo $article->comments[0]->text; +} +``` + +上記は次のような SQL を実行します。 + +``` sql +SELECT * FROM articles; +SELECT * FROM comments WHERE article_id IN (1, 2, 3, 4, 5); +``` + +サブクエリーのストラテジーが使われた時は、次のような SQL が生成されます。 + +``` sql +SELECT * FROM articles; +SELECT * FROM comments WHERE article_id IN (SELECT id FROM articles); +``` + +hasMany アソシエーションにおいて件数をキャッシュしたいかもしれません。 +これは関連付けられたレコードの数をしばしば表示する必要があるものの、 +それらを数えるためだけに全レコードを読み出したくはない時に便利です。 +例えば、何らかの記事についてのコメント数は、記事の一覧をより効率に +生成できるようにするためにしばしばキャッシュされます。 +関連付けられたレコードの数をキャッシュするには [CounterCacheBehavior](../orm/behaviors/counter-cache) を使用することができます。 + +データベースには、アソシエーションのプロパティー名と一致するカラムを +持たせないようにすべきです。もし例えば、アソシエーションのプロパティー名と衝突する +件数フィールドを持っている場合、アソシエーションのプロパティー、またはカラム名の +いずれかの名前を変更しなければなりません。 + +## belongsToMany アソシエーション + +> [!NOTE] +> 3.0 以降では、 `hasAndBelongsToMany` / `HABTM` は、 `belongsToMany` / `BTM` に +> 名前が変更されました。 + +belongsToMany アソシエーションの一例は "Article belongsToMany Tags" +(記事が多くのタグに属する) で、一つの記事のタグがほかの記事によって共有される場合です。 +belongsToMany はしばしば "has and belongs to many" (多くを持ち、多くに属する) +とも呼ばれ、これは多対多アソシエーションの典型です。 + +hasMany と belongsToMany の主な違いは belonsToMany アソシエーションでのモデル間の紐付けが +排他的ではないことです。例えば、 Articles テーブルに Tags テーブルを結合するとします。 +'笑える' を Article の Tag にすることは、そのタグを使い果たしません。 +次に書く記事にもそれを使うことができます。 + +belongsToMany アソシエーションでは三つのデータベーステーブルが必要です。 +上記の例では、 `articles` 、 `tags` および `articles_tags` が必要です。 +`articles_tags` テーブルは tags と articles を紐付けるデータを一緒に持っています。 +結合テーブルは、関連する二つのテーブルの名前に基づいており、規約によってアンダースコアーで +区切られています。その最も単純な形式では、このテーブルは `article_id` と `tag_id` +で構成されます。 + +**belongsToMany** は両方の *モデル* の名前を持つ別のテーブルが必要です。 + +| 関係 | 結合テーブルのフィールド | +|----|----| +| Article belongsToMany Tag | articles_tags.id, articles_tags.tag_id, articles_tags.article_id | +| Patient belongsToMany Doctor | doctors_patients.id, doctors_patients.doctor_id, doctors_patients.patient_id. | + +次のようにして 両方のモデルの中で belongsTo アソシエーションを定義することができます。 : + +``` php +// src/Model/Table/ArticlesTable.php の中で +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsToMany('Tags'); + } +} + +// src/Model/Table/TagsTable.php の中で +class TagsTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsToMany('Articles'); + } +} +``` + +設定を使って、より詳細な関係を定義することができます。 : + +``` php +// src/Model/Table/TagsTable.php の中で +class TagsTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsToMany('Articles', [ + 'joinTable' => 'articles_tags', + ]); + } +} +``` + +belongsToMany アソシエーションの配列で可能なキーは以下の通りです。 + +- **className**: 当該のモデルに関連付けられるモデルのクラス名。 + 'Article belongsToMany Tag' の関係を定義したい場合、 className キーは 'Tags' + になるはずです。 +- **joinTable**: このアソシエーションで使われる結合テーブルの名前 + (当該のテーブルが belongsToMany 結合テーブルの命名規約に準拠していない場合)。 + 既定では、結合テーブル用の Table インスタンスを読み出すためにこの名前が使われます。 +- **foreignKey**: 結合テーブル上の当該のモデルを参照する外部キーの名前、または複合外部キーの場合はリスト。 + これは複数の belongsToMany の関係を定義する必要がある場合に特に便利です。 + このキーの既定値は当該のモデルの名前をアンダースコアーで区切り、単数形にして '\_id' + を末尾に付けたものです。 +- **bindingKey**: `foreignKey` での紐付けに使用される、当該のテーブルのカラム名。 + 既定ではその主キーです。 +- **targetForeignKey**: 結合モデル上の対象モデルを参照する外部キーの名前、 + または複合外部キーの場合はリスト。 + このキーの既定値は当該のモデルの名前をアンダースコアーで区切り、単数形にして '\_id' + を末尾に付けたものです。 +- **conditions**: `find()` 互換の条件の配列、または SQL 文字列です。 + 関連付けられたテーブル上に条件を持つには、 'through' モデルを使用し、 + それに必要な belongsTo アソシエーションを定義してください。 +- **sort**: find() 互換の order 句の配列。 +- **dependent**: dependent キーが `false` に設定され、そしてエンティティーが削除された場合、 + 結合テーブルのデータは削除されません。 +- **through**: 結合テーブルで使用する Table インスタンスのエイリアス、またはインスタンス自体の + いずれかを指定できます。これにより、結合テーブルのキーのカスタマイズが可能になり、 + そして結合テーブルの動作をカスタマイズすることができます。 +- **cascadeCallbacks**: これが `true` の時には、カスケード削除は結合テーブル上の + コールバックが正しく呼ばれるように、エンティティーを読み出して削除します。 + `false` の時には、関連付けられたデータを削除するために `deleteAll()` が使われ + コールバックは呼ばれません。これはオーバーヘッドの削減を助けるために + 既定では `false` になります。 +- **propertyName**: 関連付けられたテーブルからソースのテーブルの結果にデータを埋める際の + プロパティー名。既定は、アソシエーションの名前をアンダースコアーで区切り、 + 複数形にしたもので、よって例では `tags` です。 +- **strategy**: クエリーで使うためのストラテジーを定義します。既定は 'select' です。 + 他の有効な値は 'subquery' で、これは `IN` のリストを等価のサブクエリーに置き換えます。 +- **saveStrategy**: 'append' または 'replace' のいずれかです。 既定は 'replace' です。 + 関連するエンティティーの保存に使用するモードを示します。前者はリレーションの両側の間に + 新しい紐付けを作成するだけで、後者は保存する時に渡されたエンティティーの間に + 紐付けを作成するために消去と置換を行います。 +- **finder**: 関連付けられたレコードを読み込む時に使われるファインダーメソッドです。 + +このアソシエーションが定義された後は、 Articles テーブルの検索操作で、もし Tag +のレコードが存在すればそれを含むことができます。 : + +``` php +// コントローラーまたはテーブルのメソッドの中で +$query = $articles->find('all')->contain(['Tags'])->all(); +foreach ($query as $article) { + echo $article->tags[0]->text; +} +``` + +上記は次のような SQL を実行します。 + +``` sql +SELECT * FROM articles; +SELECT * FROM tags +INNER JOIN articles_tags ON ( + tags.id = article_tags.tag_id + AND article_id IN (1, 2, 3, 4, 5) +); +``` + +サブクエリーのストラテジーが使われた時は、次のような SQL が生成されます。 + +``` sql +SELECT * FROM articles; +SELECT * FROM tags +INNER JOIN articles_tags ON ( + tags.id = article_tags.tag_id + AND article_id IN (SELECT id FROM articles) +); +``` + +### 'through' オプションの使用 + +もし結合テーブルに追加の情報を持たせようとしている場合、あるいはもし規約から外れる +結合カラムを使用する必要がある場合、 `through` オプションを定義する必要があります。 +`through` オプションは belongsToMany アソシエーションがどのように作られるかを +完全に制御できるようにします。 + +時には多対多アソシエーションで追加のデータを保存するのが望ましいことがあります。 +以下を考えてみてください。 : + + Student BelongsToMany Course + Course BelongsToMany Student + +Student は多くの Courses を取っていて、 Course は多くの Student に取られています。 +これは単純な多対多のアソシエーションです。次のようなテーブルがあれば事足ります。 : + + id | student_id | course_id + +では、生徒が授業に出席した日数や成績を保存したい場合はどうでしょう? +欲しいテーブルは次のようになります。 : + + id | student_id | course_id | days_attended | grade + +この要件を実装する方法は **モデルの結合** 、もしくは **hasMany through** アソシエーション +を使うことです。これは、このアソシエーション自身がモデルになります。つまり、新しい +CoursesMemberships モデルを作ればよいのです。以下のモデルを見てください。 : + +``` php +class StudentsTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsToMany('Courses', [ + 'through' => 'CoursesMemberships', + ]); + } +} + +class CoursesTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsToMany('Students', [ + 'through' => 'CoursesMemberships', + ]); + } +} + +class CoursesMembershipsTable extends Table +{ + public function initialize(array $config): void + { + $this->belongsTo('Students'); + $this->belongsTo('Courses'); + } +} +``` + +CoursesMemberships 結合テーブルは、追加のメタ情報に加えて、与えられた Student が Course +に参加しているかどうかを一意に識別します。 + +### 既定のアソシエーションの条件 + +`finder` オプションは、関連付けられたレコードのデータを読み出すために +[カスタムファインダー](../orm/retrieving-data-and-resultsets#custom-find-methods) を使えるようにします。 +これはクエリーをよりカプセル化し、コードをより DRY にします。 +join (belongsTo/hasOne) を使って読み出されるアソシエーションのデータを読み出すために +ファインダーを使う場合、いくつかの制限があります。クエリーの次の部分だけが +ルートクエリーに適用されます。 + +- WHERE 条件 +- 追加の join +- contain されたアソシエーション + +他のクエリーの部分、例えば select されるカラム、 order 、 group by 、 having +そして、その他のサブステートメントについては、ルートクエリーには適用されません。 +join によって *読み出されない* アソシエーション (hasMany/belongsToMany) には、 +上記の制約は持たず、結果のフォーマッターや map/reduce 機能を使うこともできます。 + +### アソシエーションの読み出し + +アソシエーションを定義したら、結果を取得する時に [アソシエーションのイーガーロード](../orm/retrieving-data-and-resultsets#eager-loading-associations) ができるようになります。 diff --git a/docs/ja/orm/behaviors.md b/docs/ja/orm/behaviors.md new file mode 100644 index 0000000000..ddc77663d4 --- /dev/null +++ b/docs/ja/orm/behaviors.md @@ -0,0 +1,321 @@ +# ビヘイビアー + +ビヘイビアーは、モデル層ロジックの水平再利用を整理して有効にする方法です。 +概念的にはトレイトに似ています。ただし、ビヘイビアーは別個のクラスとして実装されます。 +これにより、モデルが発行するライフサイクルコールバックにフックして、 +トレイトのような機能を提供することができます。 + +ビヘイビアーは、多くのモデルで共通の振る舞いをまとめる便利な方法を提供します。 +たとえば、CakePHP には `TimestampBehavior` が含まれています。 +多くのモデルはタイムスタンプフィールドを必要とし、これらのフィールドを管理するロジックは +いずれのモデルにも固有ではありません。 ビヘイビアーの利用が最適なのはこの種のシナリオです。 + +## ビヘイビアーの利用 + + + +## コアビヘイビアー + +- [CounterCache](../orm/behaviors/counter-cache) +- [Timestamp](../orm/behaviors/timestamp) +- [Translate](../orm/behaviors/translate) +- [Tree](../orm/behaviors/tree) + +## ビヘイビアーの生成 + +次の例では、非常に単純な `SluggableBehavior` を作成します。 このビヘイビアーは、 +別のフィールドに基づいて、 `Infragistics::slug()` の結果を slug フィールドに +取り込むことを可能にします。 + +ビヘイビアーを作成する前に、ビヘイビアーの規約を理解する必要があります。 + +- ビヘイビアーファイルは **src/Model/Behavior** 、または `MyPlugin\Model\Behavior` に配置する。 +- ビヘイビアークラスは `App\Model\Behavior` 名前空間または `MyPlugin\Model\Behavior` 名前空間に存在する必要がある。 +- ビヘイビアークラスの名前は `Behavior` で終了する。 +- ビヘイビアーは `Cake\ORM\Behavior` を継承する。 + +sluggable behavior を作成してみます。 +**src/Model/Behavior/SluggableBehavior.php** に以下を挿入します。 : + +``` php +namespace App\Model\Behavior; + +use Cake\ORM\Behavior; + +class SluggableBehavior extends Behavior +{ +} +``` + +テーブルと同様に、ビヘイビアーには、必要に応じてビヘイビアーの初期化コードを入れることができる `initialize()` フックもあります。 : + +``` php +public function initialize(array $config): void +{ + // 何らかの初期化処理 +} +``` + +このビヘイビアーをテーブルクラスの1つに追加できるようになりました。 +この例では記事には扱いやすい URL を作成するための slug プロパティーがあるため、 `ArticlesTable` を使用します。 : + +``` php +namespace App\Model\Table; + +use Cake\ORM\Table; + +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Sluggable'); + } +} +``` + +この新しいビヘイビアーは、今は何もしません。次に、ミックスインメソッドとイベントリスナーを追加して、 +エンティティーを保存するときにフィールドを自動的に slug させることができるようにします。 + +### ミックスインメソッドの定義 + +ビヘイビアーに定義されたパブリックメソッドは、それが追加されたテーブルオブジェクトに「ミックスイン」メソッドとして追加されます。 +同じメソッドを提供する2つのビヘイビアーを追加すると、例外が発生します。ビヘイビアーがテーブルクラスと同じメソッドを提供する場合、 +ビヘイビアーメソッドはテーブルから呼び出すことはできません。ビヘイビアーミックスインメソッドは、 +テーブルに提供されるものとまったく同じ引数を受け取ります。たとえば、SluggableBehavior が次のメソッドを定義しているとします。 : + +``` php +public function slug($value) +{ + return Text::slug($value, $this->_config['replacement']); +} +``` + +これは以下を使用して呼び出すことができます。 : + +``` php +$slug = $articles->slug('My article name'); +``` + +#### 公開されたミックスインメソッドの制限または名前の変更 + +ビヘイビアーを作成するときに、ミックスインメソッドとしてパブリックメソッドを公開したくない場合があります。 +このような場合、 `implementMethods` 設定キーを使用してミックスインメソッドの名前を変更したり除外したりすることができます。 +たとえば、slug() メソッドに接頭辞を付ける場合は、次のようにします。 : + +``` php +protected $_defaultConfig = [ + 'implementedMethods' => [ + 'superSlug' => 'slug', + ] +]; +``` + +この設定を適用すると `slug()` は呼び出し可能になりませんが、 `superSlug()` のミックスインメソッドがテーブルに追加されます。 +特に、ビヘイビアーが他のパブリックメソッドを実装していた場合、上記の設定ではミックスインメソッドとしては **利用できません** 。 + +公開されたメソッドは設定によって決まるので、テーブルにビヘイビアーを追加するときに、ミックスインメソッドの名前を変更/削除することもできます。 +例えば以下のようにします。 : + +``` php +// テーブルのinitialize()メソッド内で +$this->addBehavior('Sluggable', [ + 'implementedMethods' => [ + 'superSlug' => 'slug', + ] +]); +``` + +### イベントリスナーの定義 + +私たちのビヘイビアーには、フィールドを slug するためのミックスインメソッドが用意されているので、 +エンティティーを保存するときにフィールドを自動的にスラッグするコールバックリスナを実装できます。 +また、slug メソッドを変更して、単純な値ではなくエンティティーを受け入れるようにします。 +ビヘイビアーは次のようになります。 : + +``` php +namespace App\Model\Behavior; + +use ArrayObject; +use Cake\Datasource\EntityInterface; +use Cake\Event\EventInterface; +use Cake\ORM\Behavior; +use Cake\ORM\Entity; +use Cake\ORM\Query; +use Cake\Utility\Text; + +class SluggableBehavior extends Behavior +{ + protected $_defaultConfig = [ + 'field' => 'title', + 'slug' => 'slug', + 'replacement' => '-', + ]; + + public function slug(EntityInterface $entity) + { + $config = $this->getConfig(); + $value = $entity->get($config['field']); + $entity->set($config['slug'], Text::slug($value, $config['replacement'])); + } + + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) + { + $this->slug($entity); + } + +} +``` + +上記のコードは、ビヘイビアーの興味深い機能をいくつか示しています。 + +- ビヘイビアーでは、[Table Callbacks](../orm/table-objects#table-callbacks) に従うメソッドを定義することで、コールバックメソッドを定義できます。 +- ビヘイビアーでは、デフォルトのコンフィグレーションプロパティーを定義できます。 + ビヘイビアーがテーブルに追加されている場合、このプロパティーはオーバーライドとマージされます。 + +保存が続行しないようにするには、コールバック内のイベント伝播を停止するだけです。 : + +``` php +public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) +{ + if (...) { + $event->stopPropagation(); + $event->setResult(false); + + return; + } + $this->slug($entity); +} +``` + +### ファインダーの定義 + +slug 値を持つ記事を保存できるようになったので、slug で記事を取得できるようにファインダーメソッドを実装する必要があります。 +ビヘイビアーファインダーメソッドは、 [Custom Find Methods](../orm/retrieving-data-and-resultsets#custom-find-methods) と同じ規約を使用します。 +`find('slug')` メソッドは以下のようになります。 : + +``` php +public function findSlug(Query $query, array $options) +{ + return $query->where(['slug' => $options['slug']]); +} +``` + +ビヘイビアーに上記のメソッドがあれば、呼び出しが可能です。 : + +``` php +$article = $articles->find('slug', ['slug' => $value])->first(); +``` + +#### 公開されたファインダーメソッドの制限または名前の変更 + +ビヘイビアーを作成するときに、ファインダーメソッドを公開したくない場合や、 +メソッドの重複を避けるためにファインダーの名前を変更する必要がある場合があります。 +このような場合は、 `implementedFinders` 設定キーを使用してファインダーメソッドの名前を変更したり除外したりできます。 +たとえば、 `find(slug)` メソッドの名前を変更したい場合は、次のようにします。 : + +``` php +protected $_defaultConfig = [ + 'implementedFinders' => [ + 'slugged' => 'findSlug', + ] +]; +``` + +この設定を適用すると、 `find('slug')` がエラーを引き起こします。 +しかし、 `find('slugged')` が利用可能になります。 +特に、ビヘイビアーが他のファインダーメソッドを実装していた場合、 +それらは設定に含まれていないため **利用できません** 。 + +公開されたメソッドは設定によって決まるので、ビヘイビアーをテーブルに追加するときに、 +ファインダーメソッドの名前を変更/削除することもできます。例えば以下のようにします。 : + +``` php +// テーブルの initialize() メソッド内で +$this->addBehavior('Sluggable', [ + 'implementedFinders' => [ + 'slugged' => 'findSlug', + ] +]); +``` + +## リクエストデータをエンティティープロパティーに変換する + +ビヘイビアーは、 `Cake\ORM\PropertyMarshalInterface` を実装することによって、 +カスタムフィールドがどのように変換されるかについてのロジックを定義できます。 +このインターフェイスでは、1つのメソッドを実装する必要があります。 : + +``` php +public function buildMarshalMap($marshaller, $map, $options) +{ + return [ + 'custom_behavior_field' => function ($value, $entity) { + // 必要であれば値を変換 + return $value . '123'; + } + ]; +} +``` + +`TranslateBehavior` には、参照される可能性のあるこのインターフェースの重要な実装があります。 + +## ロードされたビヘイビアーの削除 + +テーブルからビヘイビアーを削除するには、 `removeBehavior()` メソッドを呼び出します。 : + +``` php +// 読み込まれたビヘイビアーを削除 +$this->removeBehavior('Sluggable'); +``` + +## ロードされたビヘイビアーへのアクセス + +ビヘイビアーをテーブルインスタンスに追加したら、読み込まれたビヘイビアーの情報を確認(introspect)したり、 +`BehaviorRegistry` を使用して特定のビヘイビアーにアクセスしたりできます。 : + +``` php +// どのビヘイビアーが読み込まれたかを調べる +$table->behaviors()->loaded(); + +// 特定のビヘイビアーが読み込まれたかどうかを調べる +// プラグインプレフィックスを含めないことに注意 +$table->behaviors()->has('CounterCache'); + +// 読み込まれたビヘイビアーを取得する +// プラグインプレフィックスを含めないことに注意 +$table->behaviors()->get('CounterCache'); +``` + +### ロードされたビヘイビアーの再構成 + +既にロードされているビヘイビアーの設定を変更するには、 +`BehaviorRegistry::get` コマンドを `InstanceConfigTrait` トレイトによって提供される +`config` コマンドと組み合わせることができます。 + +たとえば、親( `AppTable` など)のクラスに `Timestamp` ビヘイビアーがロードされている場合は、 +ビヘイビアーの設定を追加、変更、または削除するために、次の操作を行うことができます。 +この場合、Timestamp が応答するイベントを追加します。 : + +``` php +namespace App\Model\Table; + +use App\Model\Table\AppTable; // similar to AppController + +class UsersTable extends AppTable +{ + public function initialize(array $options): void + { + parent::initialize($options); + + // 例:親クラスが $this->addBehavior('Timestamp'); を呼び出していて、さらにイベントを追加したい場合 + if ($this->behaviors()->has('Timestamp')) { + $this->behaviors()->get('Timestamp')->setConfig([ + 'events' => [ + 'Users.login' => [ + 'last_login' => 'always' + ], + ], + ]); + } + } +} +``` diff --git a/docs/ja/orm/behaviors/counter-cache.md b/docs/ja/orm/behaviors/counter-cache.md new file mode 100644 index 0000000000..bc1106defa --- /dev/null +++ b/docs/ja/orm/behaviors/counter-cache.md @@ -0,0 +1,122 @@ +# CounterCache + +`class` Cake\\ORM\\Behavior\\**CounterCacheBehavior** + +多くの場合、ウェブアプリケーションは関連するオブジェクトの数を表示する必要があります。 +たとえば、記事のリストを表示するときに、記事のコメントの数を表示することができます。 +または、ユーザーを表示するときに、彼女が持っている友人/フォロワーの数を表示することもできます。 +CounterCache ビヘイビアーは、これらの状況を想定しています。 +CounterCache は、呼び出されたときにオプションで割り当てられた関連モデルのフィールドを更新します。 +フィールドはデータベースに存在し、INT 型でなければなりません。 + +## 基本的な使用方法 + +他のビヘイビアーと同様に CounterCache ビヘイビアーを有効にしますが、 +いくつかのリレーションとそれらのそれぞれに格納されるフィールド数を設定するまでは何も行いません。 +以下の例を使用して、各記事のコメント数を次のようにキャッシュすることができます。 : + +``` php +class CommentsTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('CounterCache', [ + 'Articles' => ['comment_count'] + ]); + } +} +``` + +CounterCache の設定は、リレーション名のマップとそのリレーションに対する具体的な設定でなければなりません。 + +カウンターの値は、エンティティーが保存または削除されるたびに更新されます。 +`updateAll()` または `deleteAll()` を使用するか、作成した SQL を実行すると、 +カウンターは更新 **されません。** + +## 高度な使用方法 + +キャッシュされたカウンターを関連するすべてのレコードより少なく保つ必要がある場合は、 +カウンター値を生成するために追加の条件またはファインダーメソッドを指定できます。 : + +``` php +// 特定の find メソッドを使用する。 +// find(published) の場合 +$this->addBehavior('CounterCache', [ + 'Articles' => [ + 'comment_count' => [ + 'finder' => 'published' + ] + ] +]); +``` + +カスタムファインダーメソッドがない場合は、代わりにレコードを検索するための条件の配列を指定できます。 : + +``` php +$this->addBehavior('CounterCache', [ + 'Articles' => [ + 'comment_count' => [ + 'conditions' => ['Comments.spam' => false] + ] + ] +]); +``` + +CounterCache で複数のフィールドを更新する場合(条件付きカウントと基本カウントの両方を表示するなど)、 +これらのフィールドを配列に追加できます。 : + +``` php +$this->addBehavior('CounterCache', [ + 'Articles' => ['comment_count', + 'published_comment_count' => [ + 'finder' => 'published' + ] + ] +]); +``` + +独自の CounterCache フィールド値を計算する場合は、 `ignoreDirty` オプションを +`true` に設定します。 +これにより、前に dirty を設定した場合、フィールドが再計算されなくなります。 : + +``` php +$this->addBehavior('CounterCache', [ + 'Articles' => [ + 'comment_count' => [ + 'ignoreDirty' => true + ] + ] +]); +``` + +最後に、カスタムファインダーと条件が適切でない場合は、コールバックメソッドを提供することができます。 +この呼び出し可能オブジェクトは、格納するカウント値を返さなければなりません。 : + +``` php +$this->addBehavior('CounterCache', [ + 'Articles' => [ + 'rating_avg' => function ($event, $entity, $table, $original) { + return 4.5; + } + ] +]); +``` + +あなたの関数は、カウンター列の更新をスキップするために `false` を返したり、 +カウント値を生成した `Query` オブジェクトを返すことができます。 +`Query` オブジェクトを返すと、そのクエリーは update 文のサブクエリーとして使われます。 +`$table` パラメーターは、便宜上、ビヘイビアーを保持している (ターゲット関係ではない) +テーブルオブジェクトを参照します。コールバックは、 `$original` に `false` が設定されて +少なくとも1回呼び出されます。 +entity-update がアソシエーションを変更した場合、コールバックは `true` で *2回* 呼び出され、 +戻り値は *以前* に関連付けられたアイテムのカウンターを更新します。 + +> [!NOTE] +> CounterCache ビヘイビアーは、 `belongsTo` アソシエーションに対してのみ機能します。 +> たとえば、 "Comments belongsTo Articles" の場合、Article テーブルの `comment_count` +> を生成するために、 CommentsCache ビヘイビアーを `CommentsTable` に追加する必要があります。 +> +> これを `belongsToMany` アソシエーションに対して機能させることは可能ですが、 +> アソシエーションオプションで設定されたカスタム `through` テーブルで CounterCache +> ビヘイビアーを有効にして `cascadeCallbacks` 設定オプションを true にする必要があります。 +> カスタム JOIN テーブルを設定する方法は [Using The Through Option](../../orm/associations#using-the-through-option) を参照してください。 diff --git a/docs/ja/orm/behaviors/timestamp.md b/docs/ja/orm/behaviors/timestamp.md new file mode 100644 index 0000000000..34f8e1f8a1 --- /dev/null +++ b/docs/ja/orm/behaviors/timestamp.md @@ -0,0 +1,79 @@ +# Timestamp + +`class` Cake\\ORM\\Behavior\\**TimestampBehavior** + +Timestamp ビヘイビアーは、モデルのイベントのたびにテーブルオブジェクトのタイムスタンプを更新します。 +これは、主に `created` や `modified` フィールドにデータを投入するために使用されます。 +けれども、設定を追加すると、任意の timestamp と datatime カラムを任意のイベントで更新することができます。 + +## 一般的な使い方 + +あなたは他のビヘイビアーと同様に、timestamp ビヘイビアーを以下の様に有効にできます。 : + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Timestamp'); + } +} +``` + +デフォルトの設定は以下のようになっています: + +- 新しく Entity を保存するとき、 `created` と `modified` に現在の日時を設定します。 +- Entity を更新したとき、 `modified` に現在の日時を設定します。 + +## 使い方と設定方法 + +もし別の名前でフィールドを更新する必要があるときや、カスタムイベントでさらなるタイムスタンプフィールドを更新したい場合、次のような追加設定を使うことができます。 : + +``` php +class OrdersTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Timestamp', [ + 'events' => [ + 'Model.beforeSave' => [ + 'created_at' => 'new', + 'updated_at' => 'always', + ], + 'Orders.completed' => [ + 'completed_at' => 'always' + ] + ] + ]); + } +} +``` + +上記の例では、標準の `Model.beforeSave` イベントに加えて、注文完了時に `completed_at` カラムを更新しています。 + +## Entity での Timestamp 更新 + +しばしば、他のプロパティーを変更せずに、エンティティーのタイムスタンプのみ更新したいこともあるでしょう。 +これは、レコードに「touch する」と呼ばれます。まさにこれをするために CakePHP では `touch()` メソッドを使うことができます。 : + +``` php +// Model.beforeSave イベントに基づいて touch します +$articles->touch($article); + +// 指定したイベントに基づいて touch します +$orders->touch($order, 'Orders.completed'); +``` + +Entity を保存後、フィールドが更新されます。 + +レコードの touch は、子リソースが作成や更新されたときに親リソースを変更するためのシグナルがほしい際に便利です。 +例えば、新しくコメントが追加されたときに記事を更新するといったことです。 + +## 編集のタイムスタンプ無しで更新の保存 + +エンティティーを保存する際の updated タイムスタンプカラムの自動更新を無効化するには、その属性を 'dirty' としてマークします。 : + +``` php +// modified カラムを dirty としてマークして、更新時に現在の値がセットされるようにします。 +$order->setDirty('modified', true); +``` diff --git a/docs/ja/orm/behaviors/translate.md b/docs/ja/orm/behaviors/translate.md new file mode 100644 index 0000000000..3b2136f3f9 --- /dev/null +++ b/docs/ja/orm/behaviors/translate.md @@ -0,0 +1,503 @@ +# Translate + +`class` Cake\\ORM\\Behavior\\**TranslateBehavior** + +Translate ビヘイビアーは、エンティティーの複数の言語で翻訳されたコピーを作成し、取得することができます。 +これは、独立した `i18n` テーブルを使って行います。このテーブルと結び付けられている任意の +Table オブジェクトの各フィールドの翻訳を格納します。 + +> [!WARNING] +> TranslateBehavior は、現時点では複合主キーをサポートしていません。 + +## クイックツアー + +データベース内に `i18n` テーブルを作成した後、翻訳できるようにしたい +Table オブジェクトにビヘイビアーを追加してください。 : + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Translate', [ + 'defaultLocale' => 'en_GB', + ]); + } +} +``` + +次に、アプリケーションの言語を変更して、エンティティーを取得するために使用する言語を選択します。 +これはすべての翻訳に影響します。 : + +``` php +// コントロールの中。例えば、スペイン語にロケールを変更。 +I18n::setLocale('es'); +$this->loadModel('Articles'); +``` + +次に、既存のエンティティーを取得します。 : + +``` php +$article = $this->Articles->get(12); +echo $article->title; // 'A title' と出力、まだ未翻訳。 +``` + +次に、エンティティーを翻訳します。 : + +``` php +$article->title = 'Un Artículo'; +$this->Articles->save($article); +``` + +再びエンティティーを取得してみます。 : + +``` php +$article = $this->Articles->get(12); +echo $article->title; // 'Un Artículo' と出力、超簡単! +``` + +複数の翻訳での動作は、Entity クラスに特別なトレイトを使用して行うことができます。 : + +``` php +use Cake\ORM\Behavior\Translate\TranslateTrait; +use Cake\ORM\Entity; + +class Article extends Entity +{ + use TranslateTrait; +} +``` + +これで、単一のエンティティーのすべての翻訳を見つけることができます。 : + +``` php +$article = $this->Articles->find('translations')->first(); +echo $article->translation('es')->title; // 'Un Artículo' + +echo $article->translation('en')->title; // 'An Article'; +``` + +同様に複数の翻訳を一度に保存することも簡単です。 : + +``` php +$article->translation('es')->title = 'Otro Título'; +$article->translation('fr')->title = 'Un autre Titre'; +$this->Articles->save($article); +``` + +動作の仕方や必要に応じたビヘイビアーの調整方法について詳しく知りたい場合は、 +この章の残りの部分を読んでください。 + +## i18n データベーステーブルの初期化 + +ビヘイビアーを使用するためには、正しいスキーマで `i18n` テーブルを作成する必要があります。 +現在、 `i18n` テーブルを読み込む唯一の方法は、手動でデータベースに次の +SQL スクリプトを実行することです。 + +``` sql +CREATE TABLE i18n ( + id int NOT NULL auto_increment, + locale varchar(6) NOT NULL, + model varchar(255) NOT NULL, + foreign_key int(10) NOT NULL, + field varchar(255) NOT NULL, + content text, + PRIMARY KEY (id), + UNIQUE INDEX I18N_LOCALE_FIELD(locale, model, foreign_key, field), + INDEX I18N_FIELD(model, foreign_key, field) +); +``` + +スキーマは、 **/config/schema/i18n.sql** の中で sql ファイルとして提供されています。 + +言語省略形の注意: Translate ビヘイビアーは言語識別子に制限はありませんが、 +可能な値は、 `locale` カラムの型とサイズによってのみ制限されています。 +`es-419` (ラテンアメリカのスペイン語、言語省略形と地域コード +[UN M.49](https://en.wikipedia.org/wiki/UN_M.49)) のように省略形を使用したい場合に備えて、 +`locale` は `varchar(6)` として定義されます。 + +> [!TIP] +> [国際化と地域化](../../core-libraries/internationalization-and-localization) +> で必要なものと同じ言語省略形を使用するのが賢明です。そうすると、一貫性があり、 +> 言語を切り替えることは、 `Translate ビヘイビアー` と `国際化と地域化` の両方が等しく動作します。 + +したがって、 `en` 、 `fr` 、 `de` のような 2 文字の ISO コードまたは、 +言語とそれが話される国の両方が含まれている `fr_FR` 、 `es_AR` 、 `da_DK` +のような完全なロケール名のいずれかを使用することをお勧めします。 + +## Table への Translate ビヘイビアーの追加 + +ビヘイビアーの追加は、Table クラスの `initialize()` メソッドで行うことができます。 : + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Translate', ['fields' => ['title', 'body']]); + } +} +``` + +最初に注意しなければならないのは、設定配列に `fields` キーを渡す必要があるということです。 +このフィールドのリストは、どのカラムが翻訳を格納できるかをビヘイビアーに伝えるために必要です。 + +### 別の翻訳テーブルの利用 + +特定のリポジトリーを翻訳するために `i18n` 以外のテーブルを使用する場合は、 +ビヘイビアーの設定で指定することができます。これは、翻訳するテーブルが複数あり、 +それぞれのテーブルごとに格納されているデータをより明確に分離したい場合によく使用されます。 : + +``` php +class ArticlesTable extends Table +{ + + public function initialize(array $config) + { + $this->addBehavior('Translate', [ + 'fields' => ['title', 'body'], + 'translationTable' => 'ArticlesI18n' + ]); + } +} +``` + +使用する独自のテーブルに、 `field` 、 `foreign_key` 、 `locale` 、 `model` +というカラムがあることを確認する必要があります。 + +## 翻訳された内容の読み込み + +上記に示したように、読み込まれたエンティティーの有効な翻訳を選ぶために +`setLocale()` メソッドを使用することができます。 : + +``` php +// コントローラーの先頭で I18n コア機能をロード +use Cake\I18n\I18n; + +// 次に、アクションの中で言語を変更することができます。 +I18n::setLocale('es'); +$this->loadModel('Articles'); + +// 結果のすべてのエンティティーは、スペイン語の翻訳が含まれています。 +$results = $this->Articles->find()->all(); +``` + +このメソッドは、Table 内の任意のファインダーで動作します。 +たとえば、 `find('list')` で TranslateBehavior を使用することができます。 : + +``` php +I18n::setLocale('es'); +$data = $this->Articles->find('list')->toArray(); + +// データが含まれます。 +[1 => 'Mi primer artículo', 2 => 'El segundo artículo', 15 => 'Otro articulo' ...] +``` + +### エンティティーのすべての翻訳を取得 + +翻訳されたコンテンツを更新するためのインターフェイスを構築するときに、 +同時に1つまたは複数の翻訳を表示すると便利です。 +このために `translations` ファインダーを使用することができます。 : + +``` php +// すべての対応する翻訳を持つ最初の記事を検索 +$article = $this->Articles->find('translations')->first(); +``` + +上記の例では、 `_translations` プロパティーが設定されたエンティティーのリストを取得します。 +このプロパティーは、翻訳データエンティティーのリストが含まれます。 +たとえば、次のプロパティーがアクセス可能になります。 : + +``` text +// 出力結果 'en' +echo $article->_translations['en']->locale; + +// 出力結果 'title' +echo $article->_translations['en']->field; + +// 出力結果 'My awesome post!' +echo $article->_translations['en']->body; +``` + +このデータを扱うためのよりエレガントな方法は、 +テーブルに使用されるエンティティークラスにトレイトを追加することです。 : + +``` php +use Cake\ORM\Behavior\Translate\TranslateTrait; +use Cake\ORM\Entity; + +class Article extends Entity +{ + use TranslateTrait; +} +``` + +このトレイトには `translation` というメソッドが含まれています。 +これにより、新しい翻訳エンティティーにその場でアクセスしたり作成することができます。 : + +``` php +// 出力結果 'title' +echo $article->translation('en')->title; + +// 新しい翻訳データエンティティーを article に追加 +$article->translation('de')->title = 'Wunderbar'; +``` + +### 取得する翻訳を制限 + +特定のレコードセットのためにデータベースから取得される言語を制限することができます。 : + +``` php +$results = $this->Articles->find('translations', [ + 'locales' => ['en', 'es'] +]); +$article = $results->first(); +$spanishTranslation = $article->translation('es'); +$englishTranslation = $article->translation('en'); +``` + +### 空の翻訳の取得を防止 + +翻訳レコードには任意の文字列を含めることができますが、 +もしレコードが翻訳されて空文字列('')として格納されている場合、 +translate ビヘイビアーは元のフィールド値を上書きします。 + +これが望ましくない場合は、 `allowEmptyTranslations` 設定キーを使用して、 +空である翻訳を無視することができます。 : + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Translate', [ + 'fields' => ['title', 'body'], + 'allowEmptyTranslations' => false + ]); + } +} +``` + +上記は、内容のある翻訳されたデータのみを読み込みます。 + +### アソシエーションのすべての翻訳を取得 + +単一の検索操作で任意のアソシエーションの翻訳を見つけることもできます。 : + +``` php +$article = $this->Articles->find('translations')->contain([ + 'Categories' => function ($query) { + return $query->find('translations'); + } +])->first(); + +// 出力結果 'Programación' +echo $article->categories[0]->translation('es')->name; +``` + +これは、 `Categories` に TranslateBehavior が追加されていることを前提としています。 +アソシエーションで `translations` カスタムファインダーを使用するには、クエリービルダー関数の +`contain` 句を単に使用するだけです。 + +### I18n::setLocale を使用せずに一つの言語の取得 + +`I18n::setLocale('es');` を呼び出すと、翻訳されたすべての検索のデフォルトロケールが変更されますが、 +アプリケーションの状態を変更せずに翻訳されたコンテンツを取得したいことがあります。 +これらの状況には、ビヘイビアーの `setLocale()` メソッドを使用してください。 : + +``` php +I18n::setLocale('en'); // 説明のためにリセット + +$this->loadModel('Articles'); +// 特定のロケール。3.6 より前は locale() メソッドを使用してください。 +$this->Articles->setLocale('es'); + +$article = $this->Articles->get(12); +echo $article->title; // 'Un Artículo' と出力、超簡単! +``` + +これは、Articles テーブルのみのロケールを変更し、 +関連するデータの言語に影響を与えないことに注意してください。 +関連するデータに影響を与えるためには、各テーブルでメソッドを呼び出すことが必要です。例えば、 : + +``` php +I18n::setLocale('en'); // 説明のためにリセット + +$this->loadModel('Articles'); +// 3.6 より前は locale() メソッドを使用してください。 +$this->Articles->setLocale('es'); +$this->Articles->Categories->setLocale('es'); + +$data = $this->Articles->find('all', ['contain' => ['Categories']]); +``` + +この例では、 `Categories` も TranslateBehavior が追加されていることを前提としています。 + +### 翻訳されたフィールドのクエリー + +TranslateBehavior は、デフォルトでは検索条件を置換しません。 +翻訳されたフィールドの検索条件を作成するには `translationField()` メソッドを使用します。 : + +``` php +// 3.6 より前は locale() メソッドを使用してください。 +$this->Articles->setLocale('es'); +$data = $this->Articles->find()->where([ + $this->Articles->translationField('title') => 'Otro Título' +]); +``` + +## 別の言語で保存 + +TranslateBehavior の背後にある哲学は、デフォルトの言語を表すエンティティー、 +およびそのエンティティー内の特定のフィールドを上書きできる複数の翻訳を持っているということです。 +これを踏まえて、直感的に任意のエンティティーの翻訳を保存することができます。 +たとえば、次のような設定になります。 : + +``` php +// src/Model/Table/ArticlesTable.php の中で +class ArticlesTable extends Table +{ + public function initialize(array $config) + { + $this->addBehavior('Translate', ['fields' => ['title', 'body']]); + } +} + +// src/Model/Entity/Article.php の中で +class Article extends Entity +{ + use TranslateTrait; +} + +// コントローラーの中で +$this->loadModel('Articles'); +$article = new Article([ + 'title' => 'My First Article', + 'body' => 'This is the content', + 'footnote' => 'Some afterwords' +]); + +$this->Articles->save($article); +``` + +最初の記事を保存した後、その翻訳を保存することができる2通りの方法があります。 +1つ目、エンティティーに直接言語を設定します。 : + +``` php +$article->_locale = 'es'; +$article->title = 'Mi primer Artículo'; + +$this->Articles->save($article); +``` + +エンティティーが保存された後、翻訳されたフィールドも同様に永続化されますが、 +もう1つの注意点は、デフォルトの言語の値は上書きされずに保存されることです。 : + +``` text +// 出力結果 'This is the content' +echo $article->body; + +// 出力結果 'Mi primer Artículo' +echo $article->title; +``` + +一度、値を上書きすると、そのフィールドの翻訳が保存され、通常通りに取得することができます。 : + +``` php +$article->body = 'El contendio'; +$this->Articles->save($article); +``` + +別の言語でエンティティーを保存するために使用する2つ目の方法は、 +直接テーブルにデフォルトの言語を設定することです。 : + +``` php +$article->title = 'Mi Primer Artículo'; + +// 3.6 より前は locale() メソッドを使用してください。 +$this->Articles->setLocale('es'); +$this->Articles->save($article); +``` + +同じ言語でエンティティーを取得や保存の両方が必要な時や、複数のエンティティーを一括で保存する時に、 +テーブルに直接言語を設定すると便利です。 + +## 複数の翻訳を保存 + +任意のデータベースのレコードに複数の翻訳を同時に追加したり編集したりできるようにすることは、 +一般的な要件です。これは、 `TranslateTrait` を使用して行うことができます。 : + +``` php +use Cake\ORM\Behavior\Translate\TranslateTrait; +use Cake\ORM\Entity; + +class Article extends Entity +{ + use TranslateTrait; +} +``` + +次に、それらを保存する前に翻訳を取り込むことができます。 : + +``` php +$translations = [ + 'fr' => ['title' => "Un article"], + 'es' => ['title' => 'Un artículo'] +]; + +foreach ($translations as $lang => $data) { + $article->translation($lang)->set($data, ['guard' => false]); +} + +$this->Articles->save($article); +``` + +3.3.0 では、複数の翻訳での動作は簡素化されました。 +翻訳されたフィールドのフォームコントロールを作成することができます。 : + +``` php +// ビューテンプレートの中で +Form->create($article); ?> +
    + French + Form->control('_translations.fr.title'); ?> + Form->control('_translations.fr.body'); ?> +
    +
    + Spanish + Form->control('_translations.es.title'); ?> + Form->control('_translations.es.body'); ?> +
    +``` + +コントローラーの中では、通常通りにデータをマーシャリングできます。 : + +``` php +$article = $this->Articles->newEntity($this->request->getData()); +$this->Articles->save($article); +``` + +これは、すべてが永続化されたフランス語とスペイン語の翻訳の記事になります。 +同様に、エンティティーの `$_accessible` フィールドの中に +`_translations` を追加することを忘れないでください。 + +### 翻訳されたエンティティーの検証 + +モデルに `TranslateBehavior` を追加した場合、 `newEntity()` や `patchEntity()` で、 +ビヘイビアーによって翻訳レコードが作成・更新される際に使用するバリデータを定義できます。 : + +``` php +class ArticlesTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Translate', [ + 'fields' => ['title'], + 'validator' => 'translated' + ]); + } +} +``` + +上記は、翻訳されたエンティティーを検証するための `validationTranslated` +によって作成されるバリデータを使用します。 diff --git a/docs/ja/orm/behaviors/tree.md b/docs/ja/orm/behaviors/tree.md new file mode 100644 index 0000000000..bc4f908d1c --- /dev/null +++ b/docs/ja/orm/behaviors/tree.md @@ -0,0 +1,324 @@ +# Tree + +`class` Cake\\ORM\\Behavior\\**TreeBehavior** + +データベーステーブル内に階層データを格納することは、かなり一般的です。 +そのようなデータの例としては、無制限のサブカテゴリを有するカテゴリ、 +マルチレベルメニューシステムに関連するデータ、または企業内の部門などの +階層のリテラル表現があります。 + +リレーショナル・データベースは、通常、 +このタイプのデータを格納および検索するのには適していませんが、 +複数レベルの情報を扱うために効果的な方法がいくつかあります。 + +TreeBehavior は、オーバーヘッドをほとんどかけることなく照会できる階層型のデータ構造を +データベースに保持し、検索や表示の処理のためのツリーデータを再構築するのに役立ちます。 + +## 必要条件 + +このビヘイビアーは、テーブル内の以下のカラムが必要です。 + +- `parent_id` (null も可能) 親の行の ID を保持するカラム +- `lft` (整数、符号付き) ツリー構造を維持するために使用 +- `rght` (整数、符号付き) ツリー構造を維持するために使用 + +カスタマイズする必要がある場合は、これらのフィールドの名前を設定できます。 +フィールドの意味とその使用方法の詳細については、 [MPTT ロジック](https://www.sitepoint.com/hierarchical-data-database-2/) +に書いてある記事を参照してください。 + +> [!WARNING] +> TreeBehavior は、現時点では複合主キーをサポートしていません。 + +## クイックツアー + +階層データを格納したい Table に Tree ビヘイビアーを追加して有効にします。 : + +``` php +class CategoriesTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Tree'); + } +} +``` + +テーブルがすでにいくつかの行を保持している場合、一度追加すると +CakePHP は内部構造を構築することができます。 : + +``` php +$categories = $this->getTableLocator()->get('Categories'); +$categories->recover(); +``` + +テーブルから行を取得し、その行が持つ子孫の数を調べることで動作することを確認できます。 : + +``` php +$node = $categories->get(1); +echo $categories->childCount($node); +``` + +同様に、ノードの子孫のフラットなリストを取得することは簡単です。 : + +``` php +$descendants = $categories->find('children', ['for' => 1]); + +foreach ($descendants as $category) { + echo $category->name . "\n"; +} +``` + +条件を渡す必要がある場合は、通常通り: + +``` php +$descendants = $categories + ->find('children', ['for' => 1]) + ->where(['name LIKE' => '%Foo%']) + ->all(); + +foreach ($descendants as $category) { + echo $category->name . "\n"; +} +``` + +代わりに、各ノードの子が階層内にネストされているスレッドリストが必要な場合は、 +'threaded' ファインダを積み重ねられます。 : + +``` php +$children = $categories + ->find('children', ['for' => 1]) + ->find('threaded') + ->toArray(); + +foreach ($children as $child) { + echo "{$child->name} は、直下の子が " . count($child->children) . " あります。"; +} +``` + +スレッド化された結果を取得するには通常、再帰関数が必要ですが、HTML を選択するなど、 +リストを表示できるように各レベルの単一のフィールドを含む結果セットのみが必要な場合は、 +'treeList' ファインダを使用する方が良いです。 : + +``` php +$list = $categories->find('treeList'); + +// CakePHP テンプレートファイルの中で +echo $this->Form->control('categories', ['options' => $list]); + +// もしくは、CLI スクリプトなどでプレーンテキストで出力できます +foreach ($list as $categoryName) { + echo $categoryName . "\n"; +} +``` + +出力は次のようになります。 : + + My Categories + _Fun + __Sport + ___Surfing + ___Skating + _Trips + __National + __International + +`treeList` ファインダはいくつかのオプションを持ちます。 + +- `keyPath`: 配列キーに使用するフィールドを取得するためのドット区切りパス、 + または指定された行からキーを返すためのクロージャ。 +- `valuePath`: 配列値に使用するフィールドを取得するドット区切りパス、 + または指定された行から値を返すクロージャ。 +- `spacer`: 各項目のツリーの深さを表すプレフィックスとして使用される文字列 + +使用できるすべてのオプションの例です。 : + +``` php +$query = $categories->find('treeList', [ + 'keyPath' => 'url', + 'valuePath' => 'id', + 'spacer' => ' ' +]); +``` + +クロージャーを使用する例です。 : + +``` php +$query = $categories->find('treeList', [ + 'keyPath' => 'url', + 'valuePath' => function($entity){ + return $entity->url . ' ' . $entity->id + }, + 'spacer' => ' ' +]); +``` + +とても一般的な作業の1つは、特定のノードからツリーのルートまでのツリーパスを見つけることです。 +例えば、メニュー構造を表すパンくずリストを追加するのに便利です。 : + +``` php +$nodeId = 5; +$crumbs = $categories->find('path', ['for' => $nodeId]); + +foreach ($crumbs as $crumb) { + echo $crumb->name . ' > '; +} +``` + +TreeBehavior で構築されたツリーは `lft` 以外のカラムでソートすることはできません。 +ツリーの内部表現はこのソートに依存するからです。幸いなことに、自分の親を変更することなく、 +同じレベルの内部ノードを並べ替えることができます。 : + +``` php +$node = $categories->get(5); + +// 子どもをリストアップするときに1つ上の位置に表示されるようにノードを移動します。 +$categories->moveUp($node); + +// 同じレベルの中でリストの先頭にノードを移動します。 +$categories->moveUp($node, true); + +// 一番下にノードを移動します。 +$categories->moveDown($node, true); +``` + +## 設定 + +このビヘイビアーによって使用されるデフォルトのカラム名が、スキーマと一致しない場合、 +それらの別名を提供することができます。 : + +``` php +public function initialize(array $config) +{ + $this->addBehavior('Tree', [ + 'parent' => 'ancestor_id', // parent_id の代わりに使用 + 'left' => 'tree_left', // lft の代わりに使用 + 'right' => 'tree_right' // rght の代わりに使用 + ]); +} +``` + +## ノードレベル (深さ) + +ツリーノードの深さを知ることは、例えばメニューを生成するときなど、 +一定のレベルまでノードを検索したい時に役に立ちます。 `level` オプションを使うことで、 +各ノードのレベルを保存するフィールドを指定することができます。 : + +``` php +$this->addBehavior('Tree', [ + 'level' => 'level', // デフォルトは null で、レベルは保存しません +]); +``` + +db フィールドを使用してレベルをキャッシュしたくない場合、ノードのレベルを取得するために +`TreeBehavior::getLevel()` メソッドが使用できます。 + +## スコープとマルチツリー + +時には、同じテーブルの中に複数のツリー構造を保持したい場合は、 'scope' 設定を使用して達成できます。 +たとえば、locations テーブルでは、国ごとに1つのツリーを作成することができます。 : + +``` php +class LocationsTable extends Table +{ + public function initialize(array $config): void + { + $this->addBehavior('Tree', [ + 'scope' => ['country_name' => 'Brazil'] + ]); + } +} +``` + +前の例では、すべてのツリーの操作は、 `country_name` カラムに +'Brazil' がセットされている行のみに限定されます。 +'config' 関数を使って、その場でスコープを変更することができます。 : + +``` php +$this->behaviors()->Tree->config('scope', ['country_name' => 'France']); +``` + +必要に応じて、スコープとしてクロージャを渡すことで、スコープのより細かい制御ができます。 : + +``` php +$this->behaviors()->Tree->config('scope', function ($query) { + $country = $this->getConfigureContry(); // 作成した関数 + return $query->where(['country_name' => $country]); +}); +``` + +## 独自のソートフィールドでのリカバリ + +デフォルトでは、recover() は、主キーを使用して項目を並べ替えます。 +これは数字の(自動インクリメント)カラムであればうまくいきますが、 +UUID を使用すると奇妙な結果につながる可能性があります。 + +リカバリのための独自のソートが必要な場合は、設定で独自の order 句を設定できます。 : + +``` php +$this->addBehavior('Tree', [ + 'recoverOrder' => ['country_name' => 'DESC'], +]); +``` + +## 階層データの保存 + +Tree ビヘイビアーを使用しているときは、通常、階層構造の内部表現を心配する必要はありません。 +ツリーに配置されているノードの位置は、各エンティティーの 'parent_id' カラムから推定されます。 : + +``` php +$aCategory = $categoriesTable->get(10); +$aCategory->parent_id = 5; +$categoriesTable->save($aCategory); +``` + +ツリー内にループ(そのノード自身を子ノードにする)を作成または保存しようとする場合、 +存在しない親 ID を提供すると例外がスローされます。 + +'parent_id' カラムを null に設定すると、ツリー内のノードをルートにすることができます。 : + +``` php +$aCategory = $categoriesTable->get(10); +$aCategory->parent_id = null; +$categoriesTable->save($aCategory); +``` + +新しいルートノードの子供は保存されます。 + +## ノードの削除 + +ノードとそのすべてのサブツリー(ツリーの任意の深さにある子孫)を削除することは簡単です。 : + +``` php +$aCategory = $categoriesTable->get(10); +$categoriesTable->delete($aCategory); +``` + +TreeBehavior は、内部のすべての削除操作を処理します。 +また、1つのノードを削除し、ツリー内のすぐ上位の親ノードにすべての子を再割り当てすることもできます。 : + +``` php +$aCategory = $categoriesTable->get(10); +$categoriesTable->removeFromTree($aCategory); +$categoriesTable->delete($aCategory); +``` + +すべての子ノードが保持され、新しい親が割り当てられます。 + +ノードの削除は、エンティティーの lft と rght の値に基づいて行われます。 +これは条件付き削除のためにノードのさまざまな子をループするときに注意することは重要です。 : + +``` php +$descendants = $teams->find('children', ['for' => 1])->all(); + +foreach ($descendants as $descendant) { + $team = $teams->get($descendant->id); // 最新のエンティティーオブジェクトを検索 + if ($team->expired) { + $teams->delete($team); // 削除して、データベースに登録された lft と rght を並び替えます + } +} +``` + +TreeBehavior は、ノードが削除されたときに、テーブル内のレコードの lft と rght の値を並べ替えます。 +したがって、(削除操作の前に保存された) `$descendants` 内のエンティティーの lft と rght の値は +不正確になります。テーブルの不一致を防ぐために、エンティティーは、 +その場で読み込みおよび変更する必要があります。 diff --git a/docs/ja/orm/database-basics.md b/docs/ja/orm/database-basics.md new file mode 100644 index 0000000000..8951911f50 --- /dev/null +++ b/docs/ja/orm/database-basics.md @@ -0,0 +1,1044 @@ +# データベースの基本 + +CakePHP データベースアクセス層は、リレーショナルデータベースを扱うほとんどの面を +抽象化して、サーバーコネクションの保持、クエリーの生成、SQL インジェクションの防止、 +スキーマチェックと変更、デバッグとデータベースに送信したクエリーのプロファイリング +などの支援を提供します。 + +## クイックツアー + +この章で説明する機能は、下位レベルのデータベースアクセス API でできることを説明します。 +もし ORM についてもっと詳細に知りたい場合は、 [クエリービルダー](../orm/query-builder) +や [テーブルオブジェクト](../orm/table-objects) のセクションを参照してください。 + +データベース接続を作る一番簡単な方法は、 `DSN` 文字列を使います。 : + +``` php +use Cake\Datasource\ConnectionManager; + +$dsn = 'mysql://root:password@localhost/my_database'; +ConnectionManager::setConfig('default', ['url' => $dsn]); +``` + +作成したら、コネクションオブジェクトにアクセスして使えるようになります。 : + +``` php +$connection = ConnectionManager::get('default'); +``` + +### サポートしているデータベース + +CakePHP は下記のリレーショナルデータベースをサポートしています。 + +- MySQL 5.1+ +- SQLite 3 +- PostgreSQL 8.3+ +- SQLServer 2008+ +- Oracle (コミュニティープラグイン経由) + +上記のデータベースそれぞれについて、適切な PDO 拡張がインストールされている必要があります。 +プロシージャー型 API はサポートされていません。 + +Oracle データベースは、 +[Driver for Oracle Database](https://github.com/CakeDC/cakephp-oracle-driver) +コミュニティープラグインを経由してサポートされます。 + +### Select 文の実行 + +生の SQL クエリーを実行するのは非常に簡単です。 : + +``` php +use Cake\Datasource\ConnectionManager; + +$connection = ConnectionManager::get('default'); +$results = $connection->execute('SELECT * FROM articles')->fetchAll('assoc'); +``` + +パラメーターを追加するには、プリペアードステートメントを使います。 : + +``` php +$results = $connection + ->execute('SELECT * FROM articles WHERE id = :id', ['id' => 1]) + ->fetchAll('assoc'); +``` + +これは引数として複合データ型を使用することも可能です。 : + +``` php +use Cake\Datasource\ConnectionManager; +use DateTime; + +$connection = ConnectionManager::get('default'); +$results = $connection + ->execute( + 'SELECT * FROM articles WHERE created >= :created', + ['created' => new DateTime('1 day ago')], + ['created' => 'datetime'] + ) + ->fetchAll('assoc'); +``` + +SQL 文を手で書く代わりに、クエリービルダーを使うこともできます。 : + +``` php +$results = $connection + ->newQuery() + ->select('*') + ->from('articles') + ->where(['created >' => new DateTime('1 day ago')], ['created' => 'datetime']) + ->order(['title' => 'DESC']) + ->execute() + ->fetchAll('assoc'); +``` + +### Insert 文の実行 + +データベースに行を追加するのは、通常は数行の話しです。 : + +``` php +use Cake\Datasource\ConnectionManager; +use DateTime; + +$connection = ConnectionManager::get('default'); +$connection->insert('articles', [ + 'title' => 'A New Article', + 'created' => new DateTime('now') +], ['created' => 'datetime']); +``` + +### Update 文の実行 + +データベースの行の更新も同様に直感的に可能で、下記の例では article の **id** が 10 の +データを更新しています。 : + +``` php +use Cake\Datasource\ConnectionManager; +$connection = ConnectionManager::get('default'); +$connection->update('articles', ['title' => 'New title'], ['id' => 10]); +``` + +### Delete 文の実行 + +同様に `delete()` メソッドはデータベースから行を削除するために使われ、 +下記の例では article の **id** が 10 の行を削除しています。 : + +``` php +use Cake\Datasource\ConnectionManager; +$connection = ConnectionManager::get('default'); +$connection->delete('articles', ['id' => 10]); +``` + +## 設定 + +慣例として、データベース接続は **config/app.php** に設定します。 +このファイルに定義された接続情報は、アプリケーションが使用する接続構成を生成する +`Cake\Datasource\ConnectionManager` に引き渡します。 +サンプルとなる接続情報が **config/app.default.php** にあります。 +サンプルの接続設定は、次のようになります。 : + +``` php +'Datasources' => [ + 'default' => [ + 'className' => 'Cake\Database\Connection', + 'driver' => 'Cake\Database\Driver\Mysql', + 'persistent' => false, + 'host' => 'localhost', + 'username' => 'my_app', + 'password' => 'secret', + 'database' => 'my_app', + 'encoding' => 'utf8mb4', + 'timezone' => 'UTC', + 'cacheMetadata' => true, + ] +], +``` + +上記は指定されたパラメーターを持つ 'default' 接続を生成します。 +あなたは設定ファイルに必要な数だけ接続を定義することができます。 +また、 `Cake\Datasource\ConnectionManager::config()` を使って +実行時に追加の設定をおこなうこともできます。その一例は次のようになります。 : + +``` php +use Cake\Datasource\ConnectionManager; + +ConnectionManager::setConfig('default', [ + 'className' => 'Cake\Database\Connection', + 'driver' => 'Cake\Database\Driver\Mysql', + 'persistent' => false, + 'host' => 'localhost', + 'username' => 'my_app', + 'password' => 'secret', + 'database' => 'my_app', + 'encoding' => 'utf8mb4', + 'timezone' => 'UTC', + 'cacheMetadata' => true, +]); +``` + +設定オプションは `DSN` 文字列形式で設定することもできます。 +これは、環境変数や `PaaS` 環境で作業する時に便利です。: + +``` css +ConnectionManager::setConfig('default', [ + 'url' => 'mysql://my_app:sekret@localhost/my_app?encoding=utf8&timezone=UTC&cacheMetadata=true', +]); +``` + +DSN 文字列を使用するときには、クエリー文字列引数として追加のパラメーターやオプションを +定義することができます。 + +デフォルトでは、すべてのテーブルオブジェクトは `default` の接続を使用します。 +デフォルト以外の接続を使用するには、 [Configuring Table Connections](../orm/table-objects#configuring-table-connections) を参照してください。 + +データベース設定ではいくつかのキーがサポートされています。使用可能なキーは下記の通りです。: + +className +データベースサーバーへの接続を行うクラスの名前空間名付きの完全クラス名。 +このクラスは、データベースドライバーをロードし、SQL トランザクションメカニズムを提供し、 +SQL を生成したりといったことを担当しています。 + +driver +ドライバークラス名は、データベースエンジンのすべての特異性を実装するために使われます。 +これは `プラグイン記法` を用いた短いクラス名でも、 +完全名前空間名でも、どちらでもドライバーインスタンスを生成することが可能です。 +短いクラス名の例は、 Mysql, Sqlite, Postgres, Sqlserver などです。 + +persistent +データベースへの持続的接続を使うかどうか。このオプションは、 SqlServer ではサポートされません。 +CakePHP バージョン 3.4.13 以降、SqlServer で `persistent` を `true` にセットすると +例外が投げられます。 + +host +データベースサーバーのホスト名 (または IP アドレス)。 + +username +アカウントのユーザー名。 + +password +アカウントのパスワード。 + +database +接続するデータベース名。データベース名に `.` の使用は避けてください。 +識別子を引用符で囲むことが難しくするため、CakePHP は、データベース名の +`.` をサポートしません。相対パスに起因する不正なパスを避けるため、 SQLite +データベースへのパスは、絶対パス (`ROOT . DS . 'my_app.db'` など) にしてください。 + +port (*optional*) +サーバーに接続する際に使用する TCP ポート または Unix ソケット。 + +encoding +サーバーに SQL 文を送信する際に使用する文字セットを示します。 +DB2 以外のデータベースでは、データベースのデフォルトエンコーディングが +デフォルト設定されます。 +もし MySQL で UTF-8 で接続したいのなら、ハイフンなしで 'utf8' と指定してください。 + +timezone +サーバーのタイムゾーンがセットされます。 + +schema +PostgreSQL データベースで特定のスキーマを使う時に設定します。 + +unix_socket +Unix ソケットファイルを経由して接続することをサポートしているドライバーによって +使用されます。PostgreSQL を使用していて、Unix ソケットを使用する場合は、 +host を空白のままにします。 + +ssl_key +SSL キー・ファイルへのファイルパス。 (MySQL のみでサポートされています)。 + +ssl_cert +SSL 証明書ファイルへのファイルパス。 (MySQL のみでサポートされています)。 + +ssl_ca +SSL 証明書の認証局へのファイルパス。 (MySQL のみでサポートされています)。 + +init +接続が作成されたときに、データベースサーバーに送信されるクエリーのリスト。 + +log +クエリーログを有効にするには `true` をセットします。 +有効なクエリーで `debug` レベルの時に、 `queriesLog` スコープでログ出力されます。 + +quoteIdentifiers +あなたがテーブルやカラム名に予約語や特殊文字を使用している場合は `true` に設定します。 +この設定を有効にすると、SQL を生成する際に [クエリービルダー](../orm/query-builder) +によって引用符で囲まれたクエリーが生成されます。 +これはクエリーを実行する前に横断的に処理を行う必要があるため、パフォーマンスを +低下させることに注意してください。 + +flags +ベースになる PDO のインスタンスに引き継がれる、 PDO 定数の連想配列。 +flags がサポートしている内容については、お使いの PDO ドライバーのマニュアルを +ご覧ください。 + +cacheMetadata +boolean 型の `true` か、メタデータを格納するキャッシュ設定の文字列のどちらか。 +メタデータのキャッシュをオフにする事はお勧めしませんし、パフォーマンスがとても +悪化します。詳細は [Database Metadata Cache](#database-metadata-cache) のセクションを +参照してください。 + +mask +生成されたデータベースファイルのパーミッションをセットします。 (SQLite のみでサポートされています) + +この時点で、あなたは [CakePHP の規約](../intro/conventions) を見たいと思うかもしれません。 +正しいテーブル名(といくつかのカラムの追加)によって、いくつかの機能を獲得して、 +設定を回避することができます。 +例えば、もしデータベースのテーブル名が big_boxes でしたら、 テーブルクラス +BigBoxesTable と、コントローラー BigBoxesController は、全て自動的に一緒に +動作します。 +慣例としてデータベースのテーブル名は、例えば bakers, pastry_stores, savory_cakes +といった具合に、アンダースコアー区切り・小文字・複数形とします。 + +## コネクションの管理 + +`class` Cake\\Datasource\\**ConnectionManager** + +`ConnectionManager` クラスは、あなたのアプリケーションがデータベース接続に +アクセスするためのレジストリーとして機能します。 +これは他のオブジェクトが既存のコネクションへの参照を取得するための場所を提供します。 + +### コネクションへのアクセス + +`static` Cake\\Datasource\\ConnectionManager::**get**($name) + +一度設定した接続は、 `Cake\Datasource\ConnectionManager::get()` を +使って取り出すことができます。 +このメソッドはすでに確立しているコネクションを返すか、もしまだ接続していないのであれば +ロードして接続してから返します。 : + +``` php +use Cake\Datasource\ConnectionManager; + +$connection = ConnectionManager::get('default'); +``` + +存在しない接続をロードしようとしたら、例外を throw します。 + +### 実行時にコネクションを生成する + +`config()` や `get()` を使用して、実行時に設定ファイルに定義されていない +コネクションを生成することができます。 : + +``` php +ConnectionManager::setConfig('my_connection', $config); +$connection = ConnectionManager::get('my_connection'); +``` + +コネクション生成時の設定についての詳細は [Database Configuration](#database-configuration) を参照してください。 + +## データの型 + +`class` Cake\\Database\\**TypeFactory** + +各ベンダーのデータベースは全て同じデータ型を持つわけではなく、似たようなデータ型が +同じ名前になっているわけでもありませんので、 CakePHP ではデータベース層で使用するために +基本的なデータ型のセットを提供しています。CakePHP がサポートしている型は、 + +string +一般的に CHAR または VARCHAR のカラムが指定されます。 +`fixed` オプションを使うと、強制的に CHAR カラムとなります。 +SQL Server では、NCHAR と NVARCHAR 型になります。 + +text +TEXT 型に変換します。 + +uuid +データベースがサポートするなら UUID 型に、さもなければ CHAR(36) に変換します。 + +integer +データベースがサポートする INTEGER 型に変換します。現時点では、 +BIT はサポートしていません。 + +smallinteger +データベースによって提供される SMALLINT 型に変換します。 + +tinyinteger +データベースによって提供される TINYINT 型か SMALLINT 型に変換します。 +MySQL の `TINYINT(1)` は、 boolean として扱われます。 + +biginteger +データベースによって提供される BIGINT 型に変換します。 + +float +データベースに応じて DOUBLE 型か FLOAT 型に変換されます。 +精度(小数点以下桁数)を指定するために `precision` オプションを使うことができます。 + +decimal +DECIMAL 型に変換されます。 `length` と `precision` オプションをサポート +します。 + +boolean +BOOLEAN に変換します。MySQL の場合は TINYINT(1) になります。現時点では、 +BIT(1) はサポートしていません。 + +binary +データベースによって提供される BLOB 型または BYTEA 型に変換します。 + +date +タイムゾーン情報を持たない DATE 型に変換されます。この型の戻り値は、ネイティブな +`DateTime` クラスを拡張した `Cake\I18n\Date` です。 + +datetime +タイムゾーン情報を持たない DATETIME 型に変換されます。 +PostgreSQL と SQL Server では、TIMESTAMP 型に変換されます。 +この型のデフォルトの戻り値は、組込みの `DateTime` クラスと +[Chronos](https://github.com/cakephp/chronos) を拡張した +`Cake\I18n\Time` クラスになります。 + +timestamp +TIMESTAMP 型に変換します。 + +time +全てのデータベースで TIME 型に変換します。 + +json +可能であれば、JSON 型に変換し、そうでなければ TEXT 型に変換します。 +'json' 型は 3.3.0 で追加されました。 + +これらの型は、テストフィクスデャーを使用している時に、CakePHP が提供する +スキーマリフレクション機能とスキーマ生成機能の両方で使用されます。 + +また、各型は PHP と SQL の表現の変換を行う機能も提供します。 +これらのメソッドはクエリー実行時に型のヒントに基づいて呼び出されます。 +例えば、 'datetime' という名前の項目なら、入力パラメーターを自動的に `DateTime` から +timestamp か 整形した日付文字列に変換します。 +同様に 'binary' という名前の項目ならファイルハンドラを受け入れ、データを読み込むときには +ファイルハンドラを生成します。 + +### DateTime タイプ + +`class` Cake\\Database\\**DateTimeType** + +ネイティブの `DATETIME` カラムタイプにマッピングします。 +PostgreSQL や SQL Server では、これは `TIMESTAMP` 型になります。 +このカラム型のデフォルトの戻り値は `Cake\I18n\FrozenTime` で、これは組み込みの +`DateTimeImmutable` クラスと [Chronos](https://github.com/cakephp/chronos) +を拡張したものです。 + +`method` Cake\\Database\\DateTimeType::**setTimezone**(string|\\DateTimeZone|null $timezone) + +データベースサーバーのタイムゾーンとアプリケーションの PHP のタイムゾーンが一致しない場合は、 +このメソッドを使用してデータベースのタイムゾーンを指定することができます。このタイムゾーンは、PHP +のオブジェクトをデータベースの日付文字列に変換する際に使用されます(その逆も同様です)。 + +`class` Cake\\Database\\**DateTimeFractionalType** + +MySQLの `DATETIME(6)` のようなマイクロ秒を含むdatetimeカラムのマッピングに使用できます。 +このタイプを使用するには、マッピングされたタイプとして追加する必要があります。: + +``` php +// config/bootstrap.php の中で +use Cake\Database\TypeFactory; +use Cake\Database\Type\DateTimeFractionalType; + +// デフォルトのdatetime型を、より正確なものに上書きします。 +TypeFactory::map('datetime', DateTimeFractionalType::class); +``` + +`class` Cake\\Database\\**DateTimeTimezoneType** + +PostgreSQLの `TIMESTAMPTZ` のような、タイムゾーンを含むdatetimeカラムをマッピングするために使用できます。 +このタイプを使用するには、マッピングされたタイプとして追加する必要があります。: + +``` php +// config/bootstrap.php の中で +use Cake\Database\TypeFactory; +use Cake\Database\Type\DateTimeTimezoneType; + +// デフォルトのdatetime型を、より正確なものに上書きします。 +TypeFactory::map('datetime', DateTimeTimezoneType::class); +``` + +### 独自の型を作成する + +`class` Cake\\Database\\**TypeFactory** + +`static` Cake\\Database\\TypeFactory::**map**($name, $class) + +もしあなたが CakePHP に実装されていない、データベース独自の型が必要な場合、 +CakePHP の型システムに新たな型を追加することができます。 +Type クラスは次のメソッドを実装することが期待されます。 + +- `toPHP`: 与えられた値をデータベース型から PHP で等価な値にキャストします。 +- `toDatabase`: 与えられた値を PHP 型からデータベースで受け入れ可能な値にキャストします。 +- `toStatement`: 与えられた値をステートメントの型にキャストします。 +- `marshal`: フラットデータを PHP オブジェクトに変換します。 + +基本的なインターフェイスを満たす簡単な方法は、 `Cake\Database\Type` を +拡張することです。例えば、もしあなたが JSON 型を追加したいなら、下記のような型クラスを +作成します。 : + +``` php +// src/Database/Type/JsonType.php の中で + +namespace App\Database\Type; + +use Cake\Database\DriverInterface; +use Cake\Database\Type\BaseType; +use PDO; + +class JsonType extends BaseType +{ + public function toPHP($value, DriverInterface $driver) + { + if ($value === null) { + return null; + } + + return json_decode($value, true); + } + + public function marshal($value) + { + if (is_array($value) || $value === null) { + return $value; + } + + return json_decode($value, true); + } + + public function toDatabase($value, DriverInterface $driver) + { + return json_encode($value); + } + + public function toStatement($value, DriverInterface $driver) + { + if ($value === null) { + return PDO::PARAM_NULL; + } + + return PDO::PARAM_STR; + } + +} +``` + +デフォルトでは `toStatement()` メソッドは新しい型の値を文字列として扱います。 +私たちは新しい型を作成したら、型マッピングに追加しなければなりません。 +アプリケーションの bootstrap 時に、次の事を行います。 : + +``` php +use Cake\Database\TypeFactory; + +TypeFactory::map('json', 'App\Database\Type\JsonType'); +``` + +こうすればスキーマ情報は新しい型で上書きされ、CakePHP のデータベース層は自動的に +JSON データを変換してクエリーを作成します。 +あなたは Table の [getSchema() メソッド](../orm/saving-data#saving-complex-types) で、 +新たに作った型のマッピングをすることができます。 : + +``` php +use Cake\Database\Schema\TableSchemaInterface; + +class WidgetsTable extends Table +{ + + public function getSchema(): TableSchemaInterface + { + $schema = parent::getSchema(); + $schema->columnType('widget_prefs', 'json'); + + return $schema; + } + +} +``` + +### 独自データ型から SQL 表現への変換 + +前の例は、SQL 文の文字列として表現しやすい 'json' カラム型のための独自データ型に変換します。 +複雑な SQL データ型は、SQL クエリーの文字列や整数として表現することはできません。 +これらのデータ型を動作させる際、あなたの Type クラスは、 +`Cake\Database\Type\ExpressionTypeInterface` インスタンスを実装する必要があります。 +例として、MySQL の `POINT` 型データのためのシンプルな Type クラスを作成します。 +最初に、PHP の `POINT` データを表現するために使用する「値」オブジェクトを定義します。 : + +``` php +// src/Database/Point.php の中で +namespace App\Database; + +// 値オブジェクトはイミュータブルです。 +class Point +{ + protected $_lat; + protected $_long; + + // ファクトリーメソッド + public static function parse($value) + { + // MySQLからWKBデータを解析します。 + $unpacked = unpack('x4/corder/Ltype/dlat/dlong', $value); + + return new static($unpacked['lat'], $unpacked['long']); + } + + public function __construct($lat, $long) + { + $this->_lat = $lat; + $this->_long = $long; + } + + public function lat() + { + return $this->_lat; + } + + public function long() + { + return $this->_long; + } +} +``` + +値オブジェクトを作成することで、この値オブジェクトや SQL 表現にデータを変換する +Type クラスが必要になります。 : + +``` php +namespace App\Database\Type; + +use App\Database\Point; +use Cake\Database\DriverInterface; +use Cake\Database\Expression\FunctionExpression; +use Cake\Database\ExpressionInterface; +use Cake\Database\Type\BaseType; +use Cake\Database\Type\ExpressionTypeInterface; + +class PointType extends BaseType implements ExpressionTypeInterface +{ + public function toPHP($value, DriverInterface $d) + { + return Point::parse($value); + } + + public function marshal($value) + { + if (is_string($value)) { + $value = explode(',', $value); + } + if (is_array($value)) { + return new Point($value[0], $value[1]); + } + + return null; + } + + public function toExpression($value): ExpressionInterface + { + if ($value instanceof Point) { + return new FunctionExpression( + 'POINT', + [ + $value->lat(), + $value->long() + ] + ); + } + if (is_array($value)) { + return new FunctionExpression('POINT', [$value[0], $value[1]]); + } + // その他のケースを処理 + } + + public function toDatabase($value, DriverInterface $driver) + { + return $value; + } +} +``` + +上記のクラスは、いくつかの興味深い特徴があります。 + +- `toPHP` メソッドは、SQL クエリーの結果を値オブジェクトにパースします。 +- `marchal` メソッドは、例えばリクエストデータで与えられたデータから値オブジェクトへ + 変換します。 `'10.24,12.34` のような文字列や配列を受け取れるようにしています。 +- `toExpression` メソッドは、値オブジェクトから同等の SQL 表現へ変換します。 + 例えば、結果の SQL は、 `POINT(10.24, 12.34)` のようになります。 + +一度独自の型を作成したら、 [独自の型をテーブルクラスと関連づける](../orm/saving-data#saving-complex-types) +必要があります。 + +### イミュータブル DateTime オブジェクトの有効化 + +Date/Time オブジェクトは容易に変更されてしまうため、CakePHP はイミュータブルな +オブジェクトを利用できるようなっています。以下の設定は、 あなたのアプリケーションの +**config/bootstrap.php** ファイル内で行うのが最適です。 : + +``` php +TypeFactory::build('datetime')->useImmutable(); +TypeFactory::build('date')->useImmutable(); +TypeFactory::build('time')->useImmutable(); +TypeFactory::build('timestamp')->useImmutable(); +``` + +> [!NOTE] +> 新しいアプリケーションは、デフォルトでイミュータブルオブジェクトが有効になります。 + +## Connection クラス + +`class` Cake\\Database\\**Connection** + +Connection クラスは、一貫性のある方法でデータベースコネクションと対話するための +シンプルなインターフェイスを提供します。 +これはドライバー層への基底インターフェイスであり、クエリーの実行、クエリーのロギング、 +トランザクション処理といった機能を提供するためのものです。 + +### クエリーの実行 + +`method` Cake\\Database\\Connection::**query**($sql) + +あなたがコネクションオブジェクトを取得したら、恐らく何らかのクエリーを発行したくなるでしょう。 +CakePHP のデータベース抽象化レイヤは、PDO とネイティブドライバー上にラッパー機能を提供します。 +これらのラッパーは PDO と似たようなインターフェイスを提供します。 +クエリーを実行する方法は、あなたが実行したいクエリーと取得したい結果の種類に応じて +いくつかあります。 +もっとも基本的な方法は、完全な SQL クエリーの実行を可能にする `query()` です。 : + +``` php +$statement = $connection->query('UPDATE articles SET published = 1 WHERE id = 2'); +``` + +`method` Cake\\Database\\Connection::**execute**($sql, $params, $types) + +`query()` メソッドは追加パラメーターを受け付けません。もし追加パラメーターが必要なら、 +プレースホルダーを使用可能な `execute()` メソッドを使用します。 : + +``` php +$statement = $connection->execute( + 'UPDATE articles SET published = ? WHERE id = ?', + [1, 2] +); +``` + +型に関する情報がない場合は、 `execute` は全てのプレースホルダーを文字列とみなします。 +もし特定の型にバインドする必要があるなら、クエリーを生成する時に型名を指定することが +できます。 : + +``` php +$statement = $connection->execute( + 'UPDATE articles SET published_date = ? WHERE id = ?', + [new DateTime('now'), 2], + ['date', 'integer'] +); +``` + +`method` Cake\\Database\\Connection::**newQuery**() + +これはあなたのアプリケーションで豊富なデータ型を使用し、適切に SQL 文に変換することができます。 +クエリーを作成する最後の、そして最も柔軟な方法は、 [クエリービルダー](../orm/query-builder) を +使用することです。 +この方法では、プラットフォーム固有の SQL を使用することなく、複雑で表現力豊かなクエリーを +構築することができます。 : + +``` php +$query = $connection->newQuery(); +$query->update('articles') + ->set(['published' => true]) + ->where(['id' => 2]); +$statement = $query->execute(); +``` + +クエリービルダーを使用する場合は、 `execute()` メソッドを呼ぶまではサーバーに SQL は +送信されず、メソッド呼び出し後に順次処理されます。 +最初に送信してから、順次結果セットを作成します。 : + +``` php +$query = $connection->newQuery(); +$query->select('*') + ->from('articles') + ->where(['published' => true]); + +foreach ($query as $row) { + // Do something with the row. +} +``` + +> [!NOTE] +> もし `Cake\ORM\Query` のインスタンスを生成しているのなら、 +> SELECT クエリーの結果セットを取得するのに `all()` を使用できます。 + +### トランザクションを使う + +コネクションオブジェクトは、データベーストランザクションを行うためのいくつかの簡単な +方法を提供します。 +トランザクション操作の最も基本的な方法は、SQL構文と同じような `begin()` , +`commit()` , `rollback()` を使用するものです。 : + +``` php +$connection->begin(); +$connection->execute('UPDATE articles SET published = ? WHERE id = ?', [true, 2]); +$connection->execute('UPDATE articles SET published = ? WHERE id = ?', [false, 4]); +$connection->commit(); +``` + +`method` Cake\\Database\\Connection::**transactional**(callable $callback) + +このコネクションインスタンスへのインターフェースに加えて、さらに begin/commit/rollback を +簡単にハンドリングする `transactional()` メソッドが提供されています。 : + +``` php +$connection->transactional(function ($connection) { + $connection->execute('UPDATE articles SET published = ? WHERE id = ?', [true, 2]); + $connection->execute('UPDATE articles SET published = ? WHERE id = ?', [false, 4]); +}); +``` + +基本的なクエリーに加えて、 [クエリービルダー](../orm/query-builder) または [テーブルオブジェクト](../orm/table-objects) の +いずれかを使用してより複雑なクエリーを実行することができます。 +トランザクションメソッドは下記のことを実行します。 + +- `begin` を呼び出します。 +- 引数で渡されたクロージャーを実行します。 +- もしクロージャー内で例外が発生したら、ロールバックを発行して例外を再度 throw します。 +- クロージャーが `false` を返したら、ロールバックを発行して false を返します。 +- クロージャーが正常終了したら、トランザクションをコミットします。 + +## ステートメントとの対話 + +基底レベルのデータベース API を使用していると、ステートメントオブジェクトが +よく出てきます。 +これらのオブジェクトで、ドライバーから基になるプリペアードステートメントを操作できるように +なります。 +クエリーオブジェクトを生成し実行するか `execute()` を実行した後、あなたは +`StatementDecorator` インスタンスを持つ事になります。 +これはベースとなる基本的なステートメントオブジェクトをラップして、追加の機能を +提供します。 + +### ステートメントを準備する + +あなたは `execute()` か `prepare()` でステートメントオブジェクトを生成できます。 +`execute()` メソッドは引き継いだ値をバインドしたステートメントを返します。 +それに対して `prepare()` は不完全なステートメントを返します。 : + +``` php +// execute は指定された値でバインドして SQL ステートメントを実行します。 +$statement = $connection->execute( + 'SELECT * FROM articles WHERE published = ?', + [true] +); + +// prepare はプレースホルダーのための準備をします。 +// 実行する前にパラメーターをバインドする必要があります。 +$statement = $connection->prepare('SELECT * FROM articles WHERE published = ?'); +``` + +SQL 文を準備したら、あなたは追加のデータをバインドし、それを実行することができます。 + +### 値をバインドする + +プリペアードステートメントを作成したら、追加のデータをバインドする必要があります。 +あなたは `bind()` メソッドを使って一度に複数の値をバインドする事も、 +`bindValue` を使って1項目ずつバインドする事もできます。 : + +``` php +$statement = $connection->prepare( + 'SELECT * FROM articles WHERE published = ? AND created > ?' +); + +// 複数項目のバインド +$statement->bind( + [true, new DateTime('2013-01-01')], + ['boolean', 'date'] +); + +// 1項目ずつのバインド +$statement->bindValue(1, true, 'boolean'); +$statement->bindValue(2, new DateTime('2013-01-01'), 'date'); +``` + +ステートメントを作成する時には、項目の通し番号ではなく、項目名の配列をキーに +使用することもできます。 : + +``` php +$statement = $connection->prepare( + 'SELECT * FROM articles WHERE published = :published AND created > :created' +); + +// 複数項目のバインド +$statement->bind( + ['published' => true, 'created' => new DateTime('2013-01-01')], + ['published' => 'boolean', 'created' => 'date'] +); + +// 1項目ずつのバインド +$statement->bindValue('published', true, 'boolean'); +$statement->bindValue('created', new DateTime('2013-01-01'), 'date'); +``` + +> [!WARNING] +> 同じステートメント内で、項目の通し番号と項目名のキーを混在させることはできません。 + +### 実行と結果行の取得 + +プリペアードステートメントを作成してデータをバインドしたら、実行して行フェッチすることが +できます。 +ステートメントは `execute()` メソッドで実行します。 +一度実行したら、結果は `fetch()` か `fetchAll()` を使ってフェッチします。 : + +``` php +$statement->execute(); + +// 1行読み込む +$row = $statement->fetch('assoc'); + +// 全行を読み込む +$rows = $statement->fetchAll('assoc'); + +// 全行読み込んだ結果を順次処理する +foreach ($statement as $row) { + // Do work +} +``` + +> [!NOTE] +> 読み込んだフェッチする時には、2つのモードを使用することができます。 +> 結果配列のキーを項目の通番にする場合 (num) と、項目名をキーにする場合 (assoc) です。 + +### 行数を取得する + +ステートメントを実行したら、下記のように対象行数を取得することができます。 : + +``` php +$rowCount = count($statement); +$rowCount = $statement->rowCount(); +``` + +### エラーコードをチェックする + +あなたのクエリーが成功しなかった場合は、エラー関連情報を `errorCode()` と `errorInfo()` +メソッドによって取得することができます。 +このメソッドは PDO で提供されているものと同じように動作します。 : + +``` php +$code = $statement->errorCode(); +$info = $statement->errorInfo(); +``` + +
    + +Possibly document CallbackStatement and BufferedStatement + +
    + +## クエリーロギング + +あなたのコネクションを設定する時に、 `log` オプションに `true` をセットすると +クエリーのログを有効にすることができます。 +また、 `logQueries` を使って実行中にクエリーログを切り替えることができます。 : + +``` php +// クエリーログを有効 +$connection->enableQueryLogging(true); + +// クエリーログを停止 +$connection->enableQueryLogging(false); +``` + +クエリーログを有効にしていると、 'debug' レベルで 'queriesLog' スコープで +`Cake\Log\Log` にクエリーをログ出力します。 +あなたはこのレベル・スコープを出力するようにログ設定をする必要があります。 +`stderr` にログ出力するのはユニットテストの時に便利で、files/syslog に出力するのは +Web リクエストの時に便利です。 : + +``` php +use Cake\Log\Log; + +// Console logging +Log::setConfig('queries', [ + 'className' => 'Console', + 'stream' => 'php://stderr', + 'scopes' => ['queriesLog'] +]); + +// File logging +Log::setConfig('queries', [ + 'className' => 'File', + 'path' => LOGS, + 'file' => 'queries.log', + 'scopes' => ['queriesLog'] +]); +``` + +> [!NOTE] +> クエリーログは、デバッグまたは開発用途での利用を想定しています。 +> アプリケーションのパフォーマンスに悪影響を及ぼしますので、公開サイトでは +> 利用すべきではありません。 + +## 引用識別子 + +デフォルトの CakePHP では、生成される SQL 文は引用符で囲まれて **いません** 。 +その理由は、引用識別子はいくつかの問題があるためです。 + +- パフォーマンスへの負荷 - 引用符を使うと、使わない時よりずっと遅く、複雑になります。 +- ほとんどの場合に不要 - CakePHP の規約に従う新しいデータベースでは、引用符で囲む必要はありません。 + +もしあなたが引用符が必要な古いスキーマを使用しているなら、 +[データベースの設定](#database-configuration) で `quoteIdentifiers` を設定すると +引用符を使うことができます。 +また、実行時にこの機能を有効にすることもできます。 : + +``` php +$connection->getDriver()->enableAutoQuoting(); +``` + +有効にすると、引用識別子は 全ての識別子を `IdentifierExpression` オブジェクトに +変換するトラバーサルが発生する原因になります。 + +> [!NOTE] +> QueryExpression オブジェクトに含まれる SQL スニペットは変換されません。 + +## メタデータ・キャッシング + +CakePHP の ORM は、あなたのアプリケーションのスキーマ、インデックス、外部キーを +決定するために、データベースリフレクションを使用します。 +このメタデータは頻繁に変更され、アクセスにコストがかかるため、一般的にキャッシュされます。 +デフォルトでは、メタデータは `_cake_model_` キャッシュ設定に保存されます。 +あなたはデータベース設定の `cacheMetatdata` オプションを使って +カスタムキャッシュ設定を定義することができます。 : + +``` text +'Datasources' => [ + 'default' => [ + // Other keys go here. + + // Use the 'orm_metadata' cache config for metadata. + 'cacheMetadata' => 'orm_metadata', + ] +], +``` + +実行時に `cacheMetadata()` メソッドを使ってメタデータのキャッシュを +設定することもできます。 : + +``` php +// キャッシュを無効化 +$connection->cacheMetadata(false); + +// キャッシュを有効化 +$connection->cacheMetadata(true); + +// カスタムキャッシュ設定を利用 +$connection->cacheMetadata('orm_metadata'); +``` + +CakePHP にはメタデータキャッシュを管理するための CLI ツールも同梱しています。 +詳細については [スキーマキャッシュツール](../console-commands/schema-cache) を参照してください。 + +## データベースの作成 + +もし、データベースを選択せずに接続したい場合、データベース名を省略してください。 : + +``` php +$dsn = 'mysql://root:password@localhost/'; +``` + +これでデータベースの作成や変更のクエリーを実行するためにコネクションオブジェクトが使えます。 : + +``` php +$connection->query("CREATE DATABASE IF NOT EXISTS my_database"); +``` + +> [!NOTE] +> データベースを作成する場合、文字コードや照合順序をセットすることをお勧めします。 +> もしこれらの値がなかった場合、データベースはシステムのデフォルト値をセットします。 diff --git a/docs/ja/orm/deleting-data.md b/docs/ja/orm/deleting-data.md new file mode 100644 index 0000000000..d76bdab087 --- /dev/null +++ b/docs/ja/orm/deleting-data.md @@ -0,0 +1,102 @@ +# データの削除 + +`class` Cake\\ORM\\**Table** + +`method` Cake\\ORM\\Table::**delete**(EntityInterface $entity, array $options = []) + +読み込んだエンティティーは、テーブル本来の削除メソッドを呼びだすことによって削除することが出来ます。 : + +``` php +// コントローラーの中 +$entity = $this->Articles->get(2); +$result = $this->Articles->delete($entity); +``` + +エンティティー削除時には、いくつかのことが起こります。 + +1. [削除ルール](../orm/validation#application-rules) が適用されます。 ルールのチェックに失敗した場合、 + 削除は中止されます。 +2. `Model.beforeDelete` イベントが起動されます。このイベントが停止した場合、削除は失敗し、 + イベントの戻り値が返されます。 +3. エンティティーが削除されます。 +4. 全ての依存関係先が削除されます。依存関係先がエンティティーとして削除されるとき、 + 追加のイベントが起動されます。 +5. BelongsToMany アソシエーション用の全ての結合テーブルのレコードが削除されます。 +6. `Model.afterDelete` イベントが起動されます。 + +デフォルトでは、一回のトランザクションの中で全ての削除が行われますが、 +atomic オプションで無効化することも出来ます。 : + +``` php +$result = $this->Articles->delete($entity, ['atomic' => false]); +``` + +## 連鎖削除 + +エンティティーを削除するとき関連データを削除することもできます。HasOne や HasMany が +`dependent` として設定されている場合、削除処理はそれらのエンティティーにも連鎖適用されます。 +デフォルトでは、関連テーブル内のエンティティーの削除には `Cake\ORM\Table::deleteAll()` +が使用されます。 `cascadeCallbacks` オプションを `true` に設定することにより、 +関連するエンティティーを ORM に読み出させ、それらを個別に削除させるように選択できます。 +上記2つのオプションを有効にした HasMany のサンプルは、このようになります。 : + +``` php +// テーブル内の初期化メソッド +$this->hasMany('Comments', [ + 'dependent' => true, + 'cascadeCallbacks' => true, +]); +``` + +> [!NOTE] +> `cascadeCallbacks` が `true` の時、一括削除に比較して削除処理はだいぶ遅くなります。 +> cascadeCallbacks オプションは、あなたのアプリケーションがイベントリスナーによって処理される +> 重要な仕事を持っている場合のみ有効にされるべきです。 + +## 一括削除 + +`method` Cake\\ORM\\Table::**deleteAll**($conditions) + +一行ずつ削除することが効率的でなかったり有用ではない時があります。そういったケースでは、 +一回で複数行を削除するために、一括削除を使うことが効率的です。 : + +``` php +// 全てのスパムを削除する +public function destroySpam() +{ + return $this->deleteAll(['is_spam' => true]); +} +``` + +一括削除では、1つ以上の行が削除されると成功したとみなされます。 + +> [!WARNING] +> deleteAll は、beforeDelete/afterDelete イベントを *呼び出しません* 。 +> それらのイベントを呼び出したい場合、それぞれのレコードを読み込んで削除する必要があります。 + +## 厳密な削除 + +`method` Cake\\ORM\\Table::**deleteOrFail**(EntityInterface $entity, array $options = []) + +このメソッドを使用すると、次の条件で +`Cake\ORM\Exception\PersistenceFailedException` を投げます。 + +- エンティティーが新しい場合 +- エンティティーが主キーの値を持たない場合 +- アプリケーションルールのチェックが失敗した場合 +- 削除コールバックによって中断された場合 + +保存に失敗したエンティティーを追跡する場合、 +`Cake\ORM\Exception\PersistenceFailedException::getEntity()` メソッドを +使用できます。 : + +``` php +try { + $table->deleteOrFail($entity); +} catch (\Cake\ORM\Exception\PersistenceFailedException $e) { + echo $e->getEntity(); +} +``` + +これは内部的に `Cake\ORM\Table::delete()` +コールを実行するので、対応するすべての削除イベントがトリガーされます。 diff --git a/docs/ja/orm/entities.md b/docs/ja/orm/entities.md new file mode 100644 index 0000000000..c4e3545a19 --- /dev/null +++ b/docs/ja/orm/entities.md @@ -0,0 +1,578 @@ +# エンティティー + +`class` Cake\\ORM\\**Entity** + +[テーブルオブジェクト](../orm/table-objects) がオブジェクトのコレクションへのアクセスを表し、提供するのに対し、 +エンティティーは個々の行やドメインオブジェクトを表します。エンティティーは保持するデータにアクセスして +操作するための永続的なプロパティーとメソッドを保有しています。 + +CakePHP でテーブルオブジェクトの `find()` を使うたびにエンティティーが作られます。 + +## エンティティークラスの生成 + +CakePHP の ORM を使うためにエンティティークラスを生成する必要はありません。 +しかし、使用するエンティティーでロジックをカスタマイズしたいなら、クラスを作る必要があります。 +慣例通りなら **src/Model/Entity/** にクラスが存在しています。 +もし、 `articles` テーブルが存在するなら、以下のエンティティーが作れます。 : + +``` php +// src/Model/Entity/Article.php +namespace App\Model\Entity; + +use Cake\ORM\Entity; + +class Article extends Entity +{ +} +``` + +今のところ、このエンティティーは何も出来ません。とはいえ、データを article テーブルからロードすれば、 +このクラスのインスタンスを得られます。 + +> [!NOTE] +> もし、エンティティークラスが定義されていないなら、CakePHP はデフォルトのエンティティークラスを使います。 + +## エンティティー生成 + +直接、エンティティーのインスタンスを生成できます。 : + +``` php +use App\Model\Entity\Article; + +$article = new Article(); +``` + +エンティティーからインスタンスを生成する時、インスタンスに必要なプロパティーを渡すことができます。 : + +``` php +use App\Model\Entity\Article; + +$article = new Article([ + 'id' => 1, + 'title' => 'New Article', + 'created' => new DateTime('now') +]); +``` + +エンティティーを生成するお勧めの方法は、 `Table` オブジェクトの `newEntity()` メソッドです。 : + +``` php +use Cake\ORM\TableRegistry; + +$article = TableRegistry::getTableLocator()->get('Articles')->newEmptyEntity(); + +$article = TableRegistry::getTableLocator()->get('Articles')->newEntity([ + 'id' => 1, + 'title' => 'New Article', + 'created' => new DateTime('now') +]); +``` + +`$article` は、 `App\Model\Entity\Article` のインスタンスになります。もしくは +`Article` クラスが作成しなかった場合は、代替として `Cake\ORM\Entity` のインスタンスになります。 + +## エンティティーのデータへのアクセス + +エンティティーは保有するデータにアクセスするいくつかの方法を提供します。 +最も一般的なのは、オブジェクトの表記を使ってエンティティー内のデータにアクセスすることです。 : + +``` php +use App\Model\Entity\Article; + +$article = new Article; +$article->title = 'This is my first post'; +echo $article->title; +``` + +また、 `get()` と `set()` メソッドも使えます。 : + +``` php +$article->set('title', 'This is my first post'); +echo $article->get('title'); +``` + +`set()` を使う時、一つの配列で複数のプロパティーを一度に更新できます。 : + +``` php +$article->set([ + 'title' => 'My first post', + 'body' => 'It is the best ever!' +]); +``` + +> [!WARNING] +> エンティティーをリクエストデータでアップデートするときには、一度の代入でどのフィールドに +> セットできるかホワイトリストで制限するべきです。 + +`has()` を使ってエンティティーにプロパティが定義されているかどうかを確認できます。 : + +``` php +$article = new Article([ + 'title' => 'First post', + 'user_id' => null +]); +$article->has('title'); // true +$article->has('user_id'); // false +$article->has('undefined'); // false. +``` + +`has()` メソッドは、プロパティが定義されていてヌル以外の値を持つ場合、 `true` を返します。 +`hasValue()` を使って、プロパティに '空でない' 値が含まれているかどうかを +調べることができます。 : + +``` php +$article = new Article([ + 'title' => 'First post', + 'user_id' => null +]); +$article->hasValue('title'); // true +$article->hasValue('user_id'); // false +``` + +## アクセサーとミューテーター + +シンプルな get/set インターフェイスに加えて、エンティティーは +アクセサーメソッドとミューテーターメソッドを提供できるようになっています。 +これらのメソッドは、プロパティーがどうやってセットされたり、読まれたりするかを +カスタマイズするために使えます。 + +アクセサーは `_get` + フィールド名のキャメルケースという命名ルールを使います。 + +`method` Cake\\ORM\\Entity::**get**($field) + +このメソッドは唯一の引数として `_fields` 配列内にある基本の値を受け取ります。 +アクセサーはエンティティーを保存する際に使われますので、データをフォーマットするメソッド +を定義する場合は注意が必要です。データはフォーマットされた状態で保存されることになります。 +例えば、 : + +``` php +namespace App\Model\Entity; + +use Cake\ORM\Entity; + +class Article extends Entity +{ + protected function _getTitle($title) + { + return ucwords($title); + } +} +``` + +アクセサーは以下の2つの方法でプロパティーを取得する際に実行されます。 : + +``` php +echo $article->title; +echo $article->get('title'); +``` + +> [!NOTE] +> フィールドを参照するたびに、アクセサーのコードが実行されます。 +> アクセサーでリソース集約的な操作を実行している場合は、 \$myEntityProp = \$entity-\>my_property +> のように、ローカル変数を使用してキャッシュすることができます。 + +ミューテーターを定義することによって、プロパティーの設定方法をカスタマイズできます。 + +`method` Cake\\ORM\\Entity::**set**($field = null, $value = null) + +ミューテーターは常にプロパティーに保存すべき値を返すようにしてください。 +上の例のように、ミューテーターを使って他の計算されたプロパティーを設定することもできます。 +これをする際に、呼び出しがループしてしまわないように注意して下さい。CakePHP はミューテーターの +無限ループを防ぐことが出来ません。 + +ミューテーターによりセットされるプロパティーを変換したり、 +計算されたデータを作成したりすることができるようになります。ミューテーターとアクセサーは +オブジェクト表記や、 `get()` や `set()` を使ってプロパティーが読まれた場合に適用されます。 +例えば、 : + +``` php +namespace App\Model\Entity; + +use Cake\ORM\Entity; +use Cake\Utility\Text; + +class Article extends Entity +{ + protected function _setTitle($title) + { + return Text::slug($title); + } + +} +``` + +ミューテーターは、以下の2つの方法でプロパティーを設定するときに実行されます。 : + +``` php +$user->title = 'foo'; // 同時に slug が設定されます。 +$user->set('title', 'foo'); // 同時に slug が設定されます。 +``` + +> [!WARNING] +> アクセサーは、エンティティーがデータベースに永続化される前にも実行されます。 +> フィールドを変換したいけれど、変換したものを永続化したくない場合、 +> 永続化されない仮想プロパティーの使用をお勧めします。 + +### 仮想プロパティーの生成 + +アクセサーを定義することによって、現在存在しないフィールド・プロパティーへのアクセスを提供できます。 +例えば、users テーブルが `first_name` と `last_name` 列を持っていたとして、 +フルネームのためのメソッドを作れるということです。 : + +``` php +namespace App\Model\Entity; + +use Cake\ORM\Entity; + +class User extends Entity +{ + protected function _getFullName() + { + return $this->first_name . ' ' . $this->last_name; + } +} +``` + +仮想プロパティーは、エンティティーに存在するかのようにアクセスできます。 +プロパティー名はメソッド( `full_name` )の小文字とアンダースコア付きのバージョンになります。 : + +``` text +echo $user->full_name; +``` + +仮想プロパティーは find で使えないということを覚えておいてください。 +もし、仮想プロパティーを、エンティティーを表す JSON や配列の一部にしたい場合、 +[Exposing Virtual Properties](#exposing-virtual-properties) をご覧ください。 + +## エンティティーが変更されたかチェックする + +`method` Cake\\ORM\\Entity::**dirty**($field = null, $dirty = null) + +エンティティーのプロパティーが変更されたかどうかに応じるコードを +作りたいと思うことがあるかもしれません。例えば、フィールドが変更された時にだけ +バリデートしたい場合です。 : + +``` php +// タイトルが変更された時に +$article->isDirty('title'); +``` + +フィールドに変更されたという印をつける事もできます。これは配列のプロパティーに追加した場合に便利です。 : + +``` php +// コメントを追加して、フィールドが変更されたと印をつけます。 +$article->comments[] = $newComment; +$article->setDirty('comments', true); +``` + +加えて、 `getOriginal()` メソッドを使うことで元のプロパティー値に応じたコードを書くこともできます。 +このメソッドは値が変更されているなら元の値を返し、そうでなければ実際の値を返します。 + +また、エンティティー内のプロパティーのいずれかが変化したかをチェックすることもできます。 : + +``` php +// エンティティーが変更されたか確かめる +$article->isDirty(); +``` + +`clean()` メソッドで不必要な印をエンティティーのフィールドから除去できます。 : + +``` php +$article->clean(); +``` + +オプションを追加で渡すことで、フィールドに印が付くのを避けることができます。 : + +``` php +$article = new Article(['title' => 'New Article'], ['markClean' => true]); +``` + +`Entity` の全ての変更されたプロパティーの一覧を取得するには、次のように呼ぶことができます。 : + +``` php +$dirtyFields = $entity->getDirty(); +``` + +## バリデーションエラー + +[エンティティーの保存](../orm/saving-data#saving-entities) がされた後、どんなバリデーションエラーも +エンティティー自身に保存されます。バリデーションエラーには `getErrors()` や +`getError()` 、 `hasErrors()` メソッドを使ってアクセスできます。 : + +``` php +// エラーの取得 +$errors = $user->getErrors(); + +// 1つのフィールドのエラーを取得 +$errors = $user->getError('password'); + +// エンティティーまたはネストされたエンティティーにエラーがあるか +$user->hasErrors(); + +// ルートエンティティーのみにエラーがあるか +$user->hasErrors(false); +``` + +`setErrors()` や `setError()` はまたエンティティーにエラーをセットするために使うこともできます。 +これにより、エラーメッセージで動くコードのテストが簡単になります。 : + +``` php +$user->setError('password', ['Password is required']); +$user->setErrors([ + 'password' => ['Password is required'], + 'username' => ['Username is required'] +]); +``` + +## 一括代入 (*Mass Assignment*) + +一括でエンティティーのプロパティーを設定するのは単純で便利ですが、 +これには重大なセキュリティ問題が伴います。 +リクエストからユーザーデータをエンティティーへと一括代入してしまうと、 +ユーザーはどの列でも変更できるようになってしまいます。 +匿名のエンティティークラスを使ったり、 [Bake コンソール](../bake) でエンティティーを生成すると、 +CakePHP は一括代入から保護しません。 + +`_accessible` プロパティーにより、プロパティーと一括代入できるかどうかのマップを提供できるようになります。 +`true` と `false` の値はそれぞれ、その列が一括代入できるか、できないかを示しています。 : + +``` php +namespace App\Model\Entity; + +use Cake\ORM\Entity; + +class Article extends Entity +{ + protected array $_accessible = [ + 'title' => true, + 'body' => true + ]; +} +``` + +具体的なフィールドに加え、名前が指定されなかった場合の受け皿となる `*` という特別なフィールドが +存在します。 : + +``` php +namespace App\Model\Entity; + +use Cake\ORM\Entity; + +class Article extends Entity +{ + protected array $_accessible = [ + 'title' => true, + 'body' => true, + '*' => false, + ]; +} +``` + +> [!NOTE] +> `*` プロパティーが定義されない場合、デフォルトは `false` になります。 + +### 一括代入に対する保護の回避 + +新しいエンティティーを `new` キーワードで作成する際、一括代入に対して保護しないように指示できます。 : + +``` php +use App\Model\Entity\Article; + +$article = new Article(['id' => 1, 'title' => 'Foo'], ['guard' => false]); +``` + +### 保護されたフィールドを実行時に変更する + +`setAccess()` メソッドを使うことで保護されたフィールドのリストを実行時に変更できます。 : + +``` php +// user_id にアクセスできるようにする +$article->setAccess('user_id', true); + +// title を保護する。 +$article->setAccess('title', false); +``` + +> [!NOTE] +> フィールドがアクセス可能かの変更は、そのメソッドを呼んだインスタンスのみに影響します。 + +`Table` オブジェクトの `newEntity()` と `patchEntity()` を使う際、 +オプションを使って一括代入からの保護をカスタマイズできます。 +[Changing Accessible Fields](../orm/saving-data#changing-accessible-fields) に詳細があります。 + +### フィールドに対する保護を受け渡す + +保護されたフィールドに対して一括代入を許可したい状況もあるでしょう。 : + +``` php +$article->set($properties, ['guard' => false]); +``` + +`guard` オプションを `false` にすることで、今回の `set()` の呼び出しに限り、 +アクセス可能なフィールドリストを無視することが出来ます。 + +### エンティティーが永続化されているかチェックする + +エンティティーが示す行がデータベース上に既に存在しているかを知らなければならないことは良くあることです。 +こういった場合は `isNew()` メソッドを使って下さい。 : + +``` php +if (!$article->isNew()) { + echo '既に保存されました!'; +} +``` + +既にエンティティーが永続化されているかどうかが解っているなら +`isNew()` をセッターとして使えます。 : + +``` php +$article->setNew(false); + +$article->setNew(true); +``` + +## アソシエーションの Lazy ローディング + +アソシエーションの eager ローディングは大抵の場合において最も有効なアクセス法ではありますが、 +アソシエーションデータを lazy ロードしたいときもあるかもしれません。 +この方法を見ていく前に、 eager ローディングと lazy ローディングの違いを見てみましょう: + +Eager ローディング +できるだけ *少ない* クエリーでDBから情報を取得できるようにJOINを(可能なときは)使います。 +HasMany アソシエーションを使うような分割したクエリーが必要なときは、1つのクエリーで、 +現在のオブジェクト一式に必要な *全て* の関連データを取ってこようとします。 + +Lazy ローディング +絶対に必要になるまでアソシエーションのロードを遅延させます。 +これにより、不要なデータがオブジェクト化されないので CPU 時間を節約できますが、 +大量のクエリーがDBに送られることになるかもしれません。 +例えば、 複数の記事 (articles) とそれに属するコメント (comments) を舐めるループでは、 +イテレートされた記事の数だけクエリーが何度も送られることになります。 + +CakePHP の ORM には lazy ローディングは含まれませんが、実現するためにコミュニティープラグインの +1つを使うことができます。私たちは [LazyLoad プラグイン](https://github.com/jeremyharris/cakephp-lazyload) をお勧めします。 + +あなたのエンティティーにプラグインを追加した後、以下のようにできます。 : + +``` php +$article = $this->Articles->findById($id); + +// comments プロパティーは lazy ロードされます。 +foreach ($article->comments as $comment) { + echo $comment->body; +} +``` + +## トレイトを使った再利用可能なコードの生成 + +いくつかのエンティティークラスで同じロジックを使わなければならないことに気づくことがあるでしょう。 +PHP のトレイトはこういった場合に威力を発揮します。 **src/Model/Entity** に自作のトレイトを +置くことができます。慣習的に CakePHP のトレイトは末尾に `Trait` が付いていますので、 +クラスやインターフェイスでないことが判るようになっています。トレイトは振る舞いを補完するもので、 +これを使うことで、テーブルオブジェクトやエンティティーオブジェクトに機能を提供できるようになっています。 + +例えば、 SoftDeletable プラグインを使っていたとして、これがトレイトを提供します。 +このトレイトは、エンティティーに 'deleted' マークを付けるためのメソッドを提供します。 +`softDelete` メソッドがトレイトにより提供されるのです。 : + +``` php +// SoftDelete/Model/Entity/SoftDeleteTrait.php + +namespace SoftDelete\Model\Entity; + +trait SoftDeleteTrait +{ + public function softDelete() + { + $this->set('deleted', true); + } + +} +``` + +そして、このトレイトをインポートし、インクルードすることで、独自のエンティティークラスで使えます。 : + +``` php +namespace App\Model\Entity; + +use Cake\ORM\Entity; +use SoftDelete\Model\Entity\SoftDeleteTrait; + +class Article extends Entity +{ + use SoftDeleteTrait; +} +``` + +## 配列や JSON への変換 + +API を作る時、しばしば、エンティティーを配列や JSON に変換する必要があるでしょう。 +CakePHP では以下のように簡単にできます。 : + +``` php +// 配列を取得します。 +// アソシエーションも toArray() で変換されます。 +$array = $user->toArray(); + +// JSON に変換します。 +// アソシエーションも jsonSerialize フックで変換されます。 +$json = json_encode($user); +``` + +エンティティーを JSON へと変換する際に、仮想 (virtual) フィールドや隠し (hidden) フィールドの +リストが適用されます。エンティティーは再帰的に JSON へと変換されます。これは、エンティティーと +アソシエーションを eager ロードする場合、CakePHP は関連データを正しいフォーマットへと +正しく変換できることを意味します。 + +### 仮想プロパティーが含まれるようにする + +配列や JSON に変換した際、仮想フィールドはデフォルトでは含まれません。 +仮想プロパティーが含まれるようにするためには、そのように指定する必要があります。 +エンティティークラスを定義する際に、含まれるべき仮想プロパティーのリストを提供できます。 : + +``` php +namespace App\Model\Entity; + +use Cake\ORM\Entity; + +class User extends Entity +{ + protected array $_virtual = ['full_name']; +} +``` + +実行時に `setVirtual()` を使うことでこのリストを変更できます。 : + +``` php +$user->setVirtual(['full_name', 'is_admin']); +``` + +### フィールドを隠す + +JSON/配列フォーマットで出力したくないフィールドがある場合があります。例えば、 +パスワードや ”秘密の質問” などです。エンティティークラスを定義する際、 +どのプロパティーを隠すか設定できます。 : + +``` php +namespace App\Model\Entity; + +use Cake\ORM\Entity; + +class User extends Entity +{ + protected $_hidden = ['password']; +} +``` + +実行時に `setHidden()` を使うことでこのリストを変更できます。 : + +``` php +$user->setHidden(['password', 'recovery_question']); +``` + +## 複合型の保存 + +DB の複合型のデータをシリアライズ/デシリアライズするためのロジックが +エンティティーのアクセサーとミューテーターに含まれることは想定されていません。 +配列型やオブジェクト型のような複合的なデータ型をどうやって保存するのかを理解するには +[Saving Complex Types](../orm/saving-data#saving-complex-types) を参照して下さい。 diff --git a/docs/ja/orm/query-builder.md b/docs/ja/orm/query-builder.md new file mode 100644 index 0000000000..ecbd9f6070 --- /dev/null +++ b/docs/ja/orm/query-builder.md @@ -0,0 +1,1588 @@ +# クエリービルダー + +`class` Cake\\ORM\\**Query** + +ORM のクエリービルダーにより簡単に流れるようなインターフェイスを使ってクエリーを作り、 +実行することができます。クエリーを組み立てることで、 union やサブクエリーを使った +高度なクエリーも簡単に作成することができます。 + +クエリービルダーは裏側で PDO プリペアードステートメント(prepared statement)を使うことで、 +SQL インジェクション攻撃から守っています。 + +## クエリーオブジェクト + +`Query` オブジェクトを作成するもっとも簡単な方法は `Table` オブジェクトから `find()` +を使うことです。このメソッドは完結していない状態のクエリーを返し、このクエリーは変更可能です。 +必要なら、テーブルのコネクションオブジェクトも使うことで、ORM 機能を含まない、 +より低い層のクエリービルダーにアクセスすることもできます。この詳細は [Database Queries](../orm/database-basics#database-queries) +のセクションを参照してください。 : + +``` php +use Cake\ORM\TableRegistry; + +$articles = TableRegistry::getTableLocator()->get('Articles'); + +// 新しいクエリーを始めます。 +$query = $articles->find(); +``` + +コントローラーの中では自動的に慣習的な機能を使って作成されるテーブル変数を使うことができます。 : + +``` php +// ArticlesController.php の中で + +$query = $this->Articles->find(); +``` + +### テーブルから行を取得する + +``` php +use Cake\ORM\Locator\LocatorAwareTrait; + +$query = $this->getTableLocator()->get('Articles')->find(); + +foreach ($query->all() as $article) { + debug($article->title); +} +``` + +以降の例では `$articles` は `Cake\ORM\Table` であると想定します。 +なお、コントローラーの中では `$articles` の代わりに `$this->Articles` を使うことができます。 + +`Query` オブジェクトのほとんどのメソッドが自分自身のクエリーオブジェクトを返します。 +これは `Query` が遅延評価される(lazy)ことを意味し、必要になるまで実行されないことを +意味します。 : + +``` php +$query->where(['id' => 1]); // 自分自身のクエリーオブジェクトを返します +$query->order(['title' => 'DESC']); // 自分自身を返し、SQL はまだ実行されません。 +``` + +もちろん Query オブジェクトの呼び出しではメソッドをチェーンすることもできます。 : + +``` php +$query = $articles + ->find() + ->select(['id', 'name']) + ->where(['id !=' => 1]) + ->order(['created' => 'DESC']); + +foreach ($query->all() as $article) { + debug($article->created); +} +``` + +Query オブジェクトを `debug()` で使うと、内部の状態とデータベースで実行されることになる +SQL が出力されます。 : + +``` php +debug($articles->find()->where(['id' => 1])); + +// 出力 +// ... +// 'sql' => 'SELECT * FROM articles where id = ?' +// ... +``` + +`foreach` を使わずに、クエリーを直接実行することができます。もっとも簡単なのは +`all()` メソッドか `toList()` メソッドのどちらかを呼ぶ方法です。 : + +``` php +$resultsIteratorObject = $articles + ->find() + ->where(['id >' => 1]) + ->all(); + +foreach ($resultsIteratorObject as $article) { + debug($article->id); +} + +$resultsArray = $articles + ->find() + ->where(['id >' => 1]) + ->all() + ->toList(); + +foreach ($resultsArray as $article) { + debug($article->id); +} + +debug($resultsArray[0]->title); +``` + +上記の例では、 `$resultsIteratorObject` は `Cake\ORM\ResultSet` のインスタンスです。 +このインスタンスはイテレートすることができ、それが持つメソッドで部分取り出しなどができます。 + +多くの場合、 `all()` を呼ぶ必要はなく、単に Query オブジェクトをイテレートすることで、 +結果を得ることができます。Query オブジェクトはまた、結果オブジェクトとして直接使うこともできます。 +クエリーをイテレートしたり、 `toList()` を呼んだり、 +[Collection](../core-libraries/collections) から継承したメソッドを呼んだりすると、 +クエリーは実行され、結果が返ります。 + +### テーブルから単一行を取得する + +`first()` メソッドを使うことでクエリーの最初の結果を取得することができます。 : + +``` php +$article = $articles + ->find() + ->where(['id' => 1]) + ->first(); + +debug($article->title); +``` + +### カラムから値リストを取得する + +``` php +// Collection ライブラリーの extract() メソッドを使います +// これもクエリーを実行します +$allTitles = $articles->find()->all()->extract('title'); + +foreach ($allTitles as $title) { + echo $title; +} +``` + +クエリーの結果から key-value リストを得ることもできます。 : + +``` php +$list = $articles->find('list'); + +$list = $articles->find('list')->all(); +foreach ($list as $id => $title) { + echo "$id : $title" +} +``` + +リストを生成するために使用するフィールドのカスタマイズの方法の詳しい情報は、 +[Table Find List](../orm/retrieving-data-and-resultsets#table-find-list) セクションを参照してください。 + +### クエリーは Collection オブジェクトである + +Query オブジェクトのメソッドに慣れたら、 [Collection](../core-libraries/collections) +を見て、効果的にデータを横断するスキルを磨くことを強くお勧めします。つまり、 Collection +オブジェクトで呼ぶことができるものは、 Query オブジェクトでも呼ぶことができることを、 +知っておくことは重要です。 : + +``` php +// Collection ライブラリーの combine() メソッドを使います +// これは find('list') と等価です +$keyValueList = $articles->find()->combine('id', 'title'); + +// 上級な例 +$results = $articles->find() + ->where(['id >' => 1]) + ->order(['title' => 'DESC']) + ->all() + ->map(function ($row) { + // map() は Collection のメソッドで、クエリーを実行します + $row->trimmedTitle = trim($row->title); + + return $row; + }) + ->combine('id', 'trimmedTitle') // combine() も Collection のメソッドです + ->toArray(); // これも Collection のメソッドです + +foreach ($results as $id => $trimmedTitle) { + echo "$id : $trimmedTitle"; +} +``` + +### クエリーの遅延評価 + +Query オブジェクトは遅延評価されます。 +これはクエリーが次のいずれかが起こるまで実行されないということを意味します。 + +- クエリーが `foreach()` でイテレートされる。 +- クエリーの `execute()` メソッドが呼ばれる。これは下層の statement オブジェクトを返し、 + insert/update/delete クエリーで使うことができます。 +- クエリーの `first()` メソッドが呼ばれる。 `SELECT` (それがクエリーに `LIMIT 1` + を加えます) で構築された結果セットの最初の結果が返ります。 +- クエリーの `all()` メソッドが呼ばれる。結果セットが返り、 + `SELECT` ステートメントでのみ使うことができます。 +- クエリーの `toList()` や `toArray()` メソッドが呼ばれる。 + +このような条件が合致するまでは、 SQL をデータベースへ送らずに、クエリーを変更することができます。 +つまり、 Query が評価されないかぎり、SQL はデータベースへ送信されないのです。 +クエリーが実行された後に、クエリーを変更・再評価したら、追加で SQL が走ることになります。 + +CakePHP が生成している SQL がどんなものか見たいなら、 +[クエリーロギング](../orm/database-basics#database-query-logging) を on にしてください。 + +## データを select する + +CakePHP では `SELECT` クエリーを簡単につくれます。取得するフィールドを制限するのには、 +`select()` メソッドを使います。 : + +``` php +$query = $articles->find(); +$query->select(['id', 'title', 'body']); +foreach ($query as $row) { + debug($row->title); +} +``` + +連想配列でフィールドを渡すことでフィールドのエイリアス (別名) をセットすることができます。 : + +``` php +// SELECT id AS pk, title AS aliased_title, body ... になる +$query = $articles->find(); +$query->select(['pk' => 'id', 'aliased_title' => 'title', 'body']); +``` + +フィールドを select distinct するために、 `distinct()` メソッドを使うことができます。 : + +``` php +// SELECT DISTINCT country FROM ... になる +$query = $articles->find(); +$query->select(['country']) + ->distinct(['country']); +``` + +基本の条件をセットするには、 `where()` メソッドを使うことができます。 : + +``` php +// 条件は AND で連結されます +$query = $articles->find(); +$query->where(['title' => 'First Post', 'published' => true]); + +// where() を複数回呼んでもかまいません +$query = $articles->find(); +$query->where(['title' => 'First Post']) + ->where(['published' => true]); +``` + +無名関数を `where()` メソッドに渡すこともできます。渡された無名関数は、第一引数として +`\Cake\Database\Expression\QueryExpression` のインスタンス、第二引数として +`\Cake\ORM\Query` を受け取ります。 : + +``` php +$query = $articles->find(); +$query->where(function (QueryExpression $exp, Query $q) { + return $exp->eq('published', true); +}); +``` + +さらに複雑な `WHERE` 条件の作り方を知りたい場合は [Advanced Query Conditions](#advanced-query-conditions) +のセクションをご覧ください。ソート順を適用するために、 `order` メソッドが使用できます。 : + +``` php +$query = $articles->find() + ->order(['title' => 'ASC', 'id' => 'ASC']); +``` + +一つのクエリーで `order()` を複数回呼ぶと、複数の句が追加されます。 +しかし、ファインダーを使用する時、 `ORDER BY` を上書きする必要が生じることもあります。 +`order()` (`orderAsc()` や `orderDesc()` も同様に) の第2パラメーターに +`Query::OVERWRITE` または `true` をセットしてください。 : + +``` php +$query = $articles->find() + ->order(['title' => 'ASC']); +// あとで、ORDER BY 句を追加の代わりに上書きします。 +$query = $articles->find() + ->order(['created' => 'DESC'], Query::OVERWRITE); +``` + +複合的な式でソートする必要があるなら `order` に加えて、 `orderAsc` と `orderDesc` +メソッドが使えます。 : + +``` php +$query = $articles->find(); +$concat = $query->func()->concat([ + 'title' => 'identifier', + 'synopsis' => 'identifier' +]); +$query->orderAsc($concat); +``` + +行の数を制限したり、行のオフセットをセットするためには、 `limit()` と `page()` +メソッドを使うことができます。 : + +``` php +// 50 から 100 行目をフェッチする +$query = $articles->find() + ->limit(50) + ->page(2); +``` + +上記の例にあるように、クエリーを変更するすべてのメソッドは流暢 (fluent) +なインターフェイスを提供しますので、クエリーを構築する際にチェーンメソッドの形で呼び出すことができます。 + +### 特定のフィールドを選択 + +クエリーはデフォルトでテーブルのすべてのフィールドを選択します。 +例外となるのは `select()` 関数をあえて呼び、特定のフィールドを指定した場合だけです。 : + +``` php +// articles テーブルから id と title だけが選択される +$articles->find()->select(['id', 'title']); +``` + +`select($fields)` を呼んで、なおもテーブルのすべてのフィールドを選択したいなら、 +次の方法でテーブルインスタンスを `select()` に渡すことができます。 : + +``` php +// 計算された slug フィールドを含む、 articles テーブルのすべてのフィールドを取得 +$query = $articlesTable->find(); +$query + ->select(['slug' => $query->func()->concat(['title' => 'identifier', '-', 'id' => 'identifier'])]) + ->select($articlesTable); // articles のすべてのフィールドを選択する +``` + +テーブル上のいくつかのフィールド以外のすべてのフィールドを選択したい場合には +`selectAllExcept()` を使用できます。 : + +``` php +$query = $articlesTable->find(); + +// published フィールドを除く全てのフィールドを取得 +$query->selectAllExcept($articlesTable, ['published']); +``` + +アソシエーションが含まれる場合、 `Association` オブジェクトを渡すこともできます。 + +### SQL 関数を使う + +CakePHP の ORM では抽象化された馴染み深い SQL 関数をいくつか使えるようになっています。 +抽象化により ORM は、プラットフォーム固有の実装を選んで関数を実行できるようになっています。 +たとえば、 `concat` は MySQL、PostgreSQL、SQL Server で異なる実装がされています。 +抽象化によりあなたのコードが移植しやすいものになります。 : + +``` php +// SELECT COUNT(*) count FROM ... になる +$query = $articles->find(); +$query->select(['count' => $query->func()->count('*')]); +``` + +多くのおなじみの関数が `func()` メソッドとともに作成できます: + +`rand()` +0から1の間の乱数をSQLで生成します。 + +`sum()` +合計を算出します。 引数はリテラル値として扱われます。 + +`avg()` +平均値を算出します。 引数はリテラル値として扱われます。 + +`min()` +カラムの最小値を算出します。 引数はリテラル値として扱われます。 + +`max()` +カラムの最大値を算出します。 引数はリテラル値として扱われます。 + +`count()` +件数を算出します。 引数はリテラル値として扱われます。 + +`concat()` +2つの値を結合します。 引数はバインドパラメーターとして扱われます。 + +`coalesce()` +Coalesce を算出します。 引数はバインドパラメーターとして扱われます。 + +`dateDiff()` +2つの日にち/時間の差を取得します。 引数はバインドパラメーターとして扱われます。 + +`now()` +デフォルトでは日付と時刻を返しますが、 'time' または 'date' を指定してこれらの値のみを返すこともできます。 + +`extract()` +SQL 式から特定の日付部分(年など)を返します。 + +`dateAdd()` +日付式に単位時間を加算します。 + +`dayOfWeek()` +SQL の WEEKDAY 関数を呼ぶ FunctionExpression を返します。 + +SQL 関数に渡す引数には、リテラルの引数と、バインドパラメーターの2種類がありえます。 +識別子やリテラルのパラメーターにより、カラムや他の SQL リテラルを参照できます。 +バインドパラメーターにより、ユーザーデータを SQL 関数へと安全に渡すことができます。 +たとえば : + +``` php +$query = $articles->find()->innerJoinWith('Categories'); +$concat = $query->func()->concat([ + 'Articles.title' => 'identifier', + ' - CAT: ', + 'Categories.name' => 'identifier', + ' - Age: ', + $query->func()->dateDiff( + 'NOW()' => 'literal', + 'Articles.created' => 'identifier' + ) +]); +$query->select(['link_title' => $concat]); +``` + +`literal` の値を伴う引数を作ることで、 ORM はそのキーをリテラルな SQL 値として扱うべきであると +知ることになります。 `identifier` の値を伴う引数を作ることで、ORM は、そのキーがフィールドの +識別子として扱うべきであると知ることになります。 + +上記では MySQL にて下記の SQL が生成されます。 + +``` sql +SELECT CONCAT( + Articles.title, + :c0, + Categories.name, + :c1, + (DATEDIFF(NOW(), Articles.created)) +) FROM articles; +``` + +クエリーが実行される際には、 `:c0` という値に `' - CAT'` というテキストがバインドされることになります。 +`dateDiff` 式は適切なSQLに変換されます。 + +#### カスタム関数 + +もし `func()` が必要なSQL関数をラップしていない場合は、 `func()` を使って直接呼び出すことができ、 +引数やユーザデータを安全に渡すことができます。 +カスタム関数には適切な型の引数を渡すようにしてください。 : + +``` php +$query = $articles->find(); +$year = $query->func()->year([ + 'created' => 'identifier' +]); +$time = $query->func()->date_format([ + 'created' => 'identifier', + "'%H:%i'" => 'literal' +]); +$query->select([ + 'yearCreated' => $year, + 'timeCreated' => $time +]); +``` + +これらのカスタム関数は、 MySQL にて下記の SQL を生成します。 + +``` sql +SELECT YEAR(created) as yearCreated, + DATE_FORMAT(created, '%H:%i') as timeCreated +FROM articles; +``` + +> [!NOTE] +> 信頼されていないユーザのデータを任意のSQL関数に渡すには `func()` を使ってください。 + +### 集約 - Group と Having + +`count` や `sum` のような集約関数を使う際には、 +`group by` や `having` 句を使いたいことでしょう。 : + +``` php +$query = $articles->find(); +$query->select([ + 'count' => $query->func()->count('view_count'), + 'published_date' => 'DATE(created)' +]) +->group('published_date') +->having(['count >' => 3]); +``` + +### Case 文 + +ORM ではまた、 SQL の `case` 式も使えます。 `case` 式により `if ... then ... else` +のロジックを SQL の中に実装することができます。これは条件付きで sum や count をしなければならない +状況や、条件に基いてデータを特定しなければならない状況で、データを出力するのに便利です。 + +公開済みの記事(published articles)がデータベース内にいくつあるのか知りたい場合、次の +SQL を使用することができます。 + +``` sql +SELECT +COUNT(CASE WHEN published = 'Y' THEN 1 END) AS number_published, +COUNT(CASE WHEN published = 'N' THEN 1 END) AS number_unpublished +FROM articles +``` + +これはクエリービルダーでは次のようなコードになります。 : + +``` php +$query = $articles->find(); +$publishedCase = $query->newExpr() + ->addCase( + $query->newExpr()->add(['published' => 'Y']), + 1, + 'integer' + ); +$unpublishedCase = $query->newExpr() + ->addCase( + $query->newExpr()->add(['published' => 'N']), + 1, + 'integer' + ); + +$query->select([ + 'number_published' => $query->func()->count($publishedCase), + 'number_unpublished' => $query->func()->count($unpublishedCase) +]); +``` + +`addCase` 関数は SQL 内で `if .. then .. [elseif .. then .. ] [ .. else ]` +ロジックを構築するために複数の文を一まとめに書くことができます。 + +町 (city) を人口 (population size) に基いて SMALL、MEDIUM、LARGE に分類するなら、 +次のようになります。 : + +``` php +$query = $cities->find() + ->where(function (QueryExpression $exp, Query $q) { + return $exp->addCase( + [ + $q->newExpr()->lt('population', 100000), + $q->newExpr()->between('population', 100000, 999000), + $q->newExpr()->gte('population', 999001), + ], + ['SMALL', 'MEDIUM', 'LARGE'], # 条件に合致したときの値 + ['string', 'string', 'string'] # それぞれの値の型 + ); + }); +# WHERE CASE +# WHEN population < 100000 THEN 'SMALL' +# WHEN population BETWEEN 100000 AND 999000 THEN 'MEDIUM' +# WHEN population >= 999001 THEN 'LARGE' +# END +``` + +値リストよりも case 条件リストの方が少ない場合はいつでも、 `addCase` は自動的に +`if .. then .. else` 文を作成します。 : + +``` php +$query = $cities->find() + ->where(function (QueryExpression $exp, Query $q) { + return $exp->addCase( + [ + $q->newExpr()->eq('population', 0), + ], + ['DESERTED', 'INHABITED'], # 条件に合致したときの値 + ['string', 'string'] # それぞれの値の型 + ); + }); +# WHERE CASE +# WHEN population = 0 THEN 'DESERTED' ELSE 'INHABITED' END +``` + +### エンティティーの代わりに配列を取得 + +ORM とオブジェクトの結果セットは強力である一方で、エンティティーの作成が不要なときもあります。 +たとえば、集約されたデータにアクセスする場合、Entity を構築するのは無意味です。 +データベースの結果をエンティティーに変換する処理は、ハイドレーション (hydration) と呼ばれます。 +もし、この処理を無効化したい場合、このようにします。 : + +``` php +$query = $articles->find(); +$query->enableHydration(false); // エンティティーの代わりに配列を返す +$result = $query->toList(); // クエリーを実行し、配列を返す +``` + +これらの行を実行した後、結果はこのようになります。 : + +``` php +[ + ['id' => 1, 'title' => 'First Article', 'body' => 'Article 1 body' ...], + ['id' => 2, 'title' => 'Second Article', 'body' => 'Article 2 body' ...], + ... +] +``` + +### 計算フィールドを追加する + +クエリー後に何か後処理をする必要があるかもしれません。 +計算フィールドや生成された (derived) データをいくつか追加する必要があるなら、 +`formatResults()` メソッドを使うことができます。 +これにより軽い負荷で、結果セットを map することができます。 +この処理をこれ以上に制御する必要があるなら、もしくは、結果セットを reduce する必要があるなら、 +[Map/Reduce](../orm/retrieving-data-and-resultsets#map-reduce) 機能を代わりに使ってください。 +人々のリストを問い合わせる際に、formatResults を使って年齢 (age) を算出するなら次のようになります。 : + +``` php +// フィールド、条件、関連が構築済であると仮定します。 +$query->formatResults(function (\Cake\Collection\CollectionInterface $results) { + return $results->map(function ($row) { + $row['age'] = $row['birth_date']->diff(new \DateTime)->y; + + return $row; + }); +}); +``` + +上記の例にあるように、フォーマッタ関数(フォーマットするコールバック)の第1引数に +`ResultSetDecorator` が渡されています。第2引数にはフォーマッタ関数がセットされる +Query インスタンスが渡されます。引数の `$results` は必要に応じて、 +取り出しでも変換でもすることができます。 + +フォーマッタ関数は、クエリーが値を返せるようにするために、イテレータオブジェクトを返す必要があります。 +フォーマッタ関数はすべての Map/Reduce が実行し終わった後、適用されます。 +contain された関連の中からでも同じようにフォーマッタ関数を適用することができます。 +CakePHP はフォーマッタ関数が適切なスコープになるよう保証します。 +たとえば、下記のようにした場合でも、期待どおりに動きます。 : + +``` php +// Articles テーブル内のメソッドで +$query->contain(['Authors' => function ($q) { + return $q->formatResults(function (\Cake\Collection\CollectionInterface $authors) { + return $authors->map(function ($author) { + $author['age'] = $author['birth_date']->diff(new \DateTime)->y; + + return $author; + }); + }); +}]); + +// 結果を取得する +$results = $query->all(); + +// 29 が出力される +echo $results->first()->author->age; +``` + +上記にあるように、関連付いたクエリービルダーに設置されたフォーマッタ関数は、 +関連付いたデータの中だけの操作にスコープが限定されます。 +CakePHP は計算された値が正しい Entity にセットされることを保証します。 + +## 高度な条件 + +クエリービルダーは複雑な `where` 句の構築を簡単にします。 +グループ化された条件は、 `where()` と Expression オブジェクトを組み合わせることで表現できます。 +単純なクエリーの場合、条件の配列を使用して条件を作成できます。 : + +``` php +$query = $articles->find() + ->where([ + 'author_id' => 3, + 'OR' => [['view_count' => 2], ['view_count' => 3]], + ]); +``` + +上記は次のような SQL を生成します。 + +``` sql +SELECT * FROM articles WHERE author_id = 3 AND (view_count = 2 OR view_count = 3) +``` + +深くネストされた配列を避けたい場合は、 `where()` のコールバック形式を使用して +クエリーを構築することができます。コールバック形式を使用すると、 +式ビルダーを使用して配列なしでより複雑な条件を作成できます。例: + +``` php +$query = $articles->find()->where(function (QueryExpression $exp, Query $query) { + // 同一フィールドに複数条件を追加するために add() を使用 + $author = $query->newExpr()->or(['author_id' => 3])->add(['author_id' => 2]); + $published = $query->newExpr()->and(['published' => true, 'view_count' => 10]); + + return $exp->or([ + 'promoted' => true, + $query->newExpr()->and([$author, $published]) + ]); +}); +``` + +上記は次のような SQL を出力します。 + +``` sql +SELECT * +FROM articles +WHERE ( + ( + (author_id = 2 OR author_id = 3) + AND + (published = 1 AND view_count = 10) + ) + OR promoted = 1 +) +``` + +コールバックに渡される `QueryExpression` は完全な式を構築するために、 +**combinators** と **conditions** を使用します。 + +Combinators +これらは新しい `QueryExpression` オブジェクトを作成し、 +その式に追加された条件をどのように結合するかを設定します。 + +- `and()` は、すべての条件を `AND` で結合する新しい式オブジェクトを作成します。 +- `or()` は、すべての条件を `OR` で結合する新しい式オブジェクトを作成します。 + +Conditions +これらは式に追加され、どの組み合わせが使用されたかに応じて自動的に結合されます。 + +コールバック関数に渡される QueryExpression のデフォルトは `and()` です。 : + +``` php +$query = $articles->find() + ->where(function (QueryExpression $exp) { + return $exp + ->eq('author_id', 2) + ->eq('published', true) + ->notEq('spam', true) + ->gt('view_count', 10); + }); +``` + +`where()` を使い始めた場合、 `and()` は暗黙的に選ばれているため、それを呼ぶ必要はありません。 +上記の例では新たな条件がいくつか `AND` で結合されています。結果の SQL は次のようになります。 + +``` sql +SELECT * +FROM articles +WHERE ( +author_id = 2 +AND published = 1 +AND spam != 1 +AND view_count > 10) +``` + +ただし、 `AND` と `OR` の両方を使いたいなら、次のようにすることもできます。 : + +``` php +$query = $articles->find() + ->where(function (QueryExpression $exp) { + $orConditions = $exp->or(['author_id' => 2]) + ->eq('author_id', 5); + + return $exp + ->add($orConditions) + ->eq('published', true) + ->gte('view_count', 10); + }); +``` + +これは下記のような SQL を生成します。 + +``` sql +SELECT * +FROM articles +WHERE ( + (author_id = 2 OR author_id = 5) + AND published = 1 + AND view_count >= 10 +) +``` + +**combinators** では、メソッドのチェーンを分離したい場合に、 +新しい式オブジェクトをパラメータとして受け取るコールバックを渡すこともできます。 : + +``` php +$query = $articles->find() + ->where(function (QueryExpression $exp) { + $orConditions = $exp->or(function (QueryExpression $or) { + return $or->eq('author_id', 2) + ->eq('author_id', 5); + }); + + return $exp + ->not($orConditions) + ->lte('view_count', 10); + }); +``` + +`not()` を使って式を否定することができます。 : + +``` php +$query = $articles->find() + ->where(function (QueryExpression $exp) { + $orConditions = $exp->or(['author_id' => 2]) + ->eq('author_id', 5); + + return $exp + ->not($orConditions) + ->lte('view_count', 10); + }); +``` + +これは下記のような SQL を生成します。 + +``` sql +SELECT * +FROM articles +WHERE ( + NOT (author_id = 2 OR author_id = 5) + AND view_count <= 10 +) +``` + +SQL 関数を使った式を構築することも可能です。 : + +``` php +$query = $articles->find() + ->where(function (QueryExpression $exp, Query $q) { + $year = $q->func()->year([ + 'created' => 'identifier' + ]); + + return $exp + ->gte($year, 2014) + ->eq('published', true); + }); +``` + +これは下記のような SQL を生成します。 + +``` sql +SELECT * +FROM articles +WHERE ( + YEAR(created) >= 2014 + AND published = 1 +) +``` + +Expression オブジェクトを使う際、下記のメソッド使って条件を作成できます: + +- `eq()` 等号の条件を作成します。 : + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, Query $q) { + return $exp->eq('population', '10000'); + }); + # WHERE population = 10000 + ``` + +- `notEq()` 不等号の条件を作成します。 : + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, Query $q) { + return $exp->notEq('population', '10000'); + }); + # WHERE population != 10000 + ``` + +- `like()` `LIKE` 演算子を使った条件を作成します。 : + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, Query $q) { + return $exp->like('name', '%A%'); + }); + # WHERE name LIKE "%A%" + ``` + +- `notLike()` `LIKE` 条件の否定を作成します。 : + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, Query $q) { + return $exp->notLike('name', '%A%'); + }); + # WHERE name NOT LIKE "%A%" + ``` + +- `in()` `IN` を使った条件を作成します。 : + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, Query $q) { + return $exp->in('country_id', ['AFG', 'USA', 'EST']); + }); + # WHERE country_id IN ('AFG', 'USA', 'EST') + ``` + +- `notIn()` `IN` を使った条件の否定を作成します。 : + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, Query $q) { + return $exp->notIn('country_id', ['AFG', 'USA', 'EST']); + }); + # WHERE country_id NOT IN ('AFG', 'USA', 'EST') + ``` + +- `gt()` `>` の条件を作成します。 : + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, Query $q) { + return $exp->gt('population', '10000'); + }); + # WHERE population > 10000 + ``` + +- `gte()` `>=` の条件を作成します。 : + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, Query $q) { + return $exp->gte('population', '10000'); + }); + # WHERE population >= 10000 + ``` + +- `lt()` `<` の条件を作成します。 : + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, Query $q) { + return $exp->lt('population', '10000'); + }); + # WHERE population < 10000 + ``` + +- `lte()` `<=` の条件を作成します。 : + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, Query $q) { + return $exp->lte('population', '10000'); + }); + # WHERE population <= 10000 + ``` + +- `isNull()` `IS NULL` の条件を作成します。 : + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, Query $q) { + return $exp->isNull('population'); + }); + # WHERE (population) IS NULL + ``` + +- `isNotNull()` `IS NULL` の条件の否定を作成します。 : + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, Query $q) { + return $exp->isNotNull('population'); + }); + # WHERE (population) IS NOT NULL + ``` + +- `between()` `BETWEEN` の条件を作成します。 : + + ``` php + $query = $cities->find() + ->where(function (QueryExpression $exp, Query $q) { + return $exp->between('population', 999, 5000000); + }); + # WHERE population BETWEEN 999 AND 5000000, + ``` + +- `exists()` `EXISTS` を使用した条件を作成します。 : + + ``` php + $subquery = $cities->find() + ->select(['id']) + ->where(function (QueryExpression $exp, Query $q) { + return $exp->equalFields('countries.id', 'cities.country_id'); + }) + ->andWhere(['population >' => 5000000]); + + $query = $countries->find() + ->where(function (QueryExpression $exp, Query $q) use ($subquery) { + return $exp->exists($subquery); + }); + # WHERE EXISTS (SELECT id FROM cities WHERE countries.id = cities.country_id AND population > 5000000) + ``` + +- `notExists()` `EXISTS` を使用した条件の否定を作成します。 : + + ``` php + $subquery = $cities->find() + ->select(['id']) + ->where(function (QueryExpression $exp, Query $q) { + return $exp->equalFields('countries.id', 'cities.country_id'); + }) + ->andWhere(['population >' => 5000000]); + + $query = $countries->find() + ->where(function ($exp, $q) use ($subquery) { + return $exp->notExists($subquery); + }); + # WHERE NOT EXISTS (SELECT id FROM cities WHERE countries.id = cities.country_id AND population > 5000000) + ``` + +式オブジェクトは、多くの一般的に使用される関数や式をカバーしている必要があります。 +式を使って必要な条件を作ることができない場合は、 +`bind()` を使ってパラメータを条件に手動でバインドすることができます。 : + +``` php +$query = $cities->find() + ->where([ + 'start_date BETWEEN :start AND :end' + ]) + ->bind(':start', '2014-01-01', 'date') + ->bind(':end', '2014-12-31', 'date'); +``` + +あなたの望む条件を作成するビルダーメソッドが取得できなかったり利用したくない場合、 +WHERE 句の中で SQL スニペットを使えるようにもできます。 : + +``` php +// 2つのフィールドをお互いに比較 +$query->where(['Categories.parent_id != Parents.id']); +``` + +> [!WARNING] +> 式 (expression) の中で使われる列名や SQL スニペットには安全性が確実でない内容を +> **絶対に含めてはいけません** 。関数の呼び出しで、安全でないデータを安全に渡す +> 方法については [Using Sql Functions](#using-sql-functions) のセクションを参照してください。 + +### 式の中で識別子を使用する + +クエリーの中でカラムや SQL 識別子を参照する必要がある場合、 +`identifier()` メソッドが使用できます。 : + +``` php +$query = $countries->find(); +$query->select([ + 'year' => $query->func()->year([$query->identifier('created')]) + ]) + ->where(function ($exp, $query) { + return $exp->gt('population', 100000); + }); +``` + +> [!WARNING] +> SQL インジェクションを防ぐため、識別子の式には信頼できないデータを渡すべきではありません。 + +### IN 句を自動生成する + +ORM を使ってクエリーをビルドする際、大抵の場合、利用するカラムのデータ型を指定する必要はありません。 +なぜなら CakePHP はスキーマデータに基いて推測することができるためです。 +もしクエリーの中で CakePHP に自動的に等号を `IN` に変えさせたいなら、 +カラムのデータ型を明示する必要があります。 : + +``` php +$query = $articles->find() + ->where(['id' => $ids], ['id' => 'integer[]']); + +// もしくは、自動的に配列へとキャストさせるために IN を含めます。 +$query = $articles->find() + ->where(['id IN' => $ids]); +``` + +上記では自動的に `id = ?` ではなく `id IN (...)` が作成されます。 +これは、パラメーターが単数か配列か判らない場合に便利です。データ型名の末尾に付く `[]` という接尾辞は、 +扱いたいデータが配列であることをクエリービルダーに知らせます。 +もしもデータが配列でなかったなら、まず、配列へとキャストされることになります。 +その後、配列の各値は [type system](../orm/database-basics#data-types) を使ってキャストされることになります。 +これは複合型であっても同様に動きます。たとえば、DateTime オブジェクトのリストも使うことができます。 : + +``` php +$query = $articles->find() + ->where(['post_date' => $dates], ['post_date' => 'date[]']); +``` + +### IS NULL を自動生成する + +条件の値が `null` かもしれないし、他の値かもしれない場合、 `IS` 演算子を使うことで +自動的に正しい式が作成されます。 : + +``` php +$query = $categories->find() + ->where(['parent_id IS' => $parentId]); +``` + +上記は `$parentId` の型に応じて `` parent_id` = :c1 `` もしくは `parent_id IS NULL` +が自動的に作成されます。 + +### IS NOT NULL を自動生成する + +条件として非 `null` もしくは、他の値でないことを期待する場合、 `IS NOT` 演算子を使うことで +自動的に正しい式が作成されます。 : + +``` php +$query = $categories->find() + ->where(['parent_id IS NOT' => $parentId]); +``` + +上記は `$parentId` の型に応じて `` parent_id` != :c1 `` もしくは `parent_id IS NOT NULL` +が自動的に作成されます。 + +### 未加工の式 + +クエリービルダーでは目的の SQL が構築できない場合、Expression オブジェクトを使って、 +SQL の断片をクエリーに追加することができます。 : + +``` php +$query = $articles->find(); +$expr = $query->newExpr()->add('1 + 1'); +$query->select(['two' => $expr]); +``` + +`Expression` オブジェクトは `where()` 、 `limit()` 。 `group()` 、 `select()` +等のようなクエリービルダーのメソッドで使うことができます。 + +> [!WARNING] +> Expression オブジェクトを使うと SQL インジェクションに対して脆弱になります。 +> 式の中で信頼できないデータを使用しないでください。 + +## 結果を取得する + +クエリーができたら、それから行を受け取りたいでしょう。これにはいくつかの方法があります。 : + +``` php +// クエリーをイテレートする +foreach ($query as $row) { + // なにかする +} + +// 結果を取得する +$results = $query->all(); +``` + +Query オブジェクトでは [Collection](../core-libraries/collections) +のメソッドがどれでも使えます。それらで結果を前処理したり、変換したりすることができます。 : + +``` php +// コレクションのメソッドを使う +$ids = $query->map(function ($row) { + return $row->id; +}); + +$maxAge = $query->max(function ($max) { + return $max->age; +}); +``` + +`first` や `firstOrFail` を使って、単一のレコードを受け取ることができます。 +これらのメソッドはクエリーに `LIMIT 1` 句を付加した形に変更します。 : + +``` php +// 最初の行だけを取得する +$row = $query->first(); + +// 最初の行を取得する。できないなら例外とする。 +$row = $query->firstOrFail(); +``` + +### レコードの合計数を返す + +Query オブジェクトを使って、条件の結果見つかった行の合計数を取得することができます。 : + +``` php +$total = $articles->find()->where(['is_active' => true])->count(); +``` + +`count()` メソッドは `limit`、 `offset`、 `page` 句を無視します。 +それゆえ、下記でも同じ結果を返すことになります。 : + +``` php +$total = $articles->find()->where(['is_active' => true])->limit(10)->count(); +``` + +これは、別の `Query` オブジェクトを構築する必要なく、結果セットの合計数を前もって知ることが +できるので便利です。同様に、結果のフォーマット (result formatting)、Map/Reduce 処理は +`count()` を使う際には無視されます。 + +加えて言うと、group by 句を含んだクエリーの合計数を、クエリーを少しも書き直すことなく、 +取得することが可能です。たとえば、記事 (article) の id と、そのコメント (comment) 件数を +取得するクエリーを考えてみましょう。 : + +``` php +$query = $articles->find(); +$query->select(['Articles.id', $query->func()->count('Comments.id')]) + ->matching('Comments') + ->group(['Articles.id']); +$total = $query->count(); +``` + +カウント後でも、結びついたレコードをフェッチするのにクエリーを使うことができます。 : + +``` php +$list = $query->all(); +``` + +ときには、クエリーの合計件数を返すメソッドをカスタマイズしたくなることもあるでしょう。 +たとえば、値をキャッシュしたり、合計行数を見積もったり、あるいは、left join のような +高負荷な部分を取り除くようにクエリーを変更したりなどです。 +CakePHP のページネーション (pagination) システムでは `count()` メソッドを呼び出しますので、 +これは特に有用です。 : + +``` php +$query = $query->where(['is_active' => true])->counter(function ($query) { + return 100000; +}); +$query->count(); // 100000 を返す +``` + +上記の例では、PaginatorComponent が count メソッドを呼ぶ際には、 +ハードコーディングされた行数を受け取ることになります。 + +### ロードされた結果をキャッシュする + +変更されることのない Entity をフェッチする際、結果をキャッシュしたいと思うかもしれません。 +`Query` クラスでは簡単にこれを実現できます。 : + +``` php +$query->cache('recent_articles'); +``` + +これでクエリーの結果セットのキャッシュが有効になります。もし `cache()` に1つだけの引数を渡した場合は、 +'default' のキャッシュ・コンフィグレーションが使われることになります。 +第2引数でどのキャッシュ・コンフィグレーションを使うのかを制御できます。 : + +``` php +// 文字列で Config 名 +$query->cache('recent_articles', 'dbResults'); + +// CacheEngine のインスタンス +$query->cache('recent_articles', $memcache); +``` + +`cache()` メソッドは静的なキーだけでなく、 キーを生成する関数も受け取れます。 +渡す関数は引数でクエリーを受け取りますので、クエリーの内容を読んで動的にキャッシュキーを +生成することができます。 : + +``` php +// クエリーの where 句の単純なチェックサムに基づくキーを生成します +$query->cache(function ($q) { + return 'articles-' . md5(serialize($q->clause('where'))); +}); +``` + +キャッシュメソッドはキャッシュされた結果をカスタム finder に渡したり、 +イベントリスナで使ったりするのを簡単にします。 + +キャッシュ設定されたクエリーが結果を返すときには次のようなことが起こります: + +1. クエリーが結果セットを保持しているなら、それを返します。 +2. キャッシュキーを解決して、キャッシュデータを読みす。 + キャッシュデータが空でなければ、その結果を返します。 +3. キャッシュが見つからない場合、クエリが実行され、 `Model.beforeFind` + イベントがトリガーされ、新しい `ResultSet` が作成されます。 + この `ResultSet` はキャッシュに書き込まれ、返されます。 + +> [!NOTE] +> ストリーミングクエリー (streaming query) の結果をキャッシュすることはできません。 + +## 関連付くデータをロードする + +クエリービルダーは同時に複数テーブルからデータを取り出す際に、できるだけ最少のクエリーで取り出せるように +してくれます。関連付くデータをフェッチする際には、まず、[アソシエーション - モデル同士を繋ぐ](../orm/associations) のセクションに +あるようにテーブル間の関連をセットアップしてください。他のテーブルから関連するデータを +フェッチするためにクエリーを合成する技術を **イーガーロード** (eager load) といいます。 + + + +### 関連付くデータでフィルターする + + + +### Join を追加する + +関連付くデータを `contain()` でロードすることもできますが、 +追加の join をクエリービルダーに加えることもできます。 : + +``` php +$query = $articles->find() + ->join([ + 'table' => 'comments', + 'alias' => 'c', + 'type' => 'LEFT', + 'conditions' => 'c.article_id = articles.id', + ]); +``` + +複数 join の連想配列を渡すことで、複数の join を一度に追加できます。 : + +``` php +$query = $articles->find() + ->join([ + 'c' => [ + 'table' => 'comments', + 'type' => 'LEFT', + 'conditions' => 'c.article_id = articles.id', + ], + 'u' => [ + 'table' => 'users', + 'type' => 'INNER', + 'conditions' => 'u.id = articles.user_id', + ] + ]); +``` + +上記にあるように、join を加える際に、エイリアス (別名) を配列のキーで渡すことができます。 +join の条件も条件の配列と同じように表現できます。 : + +``` php +$query = $articles->find() + ->join([ + 'c' => [ + 'table' => 'comments', + 'type' => 'LEFT', + 'conditions' => [ + 'c.created >' => new DateTime('-5 days'), + 'c.moderated' => true, + 'c.article_id = articles.id' + ] + ], + ], ['c.created' => 'datetime', 'c.moderated' => 'boolean']); +``` + +手動で join を作成する際、配列による条件を使うなら、join 条件内の各列ごとにデータ型を渡す必要があります。 +join 条件のデータ型を渡すことで、ORM はデータの型を SQL へと正しく変換できるのです。 +join を作成する際には `join()` だけでなく、`rightJoin()`、 `leftJoin()`、`innerJoin()` +を使うこともできます。 : + +``` php +// エイリアスと文字列の条件で join する +$query = $articles->find(); +$query->leftJoin( + ['Authors' => 'authors'], + ['Authors.id = Articles.author_id']); + +// エイリアスと配列の条件・型で join する +$query = $articles->find(); +$query->innerJoin( + ['Authors' => 'authors'], + [ + 'Authors.promoted' => true, + 'Authors.created' => new DateTime('-5 days'), + 'Authors.id = Articles.author_id' + ], + ['Authors.promoted' => 'boolean', 'Authors.created' => 'datetime']); +``` + +注意しなければならないのは、 `Connection` を定義する際に +`quoteIdentifiers` オプションが `true` の場合には、 +テーブルの列間の join 条件は次のようにしなければならないということです。 : + +``` php +$query = $articles->find() + ->join([ + 'c' => [ + 'table' => 'comments', + 'type' => 'LEFT', + 'conditions' => [ + 'c.article_id' => new \Cake\Database\Expression\IdentifierExpression('articles.id') + ] + ], + ]); +``` + +これは Query 内のすべての識別子に引用符が付くことを保証し、いくつかのデータベースドライバ +(とくに PostgreSQL)でエラーが起こらないようにします。 + +## データを insert する + +前の例とは違って、insert するクエリーを作成するのに `find()` は使わないでください。 +代わりに、 `query()` を使って新たな `Query` オブジェクトを作成します。 : + +``` php +$query = $articles->query(); +$query->insert(['title', 'body']) + ->values([ + 'title' => 'First post', + 'body' => 'Some body text' + ]) + ->execute(); +``` + +1つのクエリーで複数の行を insert するために `values()` メソッドを、必要な回数分 +つなげることができます。 : + +``` php +$query = $articles->query(); +$query->insert(['title', 'body']) + ->values([ + 'title' => 'First post', + 'body' => 'Some body text' + ]) + ->values([ + 'title' => 'Second post', + 'body' => 'Another body text' + ]) + ->execute(); +``` + +通常は、エンティティーを使い、 `Cake\ORM\Table::save()` でデータを +insert するほうが簡単です。また、`SELECT` と `INSERT` を一緒に構築すれば、 +`INSERT INTO ... SELECT` スタイルのクエリーを作成することができます。 : + +``` php +$select = $articles->find() + ->select(['title', 'body', 'published']) + ->where(['id' => 3]); + +$query = $articles->query() + ->insert(['title', 'body', 'published']) + ->values($select) + ->execute(); +``` + +> [!NOTE] +> クエリービルダーでレコードを insert すると、 `Model.afterSave` のような +> イベントは発生しません。代わりに [データ保存のための ORM](../orm/saving-data) +> が利用できます。 + +## データを update する + +クエリーの insert と同様に、update のクエリーを作成するのに `find()` を使わないでください。 +代わりに、 `query()` を使って新たな `Query` オブジェクトを作成します。 : + +``` php +$query = $articles->query(); +$query->update() + ->set(['published' => true]) + ->where(['id' => $id]) + ->execute(); +``` + +通常は、エンティティーを使い、 `Cake\ORM\Table::patchEntity()` でデータを +update するほうが簡単です。 + +> [!NOTE] +> クエリービルダーでレコードを update すると、 `Model.afterSave` のような +> イベントは発生しません。代わりに [データ保存のための ORM](../orm/saving-data) +> が利用できます。 + +## データを delete する + +クエリーの insert と同様に、delete のクエリーを作成するのに `find()` を使わないでください。 +代わりに、 `query()` を使って新たな クエリーオブジェクトを作成します: + +``` php +$query = $articles->query(); +$query->delete() + ->where(['id' => $id]) + ->execute(); +``` + +通常は、エンティティーを使い、 `Cake\ORM\Table::delete()` でデータを +delete するほうが簡単です。 + +## SQL インジェクションを防止する + +ORM とデータベースの抽象層では、ほとんどの SQL インジェクション問題を防止してはいますが、 +不適切な用法により危険な値が入り込む余地も依然としてありえます。 + +条件配列を使用している場合、キー/左辺および単一の値の入力は、ユーザーデータを含んではいけません。 : + +``` php +$query->where([ + // キー/左辺のデータは、生成されたクエリーにそのまま挿入されるので、 + // 安全ではありません + $userData => $value, + + // 単一の値の入力にも同じことが言えますが、 + // どの形式のユーザーデータでも安全に使用することはできません + $userData, + "MATCH (comment) AGAINST ($userData)", + 'created < NOW() - ' . $userData +]); +``` + +Expression ビルダーを使う際には、カラム名にユーザーデータを含めてはいけません。 : + +``` php +$query->where(function (QueryExpression $exp) use ($userData, $values) { + // いずれの式 (expression) の中であってもカラム名は安全ではありません + return $exp->in($userData, $values); +}); +``` + +関数式を構築する際、関数名にユーザーデータを含めてはいけません。 : + +``` php +// 安全ではありません +$query->func()->{$userData}($arg1); + +// 関数式の引数としてユーザーデータの配列を使うことも安全ではありません +$query->func()->coalesce($userData); +``` + +未加工 (raw) の式は安全ではありません。 : + +``` php +$expr = $query->newExpr()->add($userData); +$query->select(['two' => $expr]); +``` + +### 値のバインディング + +バインディングを使用することにより、多くの安全でない状況から保護することが可能です。 +[プリペアードステートメントへの値のバインディング](../orm/database-basics#database-basics-binding-values) +と同様に、 `Cake\Database\Query::bind()` メソッドを使用して、 +クエリーに値をバインドすることができます。 + +次の例は、上記の安全ではない SQL インジェクションが発生しやすい例を安全に変更したものです。 : + +``` php +$query + ->where([ + 'MATCH (comment) AGAINST (:userData)', + 'created < NOW() - :moreUserData' + ]) + ->bind(':userData', $userData, 'string') + ->bind(':moreUserData', $moreUserData, 'datetime'); +``` + +> [!NOTE] +> `Cake\Database\StatementInterface::bindValue()` と異なり、 +> `Query::bind()` は、コロンを含む名前付けされたプレースホルダーを渡す必要があります! + +## より複雑なクエリー + +クエリービルダーでは `UNION` クエリーやサブクエリーのような複雑なクエリーも構築することができます。 + +### UNION + +UNION は1つ以上のクエリーを一緒に構築して作成します。 : + +``` php +$inReview = $articles->find() + ->where(['need_review' => true]); + +$unpublished = $articles->find() + ->where(['published' => false]); + +$unpublished->union($inReview); +``` + +`unionAll()` メソッドを使うことで `UNION ALL` クエリーを作成することもできます。 : + +``` php +$inReview = $articles->find() + ->where(['need_review' => true]); + +$unpublished = $articles->find() + ->where(['published' => false]); + +$unpublished->unionAll($inReview); +``` + +### サブクエリー + +サブクエリーはリレーショナル・データベースにおいて強力な機能であり、CakePHP ではそれを実に直感的に +構築することができます。クエリーを一緒に構築することで、サブクエリーを作ることができます。 : + +``` php +$matchingComment = $articles->getAssociation('Comments')->find() + ->select(['article_id']) + ->distinct() + ->where(['comment LIKE' => '%CakePHP%']); + +$query = $articles->find() + ->where(['id IN' => $matchingComment]); +``` + +サブクエリーはクエリー式のどこにでも使うことができます。 +たとえば、 `select()` や `join()` メソッドの中でもです。 + +### ステートメントのロックの追加 + +ほとんどのリレーショナル・データベース製品は、SELECT 操作を行う際のロックをサポートします。 +これは、 `epilog()` メソッドを使用することで可能です。 : + +``` php +// MySQL の場合、 +$query->epilog('FOR UPDATE'); +``` + +`epilog()` メソッドは、クエリーの最後に生の SQL を追加することができます。 +決して生のユーザーデータを `epilog()` にセットしないでください。 + +### 複雑なクエリーを実行する + +クエリービルダーはほとんどのクエリーを簡単に構築できるようにしてくれますが、 +あまりに複雑なクエリーだと、構築するにも退屈で入り組んだものになるかもしれません。 +[望む SQL を直接実行](../orm/database-basics#running-select-statements) したいかもしれません。 + +SQL を直接実行するということは、走ることになるクエリーを微調整できることになります。 +ただし、そうしてしまうと、 `contain` や他の高レベルな ORM 機能は使えません。 diff --git a/docs/ja/orm/retrieving-data-and-resultsets.md b/docs/ja/orm/retrieving-data-and-resultsets.md new file mode 100644 index 0000000000..37379acf33 --- /dev/null +++ b/docs/ja/orm/retrieving-data-and-resultsets.md @@ -0,0 +1,1319 @@ +# データの取り出しと結果セット + +`class` Cake\\ORM\\**Table** + +テーブルオブジェクトが「リポジトリー」やオブジェクトのコレクション周りの抽象化を提供してくれますので、 +クエリーを実行した際には「エンティティー」オブジェクトとして個々のレコードを取得することができます。 +このセクションではエンティティーを検索したりロードしたりする様々な方法について説明します。 +詳細は [エンティティー](../orm/entities) セクションをご覧ください。 + +## クエリーのデバッグと結果セット + +ORM はいまや、コレクションとエンティティーを返しますので、それらのオブジェクトをデバッグすることは以前の +CakePHP よりも複雑になりえます。いまでは、様々な方法で ORM が返すデータを調査する方法が存在します。 + +- `debug($query)` SQL とバインドパラメーターが表示されます。結果は表示されません。 +- `sql($query)` DebugKit がインストールされている場合に限り、最後に描画された SQL を表示します。 +- `debug($query->all())` ResultSet のプロパティー (結果ではなく) が表示されます。 +- `debug($query->toList())` 結果を個々に見る簡単な方法です。 +- `debug(iterator_to_array($query))` クエリー結果を配列形式で表示します。 +- `debug(json_encode($query, JSON_PRETTY_PRINT))` 人に読みやすい形で結果を表示します。 +- `debug($query->first())` 単一のエンティティーのプロパティーを表示します。 +- `debug((string)$query->first())` 単一のエンティティーのプロパティーを JSON として表示します。 + +## 主キーで単一のエンティティーを取得する + +`method` Cake\\ORM\\Table::**get**($id, $options = []) + +エンティティーとそれに関連するデータを編集・閲覧する際に、データベースから単一のエンティティーを +ロードするというのは、もっともよく使う方法です。この場合は `get()` を使います。 : + +``` php +// コントローラーやテーブルのメソッド内で + +// 単一の article を取得する +$article = $articles->get($id); + +// 単一の article と、それに関連する comment を取得する。 +$article = $articles->get($id, [ + 'contain' => ['Comments'] +]); +``` + +get の操作がどの結果も見つけられない場合は、 +`Cake\Datasource\Exception\RecordNotFoundException` が発生します。 +この例外を catch してもいいですし、CakePHP に 404 エラーへと変えさせてもかまいません。 + +`find()` のように `get()` もキャッシュ機能を持ってます。 +`get()` を呼ぶ際にキャッシュを読ませるために `cache` オプションを使うことができます。 : + +``` php +// コントローラーやテーブルのメソッド内で + +// いずれかのキャッシュの config もしくは CacheEngine インスタンスと生成されたキーを使う +$article = $articles->get($id, [ + 'cache' => 'custom', +]); + +// いずれかのキャッシュの config もしくは CacheEngine インスタンスと指定したキーを使う +$article = $articles->get($id, [ + 'cache' => 'custom', 'key' => 'mykey' +]); + +// キャッシュを使わないと明示する +$article = $articles->get($id, [ + 'cache' => false +]); +``` + +選択肢として、[Custom Find Methods](#custom-find-methods) を使ってエンティティーを `get()` することもできます。 +たとえば、あるエンティティーの translations すべてを取得したいことがあるかもしれません。 +`finder` オプションを使えば、それを獲得することができます。 : + +``` php +$article = $articles->get($id, [ + 'finder' => 'translations', +]); +``` + +## データのロードに Finder を使う + +`method` Cake\\ORM\\Table::**find**($type, $options = []) + +エンティティーを使うには、それらをロードする必要があります。 +これを最も簡単に行えるのが `find()` メソッドを使うことです。 +find メソッドは、あなたが求めるデータを検索するための簡単で拡張性の高い方法を提供します。 : + +``` php +// コントローラーやテーブルのメソッド内で + +// すべての article を検索する +$query = $articles->find('all'); +``` + +`find()` メソッドの戻り値は常に `Cake\ORM\SelectQuery` オブジェクトです。 +SelectQuery クラスにより、それの生成後は、クエリーをより精錬することができるようになります。 +SelectQuery オブジェクトは怠惰に評価され、行のフェッチ、配列への変換、 +もしくは `all()` メソッドの呼び出しをするまでは実行されません。 : + +``` php +// コントローラーやテーブルのメソッド内で + +// すべての article を検索 +// この時点ではクエリーは走らない。 +$query = $articles->find('all'); + +// イテレーションはクエリーを実行する +foreach ($query->all() as $row) { +} + +// all() の呼び出しはクエリーを実行し、結果セットを返す +$results = $query->all(); + +// 結果セットがあれば すべての行を取得できる +$data = $results->toList(); + +// クエリーからキーと値の配列への変換はクエリーを実行する +$data = $query->toArray(); +``` + +> [!NOTE] +> クエリーが開始されたら、 [クエリービルダー](../orm/query-builder) インターフェースを使うことができ、 +> この便利なインターフェースにより、条件、リミット、保持する関連の追加などが行えます。 +> より複雑なクエリーを構築することができます。 + +``` php +// コントローラーやテーブルのメソッド内で +$query = $articles->find('all') + ->where(['Articles.created >' => new DateTime('-10 days')]) + ->contain(['Comments', 'Authors']) + ->limit(10); +``` + +`find()` に対するとても一般的なオプションも提供します。これがあればテストの際にモックする +メソッドを少なくできます。 : + +``` php +// コントローラーやテーブルのメソッド内で +$query = $articles->find('all', [ + 'conditions' => ['Articles.created >' => new DateTime('-10 days')], + 'contain' => ['Authors', 'Comments'], + 'limit' => 10 +]); +``` + +find() で使えるオプションは次の通りです: + +- `conditions` クエリーの WHERE 句に使う条件を提供します。 +- `limit` 欲しい行数をセットします。 +- `offset` 欲しいページオフセットをセットします。 `page` をあわせて使うことで計算を簡単にできます。 +- `contain` 関連をイーガーロード (eager load) するように定義します。 +- `fields` エンティティーへとロードされる列を制限します。いくつかの列だけがロードされることになるので + エンティティーが正しく動かないこともありえます。 +- `group` クエリーに GROUP BY 句を加えます。集約関数を使う際に便利です。 +- `having` クエリーに HAVING 句を加えます。 +- `join` カスタム JOIN を追加で定義します。 +- `order` 結果セットに並び順を設定します。 + +このリストに無いオプションはどれも beforeFind リスナに渡され、クエリーオブジェクトの変更に使われます。 +クエリーオブジェクトの `getOptions()` メソッドを使うことで、利用中のオプションを取得することができます。 +クエリーオブジェクトをコントローラーに渡すよりも、 [Custom Find Methods](#custom-find-methods) でクエリーを +まとめることをお勧めします。カスタム finder メソッドを使うことでクエリーを再利用できるようになり、 +テストが簡単になります。 + +デフォルトでクエリーと結果セットは [エンティティー](../orm/entities) オブジェクトを返します。 +変換 (hydrate) を無効化すれば、素となる配列を取得することができます。 : + +``` php +$query->disableHydration(); + +// $data は配列のデータを含む ResultSet です。 +$data = $query->all(); +``` + +## 1つ目の結果を取得する + +`first()` メソッドによりクエリーから1つ目の行だけをフェッチすることができます。 +クエリーがまだ実行されいないなら、 `LIMIT 1` 句が適用されます。 : + +``` php +// コントローラーやテーブルのメソッド内で +$query = $articles->find('all', [ + 'order' => ['Articles.created' => 'DESC'] +]); +$row = $query->first(); +``` + +このアプローチは CakePHP 旧バージョンの `find('first')` を置き換えるものです。 +また、主キーでエンティティーをロードするなら `get()` メソッドも使いたいかもしれません。 + +> [!NOTE] +> `first()` メソッドは、結果が見つからない場合、 `null` を返します。 + +## 結果の件数を取得する + +クエリーオブジェクトを作成したら、 `count()` メソッドを使うことでクエリー結果の件数を +取得することができます。 : + +``` php +// コントローラーやテーブルのメソッド内で +$query = $articles->find('all', [ + 'conditions' => ['Articles.title LIKE' => '%Ovens%'] +]); +$number = $query->count(); +``` + +`count()` メソッドのさらなる用法は [Query Count](../orm/query-builder#query-count) を参照してください。 + +## キー/値のペアを検索する + +自分のアプリケーションのデータから関連する連想配列のデータを生成できると便利なときがよくあります +たとえば、 ` +``` + +> [!NOTE] +> これは *編集* フォームなので、デフォルトの HTTP メソッドを上書きするために +> hidden `input` フィールドが生成されます。 + +場合によっては、フォームの `action` の URL の最後にエンティティーの ID が自動的に付加されます。 +URL に ID が付加されることを避けたい場合、 `$options['url']` に `'/my-acount'` や +`\Cake\Routing\Router::url(['controller' => 'Users', 'action' => 'myAccount'])` +のように文字列を渡すことができます。 + +### フォーム作成のためのオプション + +`$options` 配列は、ほとんどのフォーム設定が行われる場所です。この特別な配列には、 +form タグの生成方法に影響を与えるさまざまなキーと値のペアが含まれます。 +有効な値: + +- `'type'` - 作成するフォームの種類を選択できます。type が未指定の場合、 + フォームコンテキストに基づいて自動的に決まります。 + 有効な値: + - `'get'` - フォームの method に HTTP GET を設定します。 + - `'file'` - フォームの method に POST を設定し、 `enctype` に + "multipart/form-data" を設定します。 + - `'post'` - method に POST を設定します。 + - `'put', 'delete', 'patch'` - フォームの送信時に、HTTP メソッドを + PUT、 DELETE もしくは PATCH に上書きします。 +- `'method'` - 有効な値は、上記と同じです。フォームの method を明示的に上書きできます。 +- `'url'` - フォームを送信する URL を指定します。文字列および URL 配列を指定できます。 +- `'encoding'` - フォームに `accept-charset` エンコーディングをセットします。 + デフォルトは、 `Configure::read('App.encoding')` です。 +- `'enctype'` - 明示的にフォームのエンコーディングをセットできます。 +- `'templates'` - このフォームで使用したいテンプレート。指定したテンプレートは、 + 既に読み込まれたテンプレートの上にマージされます。 `/config` のファイル名 (拡張子を除く) か、 + 使用したいテンプレートの配列のいずれかを指定します。 +- `'context'` - フォームコンテキストクラスの追加オプション。(例えば、 + `EntityContext` は、フォームのベースとなる特定の Table クラスを設定するための + `'table'` オプションを受け付けます。) +- `'idPrefix'` - 生成された ID 属性のプレフィックス。 +- `'templateVars'` - `formStart` テンプレートのためのテンプレート変数を提供することができます。 +- `autoSetCustomValidity` - コントロールの HTML5 検証メッセージでカスタム必須および notBlank 検証メッセージを使用するには、 + `true` を設定します。デフォルトは `false` です。 + +> [!TIP] +> 上記のオプションの他に、 `$options` 引数の中で、 作成した `form` 要素に渡したい +> 有効な HTML 属性を指定できます。 + +### クエリー文字列からフォームの値を取得 + +FormHelper の値ソースは、input タグなどの描画される要素がどこから値を受け取るかを定義します。 + +デフォルトでは、FormHelper は、「コンテキスト」をもとにその値を描画します。 +`EntityContext` などのデフォルトのコンテキストは、現在のエンティティーや +`$request->getData()` からデータを取得します。 + +しかし、クエリー文字列から読み込む必要があるフォームを構築している場合は、 `FormHelper` の +`valueSource()` を使って、どこから入力データを読み込むかを変更できます。 : + +``` php +// コンテキストでクエリー文字列の優先順位をつける +echo $this->Form->create($article, [ + 'type' => 'get', + 'valueSources' => ['query', 'context'] +]); + +// 同じ効果: +echo $this->Form + ->setValueSources(['query', 'context']) + ->create($articles, ['type' => 'get']); +``` + +When input data has to be processed by the entity, i.e. marshal transformations, table +query result or entity computations, and displayed after one or multiple form submissions +where request data is retained, you need to put `context` first: + +``` php +// Prioritize context over request data: +echo $this->Form->create($article, + 'valueSources' => ['context', 'data'] +]); +``` + +サポートするソースは、 `context`, `data` そして `query` です。 +単一または複数のソースを使用できます。 `FormHelper` によって生成されたウィジェットは +設定した順序でソースから値を集めます。 + +`end()` が呼ばれた時、値ソースはデフォルト (`['context']`) にリセットされます。 + +### フォームの HTTP メソッドを変更 + +`type` オプションを使用することにより、フォームが使用する HTTP メソッドを変更することができます。 : + +``` php +echo $this->Form->create($article, ['type' => 'get']); +``` + +出力結果: + +``` html + +``` + +`type` の値に `'file'` を指定すると、フォームの送信方法は、'POST' に変更し、form タグに +"multipart/form-data" の `enctype` が含まれます。 +これは、フォーム内部に file 要素がある場合に使用されます。 +適切な `enctype` 属性が存在しない場合は、ファイルのアップロードが機能しない原因となります。 + +例: + +``` php +echo $this->Form->create($article, ['type' => 'file']); +``` + +出力結果: + +``` html + +``` + +`'type'` の値として `'put'` 、 `'patch'` または `'delete'` を使用すると、 +フォームは機能的に 'post' フォームに相当しますが、送信されると、HTTP リクエストメソッドは、 +それぞれ 'PUT'、 'PATCH' または 'DELETE' で上書きされます。 +これで、CakePHP は、ウェブブラウザーで適切な REST サポートをエミュレートすることができます。 + +### フォームの URL を設定 + +`url` オプションを使うと、フォームを現在のコントローラーやアプリケーションの別のコントローラーの +特定のアクションに向けることができます。 + +例えば、フォームを現在のコントローラーの `publish()` アクションに向けるには、次のような +`$options` 配列を与えます。 : + +``` php +echo $this->Form->create($article, ['url' => ['action' => 'publish']]); +``` + +出力結果: + +``` html + +``` + +目的のフォームアクションが現在のコントローラーにない場合は、フォームアクションの完全な URL を指定できます。 +出力される URL は CakePHP アプリケーションに対する相対になります。 : + +``` php +echo $this->Form->create(null, [ + 'url' => [ + 'controller' => 'Articles', + 'action' => 'publish' + ] +]); +``` + +出力結果: + +``` html + +``` + +または外部ドメインを指定することができます。 : + +``` php +echo $this->Form->create(null, [ + 'url' => 'https://www.google.com/search', + 'type' => 'get' +]); +``` + +出力結果: + +``` html + +``` + +フォームアクションに URL を出力したくない場合、 `'url' => false` を使用してください。 + +### カスタムバリデーターの利用 + +多くの場合、モデルには複数の検証セットがあり、コントローラーアクションが適用される +特定の検証ルールに基づいて必要なフィールドに FormHelper を設定する必要があります。 +たとえば、Users テーブルには、アカウントの登録時にのみ適用される特定の検証ルールがあります。 : + +``` php +echo $this->Form->create($user, [ + 'context' => ['validator' => 'register'] +]); +``` + +上記では `UsersTable::validationRegister()` で定義されている `register` +バリデーターの中で定義されたルールを `$user` と関連するすべてのアソシエーションに使用します。 +関連付けられたエンティティーのフォームを作成する場合は、配列を使用して各アソシエーションの検証ルールを +定義できます。 : + +``` php +echo $this->Form->create($user, [ + 'context' => [ + 'validator' => [ + 'Users' => 'register', + 'Comments' => 'default' + ] + ] +]); +``` + +上記は、ユーザーには `register` 、そしてユーザーのコメントには `default` を使用します。 + +### コンテキストクラスの作成 + +組み込みのコンテキストクラスは基本的なケースをカバーすることを目的としていますが、 +異なる ORM を使用している場合は新しいコンテキストクラスを作成する必要があります。 +このような状況では、 [Cake\View\Form\ContextInterface](https://api.cakephp.org/4.x/class-Cake.View.Form.ContextInterface.html) +を実装する必要があります。 +このインターフェイスを実装すると、新しいコンテキストを FormHelper に追加することができます。 +`View.beforeRender` イベントリスナーやアプリケーションビュークラスで行うのが最善の方法です。 : + +``` php +$this->Form->addContextProvider('myprovider', function ($request, $data) { + if ($data['entity'] instanceof MyOrmClass) { + return new MyProvider($request, $data); + } +}); +``` + +コンテキストのファクトリー関数では、正しいエンティティータイプのフォームオプションを確認するための +ロジックを追加できます。一致する入力データが見つかった場合は、オブジェクトを返すことができます。 +一致するものがない場合は null を返します。 + +## フォームコントロールの作成 + +`method` Cake\\View\\Helper\\FormHelper::**control**(string $fieldName, array $options = []) + +- `$fieldName` - `'Modelname.fieldname'` の形式のフィールド名。 +- `$options` - [Control Specific Options](#control-specific-options) や (様々な HTML 要素を生成するために + `control()` が内部的に使用する) 他のメソッドのオプション、および有効な HTML 属性を + 含むオプション配列。 + +`control()` メソッドを使うと完全なフォームコントロールを生成できます。これらのコントロールには、 +必要に応じて、囲い込む `div` 、 `label` 、コントロールウィジェット、および検証エラーが含まれます。 +フォームコンテキストでメタデータを使用することにより、このメソッドは各フィールドに適切な +コントロールタイプを選択します。内部的に `control()` は FormHelper の他のメソッドを使います。 + +> [!TIP] +> `control()` メソッドによって生成されたフィールドは、このページでは一般的に +> "入力" と呼ばれますが、技術的にいえば、 `control()` メソッドは、 HTML の +> `input` 型の要素だけでなく、他の HTML フォーム要素 (`select` 、 `button` 、 +> `textarea` など) も生成できることに注意してください。 + +デフォルトでは、 `control()` メソッドは、次のウィジェットテンプレートを使用します。 : + +``` text +'inputContainer' => '
    {{content}}
    ' +'input' => '' +``` + +検証エラーが発生した場合は、以下も使われます。 : + +``` text +'inputContainerError' => '
    {{content}}{{error}}
    ' +``` + +作成されたコントロールの型(生成された要素タイプを指定する追加のオプションを指定しない場合)は、 +モデルの内部で推測され、列のデータ型に依存します。 + +作成されるコントロールの型は、カラムのデータ型に依存します。 + +カラムの型 +得られたフォームのフィールド + +string, uuid (char, varchar, その他) +text + +boolean, tinyint(1) +checkbox + +decimal +number + +float +number + +integer +number + +text +textarea + +text で、名前が password, passwd +password + +text で、名前が email +email + +text で、名前が tel, telephone, または phone +tel + +date +day, month, および year の select + +datetime, timestamp +day, month, year, hour, minute, および meridian の select + +time +hour, minute, および meridian の select + +binary +file + +`$options` パラメーターを使うと、必要な場合に特定のコントロールタイプを選択することができます。 : + +``` php +echo $this->Form->control('published', ['type' => 'checkbox']); +``` + +> [!TIP] +> とても些細なことですが、 `control()` フォームメソッドを使用して特定の要素を生成すると、 +> デフォルトでは `div` の囲い込みが常に生成されます。特定のフォームメソッド(例えば +> `$this->Form->checkbox('published');` )を使用して同じタイプの要素を生成すると、 +> ほとんどの場合、 `div` の囲い込みが生成されません。 +> あなたのニーズに応じて、どちらかを使うことができます。 + +
    + +モデルのフィールドの検証ルールで入力が必須であり、空を許可しない場合は、囲い込む `div` は、 +クラス名に `required` が追加されます。 +`required` オプションを使用して自動的に必須フラグを無効にすることができます。 : + +``` php +echo $this->Form->control('title', ['required' => false]); +``` + +
    + +フォーム全体のブラウザー検証トリガーをスキップするには、 +`Cake\View\Helper\FormHelper::submit()` を使って生成する入力ボタンに対して +`'formnovalidate' => true` オプションを設定したり、 +`Cake\View\Helper\FormHelper::create()` のオプションで +`'novalidate' => true` を設定できます。 + +たとえば、Users モデルに *username* (varchar), *password* (varchar), *approved* (datetime) +および *quote* (text) のフィールドがあるとします。FormHelper の `control()` メソッドを使用すると、 +これらのフォームフィールドすべてに適切なコントロールを作成できます。 : + +``` php +echo $this->Form->create($user); +// 以下は、テキスト入力を生成します +echo $this->Form->control('username'); +// 以下は、パスワード入力を生成します +echo $this->Form->control('password'); +// 'approved' を datetime か timestamp フィールドとみなし、 +// 以下は、日・月・年・時・分を生成します +echo $this->Form->control('approved'); +// 以下は、テキストエリア要素を生成します +echo $this->Form->control('quote'); + +echo $this->Form->button('Add'); +echo $this->Form->end(); +``` + +日付フィールドのいくつかのオプションを示すより広範な例: + +``` php +echo $this->Form->control('birth_dt', [ + 'label' => '生年月日', + 'min' => date('Y') - 70, + 'max' => date('Y') - 18, +]); +``` + +特定の [Control Specific Options](#control-specific-options) に加えて、選択された (または CakePHP によって推論された) +コントロールタイプや HTML 属性 (例えば `onfocus`) に対応する特定のメソッドによって +受け入れられるオプションを指定することができます。 + +*belongsTo* または *hasOne* を使用していて `select` フィールドを作成する場合は、 +Users コントローラーに次のものを追加できます(User *belongsTo* Group を前提とします)。 : + +``` php +$this->set('groups', $this->Users->Groups->find('list')); +``` + +その後、ビューテンプレートに以下を追加します。 : + +``` php +echo $this->Form->control('group_id', ['options' => $groups]); +``` + +*belongsToMany* で関連付く Groups の `select` ボックスを作成するには、 +UsersController に以下を追加します。 : + +``` php +$this->set('groups', $this->Users->Groups->find('list')); +``` + +その後、ビューテンプレートに以下を追加します。 : + +``` php +echo $this->Form->control('groups._ids', ['options' => $groups]); +``` + +モデル名が2つ以上の単語 (たとえば "UserGroup") で構成されている場合、 +`set()` を使用してデータを渡すときは、データを次のように複数形と +[ローワーキャメルケース](https://en.wikipedia.org/wiki/Camel_case#Variations_and_synonyms) +で名前を付ける必要があります。 : + +``` php +$this->set('userGroups', $this->UserGroups->find('list')->all()); +``` + +> [!NOTE] +> 送信ボタンを生成するために `FormHelper::control()` を使用しないでください。 +> 代わりに `Cake\View\Helper\FormHelper::submit()` を使用してください。 + +### フィールドの命名規則 + +コントロールウィジェットを作成するときは、フィールドの名前をフォームのエンティティーに一致する属性の後に +指定する必要があります。たとえば、 `$article` エンティティーのフォームを作成した場合、 +そのプロパティーの名前を付けたフィールドを作成します。例えば `title` 、 `body` と `published` 。 + +`association.fieldname` を最初のパラメーターとして渡すことで、関連するモデルや任意のモデルの +コントロールを作成できます。 : + +``` php +echo $this->Form->control('association.fieldname'); +``` + +フィールド名のドットは、ネストされたリクエストデータに変換されます。 +たとえば、 `0.comments.body` という名前のフィールドを作成した場合、 +`0[comments][body]` のような名前属性が得られます。 +この規則により、ORM でデータを簡単に保存できます。 +さまざまなアソシエーションタイプの詳細は、 [Associated Form Inputs](#associated-form-inputs) セクションにあります。 + +datetime に関連するコントロールを作成する場合、FormHelper はフィールドのサフィックスを追加します。 +`year` 、 `month` 、 `day` 、 `hour` 、 `minute` 、または `meridian` +というフィールドが追加されていることがあります。エンティティーがマーシャリングされると、 +これらのフィールドは自動的に `DateTime` オブジェクトに変換されます。 + +### コントロールのオプション + +`FormHelper::control()` は、その `$options` 引数を通して、多数のオプションをサポートしています。 +`control()` 自身のオプションに加えて、生成されたコントロールタイプに対するオプションと +HTML 属性を受け付けます。以下は `FormHelper::control()` で特有のオプションについて説明します。 + +- `$options['type']` - 生成するためのウィジェットタイプを指定する文字列。 + [Automagic Form Elements](#automagic-form-elements) にあるフィールド型に加えて、 `'file'` 、 `'password'` 、 + および HTML5 でサポートされているすべてのタイプを作成することもできます。 + `'type'` を指定することで、モデルの設定を上書きして、コントロールのタイプを強制することができます。 + デフォルトは `null` 。 + + 例: + + ``` php + echo $this->Form->control('field', ['type' => 'file']); + echo $this->Form->control('email', ['type' => 'email']); + ``` + + 出力結果: + + ``` html +
    + + +
    + + ``` + +- `$options['label']` - 文字列の見出しや [ラベルのオプション](#create-label) の配列。 + このキーは、通常は `input` HTML 要素に付随するラベル内に表示したい文字列に設定することができます。 + デフォルトは `null` です。 + + 例: + + ``` php + echo $this->Form->control('name', [ + 'label' => 'The User Alias' + ]); + ``` + + 出力結果: + + ``` html +
    + + +
    + ``` + + あるいは、 `label` 要素の出力を無効にするには、このキーに `false` を設定します。 + + 例: + + ``` php + echo $this->Form->control('name', ['label' => false]); + ``` + + 出力結果: + + ``` html +
    + +
    + ``` + + これに配列を設定すると、 `label` 要素の追加オプションが提供されます。 + これを行う場合、配列中の `text` キーを使ってラベルテキストをカスタマイズすることができます。 + + 例: + + ``` php + echo $this->Form->control('name', [ + 'label' => [ + 'class' => 'thingy', + 'text' => 'The User Alias' + ] + ]); + ``` + + 出力結果: + + ``` html +
    + + +
    + ``` + +- `$options['options']` - ここには、アイテムの配列を引数として必要とする `radio` や + `select` のようなウィジェットのために、生成される要素を含む配列を提供することができます + (詳細は、 [Create Radio Button](#create-radio-button) と [Create Select Picker](#create-select-picker) をご覧ください)。 + デフォルトは、 `null` です。 + +- `$options['error']` - このキーを使用すると、デフォルトのモデルエラーメッセージを + 無効にすることができ、たとえば国際化メッセージを設定するために使用できます。 + エラーメッセージの出力とフィールドクラスを無効にするには、 `'error'` キーを + `false` に設定してください。デフォルトは `null` 。 + + 例: + + ``` php + echo $this->Form->control('name', ['error' => false]); + ``` + + モデルのエラーメッセージを上書きするには、 + 元の検証エラーメッセージと一致するキーを持つ配列を使用します。 + + 例: + + ``` php + $this->Form->control('name', [ + 'error' => ['Not long enough' => __('This is not long enough')] + ]); + ``` + + 上記のように、モデルにある各検証ルールに対してエラーメッセージを設定することができます。 + さらに、フォームに国際化メッセージを提供することもできます。 + +- `$options['nestedInput']` - チェックボックスとラジオボタンで使用。 + input 要素を `label` 要素の内側か外側に生成するかどうかを制御します。 + `control()` がチェックボックスやラジオボタンを生成する時、これに `false` を設定して、 + `label` 要素の外側に HTML の `input` 要素を強制的に生成することができます。 + + 一方、任意のコントロールタイプに対して、これを `true` に設定することで、 + 生成された input 要素をラベルの中に強制的に入れることができます。 + これをラジオボタンで変更する場合は、デフォルトの [radioWrapper](#create-radio-button) + テンプレートも変更する必要があります。生成されるコントロールタイプによっては、 + デフォルトが `true` や `false` になります。 + +- `$options['templates']` - この入力に使用するテンプレート。 + 指定したテンプレートは、既に読み込まれたテンプレートの上にマージされます。 + このオプションは、ロードするテンプレートを含む `/config` のファイル名 (拡張子を除く) か、 + 使用するテンプレートの配列のいずれかです。 + +- `$options['labelOptions']` - これを `false` に設定すると nestedWidgets + の周りのラベルを無効にします。または、 `label` タグに提供される属性の配列を設定します。 + +- `$options['readOnly']` - フォームにて、フィールドを `readOnly` に設定します。 + + 例: + + ``` php + echo $this->Form->control('name', ['readonly' => true]); + ``` + +## コントロールの特定のタイプを生成 + +汎用的な `control()` メソッドに加えて、 `FormHelper` には様々な種類の +コントロールタイプを生成するために個別のメソッドがあります。 +これらは、コントロールウィジェットそのものを生成するのに使えますが、 +完全に独自のフォームレイアウトを生成するために +`Cake\View\Helper\FormHelper::label()` や +`Cake\View\Helper\FormHelper::error()` といった +他のメソッドを組み合わせることができます。 + +### 特定のコントロールのための共通オプション + +さまざまなコントロール要素メソッドは、共通のオプションをサポートしており、 +使用されるフォームメソッドに応じて、 `$options` または `$attributes` 配列の引数の中に +指定する必要があります。これらのオプションはすべて、 `control()` でもサポートされています。 +繰り返しを減らすために、すべてのコントロールメソッドで共有される共通オプションは次の通りです。 + +- `'id'` - このキーを設定すると、コントロールの DOM id の値が強制的に設定されます。 + これにより、設定可能な `'idPrefix'` が上書きされます。 + +- `'default'` コントロールフィールドのデフォルト値を設定します。 + この値は、フォームに渡されるデータにそのフィールドに関する値が含まれていない場合 + (または、一切データが渡されない場合) に使われます。 + 明示的なデフォルト値は、スキーマで定義されたデフォルト値を上書きします。 + + 使用例: + + ``` php + echo $this->Form->text('ingredient', ['default' => 'Sugar']); + ``` + + `select` フィールドを持つ例("Medium" サイズがデフォルトで選択されます) : + + ``` php + $sizes = ['s' => 'Small', 'm' => 'Medium', 'l' => 'Large']; + echo $this->Form->select('size', $sizes, ['default' => 'm']); + ``` + + > [!NOTE] + > checkbox をチェックする目的では `default` は使えません。その代わり、コントローラーで + > `$this->request->getData()` の中の値をセットするか、またはコントロールオプションの + > `checked` を `true` にします。 + > + > デフォルト値への代入の際 `false` を使うのは注意が必要です。 + > `false` 値はコントロールフィールドのオプションを無効または除外するために使われます。 + > そのため `'default' => false` では値を全く設定しません。 + > 代わりに `'default' => 0` を使用してください。 + +- `'value'` - コントロールフィールドに特定の値を設定するために使用します。 + これは、Form、Entity、 `request->getData()` などのコンテキストから + 注入される可能性のある値を上書きします。 + + > [!NOTE] + > コンテキストや valuesSource から値を取得しないようにフィールドを設定したい場合、 + > `'value'` を `''` に設定する必要があります (もしくは `null` に設定) 。 + +上記のオプションに加えて、任意の HTML 属性を混在させることができます。 +特に規定のないオプション名は HTML 属性として扱われ、生成された HTML のコントロール要素に反映されます。 + +## input 要素の作成 + +FormHelper で利用可能なメソッドには、さらに特定のフォーム要素を作成するためのものがあります。 +これらのメソッドの多くでは、特別な `$options` や `$attributes` パラメーターを指定できます。 +ただし、この場合、このパラメーターは主に (フォーム要素の DOM id の値のような) HTML タグの属性を +指定するために使われます。 + +### テキスト入力の作成 + +`method` Cake\\View\\Helper\\FormHelper::**text**(string $name, array $options) + +- `$name` - `'Modelname.fieldname'` の形式のフィールド名。 +- `$options` - [General Control Options](#general-control-options) や有効な HTML 属性を含むオプション配列。 + +シンプルな `text` 型の `input` HTML 要素を作成します。 + +例: + +``` php +echo $this->Form->text('username', ['class' => 'users']); +``` + +出力結果: + +``` html + +``` + +### パスワード入力の作成 + +`method` Cake\\View\\Helper\\FormHelper::**password**(string $fieldName, array $options) + +- `$fieldName` - `'Modelname.fieldname'` の形式のフィールド名。 +- `$options` - [General Control Options](#general-control-options) や有効な HTML 属性を含むオプション配列。 + +シンプルな `password` 型の `input` 要素を作成します。 + +例: + +``` php +echo $this->Form->password('password'); +``` + +出力結果: + +``` html + +``` + +### 非表示入力の作成 + +`method` Cake\\View\\Helper\\FormHelper::**hidden**(string $fieldName, array $options) + +- `$fieldName` - `'Modelname.fieldname'` の形式のフィールド名。 +- `$options` - [General Control Options](#general-control-options) や有効な HTML 属性を含むオプション配列。 + +非表示のフォーム入力を作成します。 + +例: + +``` php +echo $this->Form->hidden('id'); +``` + +出力結果: + +``` html + +``` + +### テキストエリアの作成 + +`method` Cake\\View\\Helper\\FormHelper::**textarea**(string $fieldName, array $options) + +- `$fieldName` - `'Modelname.fieldname'` の形式のフィールド名。 +- `$options` - [General Control Options](#general-control-options) やテキストエリア特有のオプション + (下記参照) と、有効な HTML 属性を含むオプション配列。 + +textarea コントロールフィールドを作成します。使用されるデフォルトのウィジェットテンプレートは、 : + +``` text +'textarea' => '' +``` + +例: + +``` php +echo $this->Form->textarea('notes'); +``` + +出力結果: + +``` html + +``` + +フォームが編集されると(すなわち、配列 `$this->request->getData()` に +`User` モデルに渡すために保存された情報が含まれている場合)、生成される HTML には +`notes` フィールドに対応する値が自動的に含まれます。 + +例: + +``` html + +``` + +**テキストエリアのオプション** + +[General Control Options](#general-control-options) に加えて、 `textarea()` はいくつかの固有のオプションを +サポートします。 + +- `'escape'` - テキストエリアの内容をエスケープするかどうかを指定します。 + デフォルトは `true` です。 + + 例: + + ``` php + echo $this->Form->textarea('notes', ['escape' => false]); + // もしくは.... + echo $this->Form->control('notes', ['type' => 'textarea', 'escape' => false]); + ``` + +- `'rows', 'cols'` - これらの2つのキーを使用して、 `textarea` フィールドの行数と列数を + 指定する HTML 属性を設定することができます。 + + 例: + + ``` php + echo $this->Form->textarea('comment', ['rows' => '5', 'cols' => '5']); + ``` + + 出力結果: + + ``` html + + ``` + +### セレクト、チェックボックス、ラジオコントロールの作成 + +これらのコントロールは、いくつかの共通点といくつかのオプションを共有し、 +それらは簡単に参照するために、このサブセクションで全てグループ化します。 + +#### セレクト、チェックボックス、ラジオに関するオプション + +`select()` 、 `checkbox()` そして `radio()` によって共有されるオプションは次の通りです。 +(各メソッドの独自のセクションには、そのメソッド特有のオプションが記述されています。) + +- `'value'` - 影響を受ける要素の値を設定または選択します。 + + - チェックボックスの場合、 `input` 要素に割り当てられた HTML の `'value'` 属性を、 + 値として提供するものに設定します。 + + - ラジオボタンまたは選択ピッカーの場合は、フォームが描画されるときに選択される要素を定義します + (この場合、 `'value'` は有効で存在する要素の値を割り当てなければなりません)。 + `date()` 、 `time()` 、 `dateTime()` のようなセレクト型コントロールと + 組み合わせて使用することもできます。 : + + ``` php + echo $this->Form->time('close_time', [ + 'value' => '13:30:00' + ]); + ``` + + > [!NOTE] + > `date()` および `dateTime()` コントロールの `'value'` キーには、 + > UNIX タイムスタンプまたは DateTime オブジェクトを使用することもできます。 + + `multiple` 属性を `true` に設定した `select` コントロールでは、 + デフォルトで選択したい値の配列を使うことができます。 : + + ``` php + // 値に 1 と 3 を持つ HTML ' +``` + +以下も使用します。 : + +``` text +'optgroup' => '{{content}}' +'selectMultiple' => '' +``` + +**選択ピッカーの属性** + +- `'multiple'` - `true` をセットすると、選択ピッカー内で複数選択ができます。 + `'checkbox'` をセットすると、複数チェックボックスが代わりに作成されます。 + デフォルトは `null` です。 +- `'escape'` - ブール値。 `true` の場合、選択ピッカー内の `option` 要素の内容は + エンコードされた HTML エンティティーになります。デフォルトは `true` です。 +- `'val'` - 選択ピッカーで値を事前に選択できるようにします。 +- `'disabled'` - `disabled` 属性を制御します。 + `true` をセットした場合、選択ピッカー全体を無効にします。 + 配列をセットした場合、配列に含まれている値を持つ特定の `option` のみ無効にします。 + +`$options` 引数は、 `select` コントロールの `option` 要素の内容を手動で指定できます。 + +例: + +``` php +echo $this->Form->select('field', [1, 2, 3, 4, 5]); +``` + +出力結果: + +``` html + +``` + +`$options` の配列はキーと値のペアとしても指定することができます。 + +例: + +``` php +echo $this->Form->select('field', [ + 'Value 1' => 'Label 1', + 'Value 2' => 'Label 2', + 'Value 3' => 'Label 3' +]); +``` + +出力結果: + +``` html + +``` + +optgroup 付きで `select` を生成したい場合は、データを階層形式 (ネストした配列) で渡すだけです。 +これは複数のチェックボックスとラジオボタンでも機能しますが、 `optgroup` の代わりに +`fieldset` 要素で囲みます。 + +例: + +``` php +$options = [ + 'Group 1' => [ + 'Value 1' => 'Label 1', + 'Value 2' => 'Label 2' + ], + 'Group 2' => [ + 'Value 3' => 'Label 3' + ] +]; +echo $this->Form->select('field', $options); +``` + +出力結果: + +``` html + +``` + +`option` タグ内で HTML 属性を生成するには: + +``` php +$options = [ + [ 'text' => 'Description 1', 'value' => 'value 1', 'attr_name' => 'attr_value 1' ], + [ 'text' => 'Description 2', 'value' => 'value 2', 'attr_name' => 'attr_value 2' ], + [ 'text' => 'Description 3', 'value' => 'value 3', 'other_attr_name' => 'other_attr_value' ], +]; +echo $this->Form->select('field', $options); +``` + +出力結果: + +``` html + +``` + +**属性による選択ピッカーの制御** + +`$attributes` パラメーター内の特定のオプションを使用することにより、 +`select()` メソッドの特定の振る舞いを制御することができます。 + +- `'empty'` - `$attributes` 引数の中で `'empty'` キーを `true` にセットすると + (デフォルトの値は `false`)、ドロップダウンリストの先頭に空の値の空白オプションを追加できます。 + + 例: + + ``` php + $options = ['M' => 'Male', 'F' => 'Female']; + echo $this->Form->select('gender', $options, ['empty' => true]); + ``` + + 出力結果: + + ``` html + + ``` + +- `'escape'` - `select()` メソッドは `'escape'` という属性が使用でき、 + ブール値を受け取り、HTML エンティティーに select オプションの内容をエンコードするかどうかを決定します。 + + 例: + + ``` php + // これで、各オプション要素の内容の HTML エンコードを止められます + $options = ['M' => 'Male', 'F' => 'Female']; + echo $this->Form->select('gender', $options, ['escape' => false]); + ``` + +- `'multiple'` - `true` にセットすると、選択ピッカーは複数選択ができます。 + + 例: + + ``` php + echo $this->Form->select('field', $options, ['multiple' => true]); + ``` + + または、関連するチェックボックスのリストを出力するために + `'multiple'` を `'checkbox'` に設定します。 : + + ``` php + $options = [ + 'Value 1' => 'Label 1', + 'Value 2' => 'Label 2' + ]; + echo $this->Form->select('field', $options, [ + 'multiple' => 'checkbox' + ]); + ``` + + 出力結果: + + ``` html + +
    + +
    +
    + +
    + ``` + +- `'disabled'` - このオプションを設定して、すべてまたは一部の `select` の `option` 項目を + 無効にすることができます。すべての項目を無効にするには、 `'disabled'` に `true` を + 設定します。特定の項目のみを無効にするには、無効にする項目をキーに含む配列を `'disabled'` に + 設定してください。 + + すべてのチェックボックスを無効にするには disabled を `true` にします。 + + 例: + + ``` php + $options = [ + 'M' => 'Masculine', + 'F' => 'Feminine', + 'N' => 'Neuter' + ]; + echo $this->Form->select('gender', $options, [ + 'disabled' => ['M', 'N'] + ]); + ``` + + 出力結果: + + ``` html + + ``` + + このオプションは `'multiple'` が `'checkbox'` に設定されている場合にも有効です。 : + + ``` php + $options = [ + 'Value 1' => 'Label 1', + 'Value 2' => 'Label 2' + ]; + echo $this->Form->select('field', $options, [ + 'multiple' => 'checkbox', + 'disabled' => ['Value 1'] + ]); + ``` + + 出力結果: + + ``` html + +
    + +
    +
    + +
    + ``` + +### ファイル入力の作成 + +`method` Cake\\View\\Helper\\FormHelper::**file**(string $fieldName, array $options) + +- `$fieldName` - `'Modelname.fieldname'` の形式のフィールド名。 +- `$options` - [General Control Options](#general-control-options) や有効な HTML 属性を含むオプション配列。 + +フォームの中にファイルアップロードフィールドを作成します。 +デフォルトで使用されるウィジェットテンプレートは: + +``` text +'file' => '' +``` + +フォームにファイルアップロードフィールドを追加するためには、まずフォームの enctype に +`'multipart/form-data'` がセットされていることを確認してください。 + +まずは、次のように `create()` メソッドを使用してください。 : + +``` php +echo $this->Form->create($document, ['enctype' => 'multipart/form-data']); +// または +echo $this->Form->create($document, ['type' => 'file']); +``` + +次にフォームのビューファイルに以下のいずれかを追加します。 : + +``` php +echo $this->Form->control('submittedfile', [ + 'type' => 'file' +]); + +// または +echo $this->Form->file('submittedfile'); +``` + +> [!NOTE] +> HTML 自体の制限により、'file' タイプの入力フィールドにデフォルト値を設定することはできません。 +> フォームを表示するたびに、内部の値は空に設定されます。 + +フォームの送信に際して file フィールドは、フォームを受信しようとしているスクリプトに対して拡張された +data 配列を提供します。 + +CakePHP が Windows サーバー上にインストールされている場合、上記の例について、 +送信されるデータ配列内の値は次のように構成されます +(Unix 環境では `'tmp_name'` が異なったパスになります)。 : + +``` php +$this->request->data['submittedfile'] + +// 次の配列を含みます: +[ + 'name' => 'conference_schedule.pdf', + 'type' => 'application/pdf', + 'tmp_name' => 'C:/WINDOWS/TEMP/php1EE.tmp', + 'error' => 0, // Windows の場合、文字列になります。 + 'size' => 41737, +]; +``` + +この配列は PHP 自身によって生成されます。PHP が file フィールドを通してデータを +どう処理しているのかについては、 [PHP マニュアルのファイルアップロードのセクションをご覧ください](https://secure.php.net/features.file-upload) 。 + +> [!NOTE] +> `$this->Form->file()` を使う場合、 `$this->Form->create()` の中の `'type'` +> オプションを `'file'` に設定することで、フォームのエンコーディングのタイプを設定できます。 + +### 日付と時刻に関するコントロールの作成 + +日時関連の方法は、多くの共通の特性とオプションを共有しているため、 +このサブセクションにまとめられています。 + +#### 日付と時刻のコントロールの共通オプション + +これらのオプションは、日付と時刻に関するコントロールに共通します。 + +- `'empty'` - `true` の場合、余分の空の `option` HTML 要素が、 + `select` の中のリストの先頭に追加されます。文字列の場合、 + その文字列は空の要素として表示されます。デフォルトは `true` です。 +- `'default'` \| `value` - 2つのいずれかを使用して、 フィールドに表示されるデフォルト値を設定します。 フィールド名と一致する `$this->request->getData()` の値は、この値を上書きします。 デフォルトが指定されていない場合、 `time()` が使用されます。 - `'year', 'month', 'day', 'hour', 'minute', 'second', 'meridian'` -これらのオプションを使用すると、コントロール要素が生成されるかどうか制御できます。 これらのオプションを `false` にセットすることにより、特定の選択ピッカーの生成を 無効にすることができます (デフォルトでは、使用されたメソッドの中で描画されます) 。 さらに、各オプションでは、HTML 属性を指定した `select` 要素に渡すことができます。 + +#### 日付関連コントロールのオプション + +これらのオプションは、日付関連のメソッド、つまり `year()` 、 `month()` 、 +`day()` 、 `dateTime()` そして `date()` に関連しています。 + +- `'monthNames'` - `false` の場合は、選択ピッカーの月の表示で + テキストの代わりに2桁の数字が使用されます。配列をセットした場合 + (例 `['01' => 'Jan', '02' => 'Feb', ...]`)、指定された配列が使用されます。 +- `'min'` - 年の select フィールドで使用される最小の年。 +- `'max'` - 年の select フィールドで使用される最大の年。 +- `'order'` - 年選択ピッカー内の年の値の順序。 + 利用可能な値は `'asc'` と `'desc'` 。デフォルトは `'desc'` です。 +- `'year', 'month', 'day'` - これらのオプションを使用すると、コントロール要素が生成されるかどうか制御できます。 + これらのオプションを `false` にセットすることにより、特定の選択ピッカーの生成を + 無効にすることができます (デフォルトでは、使用されたメソッドの中で描画されます) 。 + さらに、各オプションでは、HTML 属性を指定した `select` 要素に渡すことができます。 + +#### 時刻関連コントロールのオプション + +これらのオプションは、時刻関連のメソッド、 `hour()` 、 `minute()` 、 +`second()` 、 `dateTime()` そして `time()` に関連しています。 + +- `'interval'` - 分選択ピッカーの `option` 要素の中に表示される分の値の間隔。 + デフォルトは 1 です。 +- `'round'` - 値が一定の間隔にきちんと収まらないときに、いずれかの方向に丸めるようにしたい場合は、 + `up` または `down` に設定します。デフォルトは `null` です。 +- `timeFormat` - `dateTime()` と `time()` に適用されます。 + 選択ピッカーで使用する時刻の書式は、 `12` または `24` のいずれかです。 + このオプションに `24` 以外の何かをセットした場合、書式は自動的に `12` がセットされ、 + 秒選択ピッカーの右側に `meridian` 選択ピッカーが自動的に表示されます。 + デフォルトは 24 です。 +- `format` - `hour()` に適用されます。 + 選択ピッカーで使用する時刻の書式は、 `12` または `24` のいずれかです。 + `12` をセットした場合、 `meridian` 選択ピッカーは自動的に表示されません。 + それを追加するか、フォームコンテキストから適切な期間を推論する方法を提供するかは、 + あなた次第です。デフォルトは 24 です。 +- `second` - `dateTime()` と `time()` に適用されます。 + 秒を有効にするために `true` に設定します。デフォルトは `false` です。 +- `'hour', 'minute', 'second', 'meridian'` - これらのオプションを使用すると、コントロール要素が生成されるかどうか制御できます。 + これらのオプションを `false` にセットすることにより、特定の選択ピッカーの生成を + 無効にすることができます (デフォルトでは、使用されたメソッドの中で描画されます) 。 + さらに、各オプションでは、HTML 属性を指定した `select` 要素に渡すことができます。 + +#### 日時入力の作成 + +`method` Cake\\View\\Helper\\FormHelper::**dateTime**(string $fieldName, array $options = []) + +- `$fieldName` - `select` 要素の HTML `name` 属性のプレフィックスとして使用される文字列。 +- `$options` - [General Control Options](#general-control-options) または日時特有のオプション (下記参照)、 + そして有効な HTML 属性を含むオプション配列。 + +日付と時刻の `select` 要素のセットを生成します。 + +コントロールの順序、およびコントロール間の要素/内容を制御するには、 `dateWidget` +テンプレートを上書きします。デフォルトで `dateWidget` テンプレートは: + +``` text +{{year}}{{month}}{{day}}{{hour}}{{minute}}{{second}}{{meridian}} +``` + +オプションを指定せずにメソッドを呼び出すと、デフォルトでは、年(4桁)、月(英語の完全名)、 +曜日(数値)、時間(数値)、分(数値)の5つの選択ピッカーが生成されます。 + +例: + +``` php +form->dateTime('registered') ?> +``` + +出力結果: + +``` html + + + + + +``` + +特定の select ボックスにカスタムクラス/属性を含む datetime コントロールを作成するには、 +`$options` 引数の中で各コンポーネントのオプションの配列として指定します。 + +例: + +``` php +form->dateTime('registered', ['value' => new DateTime()]) ?> +``` + +これは、次の2つの選択ピッカーを作成します。 + +``` html + +``` + +#### 日付コントロールの作成 + +`method` Cake\\View\\Helper\\FormHelper::**date**(string $fieldName, array $options = []) + +- `$fieldName` - `select` 要素の HTML `name` 属性のプレフィックスとして使用される文字列。 +- `$options` - [General Control Options](#general-control-options) 、 [Datetime Options](#datetime-options) 、 + 適用可能な [Time Options](#time-options) 、そして有効な HTML 属性を含むオプション配列。 + +デフォルトでは、年(4桁)、月(英語の完全名)、日(数値)の値が設定された +3つの選択ピッカーを作成します。 + +生成された `select` 要素をさらに制御するには、オプションを追加します。 + +例: + +``` php +// 今年が 2017 年だと仮定すると、これは日ピッカーを無効にし、年ピッカーの空の +// オプションを削除し、最低年を制限し、年の HTML 属性を追加し、 +// 月の文字列の 'empty' オプションを追加し、月を数値に変更します。 +Form->date('registered', [ + 'minYear' => 2018, + 'monthNames' => false, // 月は数字で表示されます。 + 'empty' => [ + 'year' => false, // 年選択コントロールは空の値のオプションを持ちません。 + 'month' => 'Choose month...' // しかしながら、月選択コントロールは持ちます。 + ], + 'day' => false, // 日付選択コントールを表示しない。 + 'year' => [ + 'class' => 'cool-years', + 'title' => 'Registration Year' + ] + ]); +?> +``` + +出力結果: + +``` html + +``` + +#### 時間コントロールの作成 + +`method` Cake\\View\\Helper\\FormHelper::**time**(string $fieldName, array $options = []) + +- `$fieldName` - `select` 要素の HTML `name` 属性のプレフィックスとして使用される文字列。 +- `$options` - [General Control Options](#general-control-options) 、 [Datetime Options](#datetime-options) 、 + 適用可能な [Time Options](#time-options) 、そして有効な HTML 属性を含むオプション配列。 + +デフォルトでは、 24 時間と 60 分の値が入力された2つの `select` 要素 (`hour` と `minute`) +を生成します。さらに、HTML 属性は、特定のコンポーネントごとに `$options` で指定することができます。 +`$options['empty']` が `false` の場合、選択ピッカーは空のデフォルトオプションを含みません。 + +たとえば、15 分単位で選択できる時間範囲を作成し、各 select ボックスにクラスを適用するには、 +次のようにします。 : + +``` php +echo $this->Form->time('released', [ + 'interval' => 15, + 'hour' => [ + 'class' => 'foo-class', + ], + 'minute' => [ + 'class' => 'bar-class', + ], +]); +``` + +これは、次の2つの選択ピッカーを作成します。 + +``` html +echo $this->Form->time('released'); +``` + +#### 年コントロールの作成 + +`method` Cake\\View\\Helper\\FormHelper::**year**(string $fieldName, array $options = []) + +- `$fieldName` - `select` 要素の HTML `name` 属性のプレフィックスとして使用される文字列。 +- `$options` - [General Control Options](#general-control-options) 、 [Datetime Options](#datetime-options) 、 + 適用可能な [Time Options](#time-options) 、そして有効な HTML 属性を含むオプション配列。 + +`min` から `max` (これらのオプションが提供されているとき)、 +または今日から数えて-5年から+5年までの値を持つ `select` 要素を作成します。 +さらに、HTML 属性は、 `$options` で指定することができます。 +`$options ['empty']` が `false` の場合、選択ピッカーはリスト内に空の項目を含みません。 + +たとえば、2000 年から今年までの年を作成するには、次のようにします。 : + +``` php +echo $this->Form->year('purchased', [ + 'min' => 2000, + 'max' => date('Y') +]); +``` + +2009 年だった場合は、次のようになるでしょう。 + +``` html + +``` + +#### 月コントロールの作成 + +`method` Cake\\View\\Helper\\FormHelper::**month**(string $fieldName, array $attributes) + +- `$fieldName` - `select` 要素の HTML `name` 属性のプレフィックスとして使用される文字列。 +- `$attributes` - [General Control Options](#general-control-options) 、 [Datetime Options](#datetime-options) 、 + 適用可能な [Time Options](#time-options) 、そして有効な HTML 属性を含むオプション配列。 + +月の名前を列挙した `select` 要素を作成します。 + +例: + +``` php +echo $this->Form->month('mob'); +``` + +Will output: + +``` html + +``` + +#### 時間コントロールの作成 + +`method` Cake\\View\\Helper\\FormHelper::**year**(string $fieldName, array $options = []) + +- `$fieldName` - `select` 要素の HTML `name` 属性のプレフィックスとして使用される文字列。 +- `$attributes` - [General Control Options](#general-control-options) 、 [Datetime Options](#datetime-options) 、 + 適用可能な [Time Options](#time-options) 、そして有効な HTML 属性を含むオプション配列。 + +時を列挙した `select` 要素を作成します。 + +`format` オプションを使用して、12 時間または 24 時間のピッカーを作成することができます。 : + +``` php +echo $this->Form->hour('created', [ + 'format' => 12 +]); +echo $this->Form->hour('created', [ + 'format' => 24 +]); +``` + +#### 分コントロールの作成 + +`method` Cake\\View\\Helper\\FormHelper::**minute**(string $fieldName, array $attributes) + +- `$fieldName` - `select` 要素の HTML `name` 属性のプレフィックスとして使用される文字列。 +- `$attributes` - [General Control Options](#general-control-options) 、 [Datetime Options](#datetime-options) 、 + 適用可能な [Time Options](#time-options) 、そして有効な HTML 属性を含むオプション配列。 + +分の値を列挙した `select` 要素を作成します。 +`interval` オプションを使用して特定の値のみを含む選択ピッカーを作成することができます。 + +たとえば、10 分ずつ増やしたい場合は、次のようにします。 : + +``` php +// ビューテンプレートファイルの中で +echo $this->Form->minute('arrival', [ + 'interval' => 10 +]); +``` + +これは、以下を出力します。 + +``` html + +``` + +## ラベルの作成 + +`method` Cake\\View\\Helper\\FormHelper::**label**(string $fieldName, string $text, array $options) + +- `$fieldName` - `'Modelname.fieldname'` の形式のフィールド名。 +- `$text` - ラベルの見出しテキストを指定するためのオプション文字列。 +- `$options` - オプション。[General Control Options](#general-control-options) と有効な HTML 属性を含む配列。 + +`label` 要素を作成します。引数の `$fieldName` は、要素の HTML `for` 属性を +生成するために使われます。 `$text` が未定義の場合、 `$fieldName` はラベルの +`text` 属性を変えるために使われます。 + +例: + +``` php +echo $this->Form->label('name'); +echo $this->Form->label('name', 'Your username'); +``` + +出力結果: + +``` html + + +``` + +第3パラメーター `$options` に id や class を設定できます。 : + +``` php +echo $this->Form->label('name', null, ['id' => 'user-label']); +echo $this->Form->label('name', 'Your username', ['class' => 'highlight']); +``` + +出力結果: + +``` html + + +``` + +## エラーの表示と確認 + +FormHelper は、フィールドエラーを簡単にチェックしたり、必要に応じてカスタマイズされた +エラーメッセージを表示できる、いくつかのメソッドを公開しています。 + +### エラーの表示 + +`method` Cake\\View\\Helper\\FormHelper::**error**(string $fieldName, mixed $text, array $options) + +- `$fieldName` - `'Modelname.fieldname'` の形式のフィールド名。 +- `$text` - オプション。エラーメッセージを提供する文字列または配列。 + 配列の場合、 キー名 =\> メッセージのハッシュになります。デフォルトは `null` 。 +- `$options` - `'escape'` キーのブール値のみを含みます。これは、 + エラーメッセージの内容を HTML エスケープするかどうかを定義します。デフォルトは `true` です。 + +検証エラーが発生した際に、与えられたフィールドの `$text` で指定された、 +検証エラーメッセージを表示します。フィールドの `$text` がない場合、 +そのフィールドのデフォルトの検証エラーメッセージが使用されます。 + +次のテンプレートウィジェットを使います。 : + +``` text +'error' => '
    {{content}}
    ' +'errorList' => '
      {{content}}
    ' +'errorItem' => '
  • {{text}}
  • ' +``` + +`'errorList'` と `'errorItem'` テンプレートは、1つのフィールドに複数の +エラーメッセージを書式化するために使用されます。 + +例: + +``` php +// TicketsTable に 'notEmpty' 検証ルールがある場合: +public function validationDefault(Validator $validator) +{ + $validator + ->requirePresence('ticket', 'create') + ->notEmpty('ticket'); +} + +// そして、 templates/Tickets/add.php の中が次のような場合: +echo $this->Form->text('ticket'); + +if ($this->Form->isFieldError('ticket')) { + echo $this->Form->error('ticket', 'Completely custom error message!'); +} +``` + +もし、 *Ticket* フィールドの値を指定せずにフォームの *Submit* ボタンをクリックした場合、 +フォームは次のように出力されます。 + +``` html + +
    Completely custom error message!
    +``` + +> [!NOTE] +> `Cake\View\Helper\FormHelper::control()` を使用している時、 +> デフォルトではエラーは描画されますので、 `isFieldError()` を使用したり、 +> 手動で `error()` を呼び出す必要はありません。 + +> [!TIP] +> あるモデルのフィールドを使用して、 `control()` で複数のフォームフィールドを生成し、 +> それぞれ同じ検証エラーメッセージを表示させたい場合、それぞれの +> [検証ルール](../../core-libraries/validation#creating-validators) の中でカスタムエラーメッセージを +> 定義する方が良いでしょう。 + +
    + +Add examples. + +
    + +### エラーの確認 + +`method` Cake\\View\\Helper\\FormHelper::**isFieldError**(string $fieldName) + +- `$fieldName` - `'Modelname.fieldname'` の形式のフィールド名。 + +指定された `$fieldName` に有効な検証エラーがある場合は `true` を返します。 +そうでなければ `fales` を返します。 + +例: + +``` php +if ($this->Form->isFieldError('gender')) { + echo $this->Form->error('gender'); +} +``` + +## HTML5 検証メッセージにバリデーションメッセージを表示 + +`autoSetCustomValidity` FormHelper オプションが `true` に設定されている場合、 +デフォルトのブラウザーの HTML5 必須メッセージの代わりに、フィールドの必須および +notBlank バリデーションメッセージに対するエラーメッセージが使用されます。 +このオプションを有効にすると、フィールドに `onvalid` と `oninvalid` イベント属性が追加されます。 +例えば、 : + +``` html + +``` + +カスタム Javascript を使用してこれらのイベントを手動で設定したい場合は、 +`autoSetCustomValidity` オプションを `false` に設定して、 +代わりに 特別な `customValidityMessage` テンプレート変数を使用することができます。 +このテンプレート変数はフィールドが必須の場合に追加されます。 : + +``` text +// テンプレート例 +[ + 'input' => '', +] + +// このような input が作成されます + +``` + +それから Javascript を使って `onvalid` と `oninvalid` イベントを好きなように設定できます。 + +## ボタンと submit 要素の作成 + +### Submit 要素の作成 + +`method` Cake\\View\\Helper\\FormHelper::**submit**(string $caption, array $options) + +- `$caption` - ボタンのテキスト見出しまたは画像へのパスを提供するオプション文字列。 + デフォルトは、 `'Submit'` です。 +- `$options` - [General Control Options](#general-control-options) 、または submit 特有のオプション + (下記参照) 。 + +`$caption` を値としてもつ `submit` タイプの `input` 要素を作成します。 +指定された `$caption` が画像の URL である場合 (つまり、 '://' を含む文字列または、拡張子 +'.jpg, .jpe, .jpeg, .gif' を含む場合)、画像の送信ボタンが生成され、指定された画像が +存在する場合は、それを使用します。最初の文字が '/' の場合、画像は *webroot* からの +相対パスになり、最初の文字が '/' ではない場合、画像は *webroot/img* からの相対パスになります。 + +デフォルトで次のウィジェットテンプレートを使用します。 : + +``` text +'inputSubmit' => '' +'submitContainer' => '
    {{content}}
    ' +``` + +**Submit のオプション** + +- `'type'` - リセットボタンを生成するためにこのオプションに `'reset'` を設定します。 + デフォルトは `'submit'` です。 +- `'templateVars'` - input 要素や、そのコンテナーにテンプレート変数を追加するために、 + この配列を設定します。 +- その他の指定された属性は `input` 要素に割り当てられます。 + +例: + +``` php +echo $this->Form->submit('Click me'); +``` + +出力結果: + +``` html +
    +``` + +見出しテキストの代わりに見出しパラメーターとして画像への相対 URL または +絶対 URL を渡すことができます。 : + +``` php +echo $this->Form->submit('ok.png'); +``` + +出力結果: + +``` html +
    +``` + +submit 入力は、基本的なテキストやイメージが必要な場合に便利です。 +より複雑なボタンの内容が必要な場合は、 `button()` を使用してください。 + +### ボタン要素の作成 + +`method` Cake\\View\\Helper\\FormHelper::**button**(string $title, array $options = []) + +- `$title` - ボタンの見出しテキストを提供する必須の文字列。 +- `$options` - [General Control Options](#general-control-options) やボタン特有のオプション (下記参照)と + 有効な HTML 属性を含むオプション配列。 + +指定されたタイトルと `'button'` のデフォルトタイプの HTML ボタンを作成します。 + +**ボタンのオプション** + +- `'type'` - これを設定すると、次の3つの button タイプのどれかが出力されます。 + 1. `'submit'` - `$this->Form->submit()` と同様に送信ボタンを作成します。 + しかしながら、 `submit()` のように `div` の囲い込みは生成しません。 + これがデフォルトのタイプです。 + 2. `'reset'` - フォームのリセットボタンを作成します。 + 3. `'button'` - 標準の押しボタンを作成します。 +- `'escapeTitle'` - ブール値。 `true` をセットした場合、 + `$title` で指定された値を HTML エンコードします。デフォルトは `true` です。 +- `'escape'` - ブール値。 `true` に設定すると、 + ボタンに対して生成されたすべてのHTML属性をHTMLエンコードします。 デフォルトは `true` です。 +- `'confirm'` - クリック時に表示される確認メッセージ。デフォルトは `null` です。 + +例: + +``` php +echo $this->Form->button('ボタン'); +echo $this->Form->button('別のボタン', ['type' => 'button']); +echo $this->Form->button('フォームのリセット', ['type' => 'reset']); +echo $this->Form->button('フォームの送信', ['type' => 'submit']); +``` + +出力結果: + +``` html + + + + +``` + +`'escapeTitle'` オプションの使用例: + +``` php +// エスケープされていないHTMLをレンダリングします。 +echo $this->Form->button('Submit Form', [ + 'type' => 'submit', + 'escapeTitle' => false, +]); +``` + +## フォームを閉じる + +`method` Cake\\View\\Helper\\FormHelper::**end**($secureAttributes = []) + +- `$secureAttributes` - オプション。FormProtectionComponent 用に生成された非表示の + input 要素に HTML 属性として渡されるセキュアな属性を提供できます。 + +`end()` は、フォームを閉じて完成します。 +多くの場合、 `end()` は終了タグだけを出力しますが、 `end()` を使うと、 +FormHelper が `Cake\Controller\Component\FormProtectionComponent` に必要な +hidden フォーム要素を挿入できるようになります。 + +``` php +Form->create(); ?> + + + +Form->end(); ?> +``` + +生成された hidden 入力に属性を追加する必要がある場合は、 +`$secureAttributes` 引数を使用できます。 : + +``` php +echo $this->Form->end(['data-type' => 'hidden']); +``` + +出力結果: + +``` html +
    + + +
    +``` + +> [!NOTE] +> アプリケーションで `Cake\Controller\Component\FormProtectionComponent` +> を使用している場合は、必ずフォームを `end()` で終わらせてください。 + +## 単独のボタンと POST リンクの作成 + +### POST ボタンの作成 + +`method` Cake\\View\\Helper\\FormHelper::**postButton**(string $title, mixed $url, array $options = []) + +- `$title` - ボタンの見出しテキストを提供する必須の文字列。 + デフォルトでは HTML エンコードされません。 +- `$url` - 文字列や配列として提供される URL。 +- `$options` - [General Control Options](#general-control-options) 、特定のオプション(下記参照)と + 有効な HTML 属性を含むオプション配列。 + +デフォルトでは、POST で送信する `` 要素で囲まれた ` +
    + + + +
    + +``` + +このメソッドは `form` 要素を作成します。 +なので、開かれたフォームの中でこのメソッドを使用しないでください。 +代わりに `Cake\View\Helper\FormHelper::submit()` または +`Cake\View\Helper\FormHelper::button()` を使用して、 +開かれたフォームの中でボタンを作成してください。 + +### POST リンクの作成 + +`method` Cake\\View\\Helper\\FormHelper::**postLink**(string $title, mixed $url = null, array $options = []) + +- `$title` - `` タグに囲まれたテキストを提供する必須の文字列。 +- `$url` - オプション。フォームの URL (相対 URL 、または `http://` で始まる外部 URL) + を含む文字列または配列。 +- `$options` - [General Control Options](#general-control-options) 、特有のオプション(下記参照)と + 有効な HTML 属性を含むオプション配列。 + +HTML リンクを作成しますが、指定した方法 (デフォルトは POST)で URL にアクセスします。 +ブラウザーで有効にするには JavaScript が必要です。 + +**POST リンクのオプション** + +- `'data'` - hidden 入力に渡すキーと値の配列。 +- `'method'` - 使用するリクエスト方法。例えば、 `'delete'` をセットすると、 + HTTP/1.1 DELETE リクエストをシミュレートします。デフォルトは `'post'` です。 +- `'confirm'` - クリック時に表示される確認メッセージ。デフォルトは `null` です。 +- `'block'` - ビューブロック `'postLink'` へフォームの追加するために + このオプションに `true` をセットしたり、カスタムブロック名を指定します。 + デフォルトは `null` です。 +- また、 `postLink` メソッドは、 `link()` メソッドの有効なオプションを受け付けます。 + +このメソッドは `
    ` 要素を作成します。 +このメソッドを既存のフォームの中で使いたい場合は、 `block` オプションを使用して、 +新しいフォームがメインフォームの外部でレンダリング可能な +[ビューブロック](../../views#view-blocks) に設定されるようにする必要があります。 + +あなたが探しているものがフォームを送信するボタンであれば、代わりに +`Cake\View\Helper\FormHelper::button()` または +`Cake\View\Helper\FormHelper::submit()` を使用してください。 + +> [!NOTE] +> 開いているフォームの中に postLink を入れないように注意してください。 +> 代わりに、 `block` オプションを使ってフォームを +> [ビューブロック](../../views#view-blocks) にバッファリングしてください。 + +## FormHelper で使用するテンプレートのカスタマイズ + +CakePHP の多くのヘルパーと同じように、FormHelper は、 +作成する HTML をフォーマットするための文字列テンプレートを使用しています。 +既定のテンプレートは、合理的な既定値のセットを意図していますが、 +アプリケーションに合わせてテンプレートをカスタマイズする必要があるかもしれません。 + +ヘルパーが読み込まれたときにテンプレートを変更するには、コントローラーにヘルパーを含めるときに +`templates` オプションを設定することができます。 : + +``` php +// View クラスの中で +$this->loadHelper('Form', [ + 'templates' => 'app_form', +]); +``` + +これは、 **config/app_form.php** の中のタグを読み込みます。 +このファイルには、名前で索引付けされたテンプレートの配列が含まれている必要があります。 : + +``` text +// config/app_form.php の中で +return [ + 'inputContainer' => '
    {{content}}
    ', +]; +``` + +定義したテンプレートは、ヘルパーに含まれるデフォルトのテンプレートを置き換えます。 +置き換えられていないテンプレートは引き続きデフォルト値を使用します。 +`setTemplates()` メソッドを使って実行時にテンプレートを変更することもできます。 : + +``` php +$myTemplates = [ + 'inputContainer' => '
    {{content}}
    ', +]; +$this->Form->setTemplates($myTemplates); +// 3.4 より前 +$this->Form->templates($myTemplates); +``` + +> [!WARNING] +> パーセント記号 (`%`) を含むテンプレート文字列には特別な注意が必要です。 +> この文字の先頭に `%%` のようにもう一つパーセンテージを付ける必要があります。 +> なぜなら、内部的なテンプレートは `sprintf()` で使用されるためにコンパイルされているからです。 +> 例: `'
    {{content}}
    '` + +### テンプレート一覧 + +デフォルトのテンプレートのリスト、それらのデフォルトのフォーマット、そして期待される変数は +[FormHelper API ドキュメント](https://api.cakephp.org/4.x/class-Cake.View.Helper.FormHelper.html#%24_defaultConfig) +で見つけることができます。 + +#### 異なるカスタムコントロールコンテナーの使用 + +これらのテンプレートに加えて、 `control()` メソッドはコントロールコンテナーごとに異なるテンプレートを +使用しようとします。たとえば、datetime コントロールを作成する場合、 `datetimeContainer` +が存在する場合にはそれが使用されます。 +そのコンテナーがない場合、 `inputContainer` テンプレートが使用されます。 +例えば: + +``` php +// 独自の HTML で囲まれた radio を追加 +$this->Form->templates([ + 'radioContainer' => '
    {{content}}
    ' +]); + +// 独自の div で囲まれた radio セットを作成 +echo $this->Form->control('email_notifications', [ + 'options' => ['y', 'n'], + 'type' => 'radio' +]); +``` + +#### 異なるカスタムフォームグループの使用 + +コンテナーの制御と同様に、 `control()` メソッドはフォームグループごとに異なるテンプレートを +使用しようとします。フォームグループは、ラベルとコントロールの組み合わせです。 +例えば、radio 入力を作成する時、 `radioFormGroup` が存在する場合、それが使用されます。 +そのテンプレートが存在しない場合、デフォルトでは、ラベル&入力の各セットは、 +`formGroup` テンプレートを使用して描画されます。 + +例: + +``` php +// 独自の radio フォームグループを追加 +$this->Form->setTemplates([ + 'radioFormGroup' => '
    {{label}}{{input}}
    ' +]); +``` + +### テンプレートにテンプレート変数を追加 + +カスタムテンプレートにテンプレートプレースホルダを追加し、 +コントロールを生成するときにプレースホルダを設定することができます。 + +例: + +``` php +// help プレースホルダ付きでテンプレートを追加 +$this->Form->setTemplates([ + 'inputContainer' => '
    + {{content}} {{help}}
    ' +]); + +// help 変数を設定し入力を生成 +echo $this->Form->control('password', [ + 'templateVars' => ['help' => '少なくとも 8 文字の長さ。'] +]); +``` + +出力結果: + +``` html +
    + + + 少なくとも 8 文字の長さ。 +
    +``` + +### チェックボックスとラジオのラベル外への移動 + +デフォルトでは、CakePHP は、 `control()` で作成されたチェックボックスと +`control()` と `radio()` の両方で作成されたラジオボタンをラベル要素内でネストします。 +これにより、人気の CSS フレームワークとの統合に役立ちます。 +ラベルの外に checkbox/radio 入力を配置する必要がある場合は、 +テンプレートを変更することで行うことができます。 : + +``` php +$this->Form->setTemplates([ + 'nestingLabel' => '{{hidden}}{{input}}{{text}}', + 'formGroup' => '{{input}}{{label}}', +]); +``` + +これにより、ラジオボタンとチェックボックスがラベルの外側に描画されます。 + +## フォーム全体の生成 + +### 複数のコントロールの作成 + +`method` Cake\\View\\Helper\\FormHelper::**controls**(array $fields = [], array $options = []) + +- `$fields` - 生成するフィールドの配列。指定した各フィールドのカスタムタイプ、 + ラベル、その他のオプションを設定できます。 +- `$options` - オプション。オプションの配列。有効なキーは: + 1. `'fieldset'` - filedset を無効にするために `false` を設定してください。 + 空の配列を渡すと、fieldset は有効になります。 + HTML 属性として適用するパラメーターの配列を `fieldset` タグに渡すこともできます。 + 2. `legend` - `legend` のテキストをカスタマイズするための文字列。 + 生成された入力セットの legend を無効にするために `false` を設定してください。 + +`fieldset` で囲まれた指定された一連のコントロールセットを生成します。 +生成されたフィールドを含めることで指定できます。 : + +``` php +echo $this->Form->controls([ + 'name', + 'email' +]); +``` + +オプションを使用して legend のテキストをカスタマイズすることができます。 : + +``` php +echo $this->Form->controls($fields, ['legend' => 'Update news post']); +``` + +`$fields` パラメーターで追加のオプションを定義することによって、 +生成されたコントロールをカスタマイズすることができます。 : + +``` php +echo $this->Form->controls([ + 'name' => ['label' => 'カスタムラベル'] +]); +``` + +`$fields` をカスタマイズする場合、生成された legend/fieldset を制御するために +`$options` パラメーターを使用することができます。 + +例えば: + +``` php +echo $this->Form->controls( + [ + 'name' => ['label' => 'カスタムラベル'] + ], + ['legend' => 'Update your post'] +); +``` + +`fieldset` を無効にすると、 `legend` は出力されません。 + +### エンティティー全体のコントロールを作成 + +`method` Cake\\View\\Helper\\FormHelper::**allControls**(array $fields, $options = []) + +- `$fields` - オプション。生成するフィールドのカスタマイズ配列。 + カスタムタイプ、ラベル、その他のオプションを設定できます。 +- `$options` - オプション。オプションの配列。有効なキーは: + 1. `'fieldset'` - これに `false` を設定すると fieldset が無効になります。 + 空の場合、fieldset は有効になります。パラメーターの配列を渡すと、 `fieldset` の + HTML 属性として適用されます。 + 2. `legend` - `legend` テキストをカスタマイズするための文字列。 + これに `false` を設定すると、生成されたコントロールセットの `legend` が無効になります。 + +このメソッドは `controls()` と密接に関係していますが、 `$fields` 引数は +現在のトップレベルエンティティーの *全ての* フィールドにデフォルト設定されています。 +生成されたコントロールから特定のフィールドを除外するには、 `$fields` パラメーターで +`false` を設定します。 : + +``` php +echo $this->Form->allControls(['password' => false]); +// 3.4.0 より前の場合: +echo $this->Form->allInputs(['password' => false]); +``` + +## 関連データの入力を作成 + +関連するデータのフォームを作成するのは簡単で、エンティティーのデータ内のパスに密接に関連しています。 +次のテーブルリレーションを仮定します。 + +- Authors HasOne Profiles +- Authors HasMany Articles +- Articles HasMany Comments +- Articles BelongsTo Authors +- Articles BelongsToMany Tags + +アソシエーション付きで読み込まれた記事を編集していた場合、次のコントロールを作成できます。 : + +``` php +$this->Form->create($article); + +// Article コントロール +echo $this->Form->control('title'); + +// Author コントロール (belongsTo) +echo $this->Form->control('author.id'); +echo $this->Form->control('author.first_name'); +echo $this->Form->control('author.last_name'); + +// Author の profile (belongsTo + hasOne) +echo $this->Form->control('author.profile.id'); +echo $this->Form->control('author.profile.username'); + +// 別々の入力として、 +// Tags コントロール (belongsToMany) +echo $this->Form->control('tags.0.id'); +echo $this->Form->control('tags.0.name'); +echo $this->Form->control('tags.1.id'); +echo $this->Form->control('tags.1.name'); + +// 結合テーブルの入力 (articles_tags) +echo $this->Form->control('tags.0._joinData.starred'); +echo $this->Form->control('tags.1._joinData.starred'); + +// Comments コントロール (hasMany) +echo $this->Form->control('comments.0.id'); +echo $this->Form->control('comments.0.comment'); +echo $this->Form->control('comments.1.id'); +echo $this->Form->control('comments.1.comment'); +``` + +上記のコントロールは、コントローラー内の次のコードを使用して完成したエンティティーグラフに +マーシャリングすることができます。 : + +``` php +$article = $this->Articles->patchEntity($article, $this->request->getData(), [ + 'associated' => [ + 'Authors', + 'Authors.Profiles', + 'Tags', + 'Comments' + ] +]); +``` + +上記の例は、各エンティティーと結合データレコードに対して別々の入力を持つ、 +多数の関連するデータセットの拡張された例を示しています。 +また、多数の関連に属する複数選択入力を作成することもできます。 : + +``` php +// belongsToMany の複数選択要素 +// _joinData をサポートしません +echo $this->Form->control('tags._ids', [ + 'type' => 'select', + 'multiple' => true, + 'options' => $tagList, +]); +``` + +## 独自ウィジェットの追加 + +CakePHP を使うと、アプリケーションに独自のコントロールウィジェットを簡単に追加でき、 +他のコントロールタイプと同様に使用することができます。 +すべてのコアコントロールタイプはウィジェットとして実装されています。 +つまり、独自の実装でコアウィジェットを上書きすることができます。 + +### Widget クラスの構築 + +Widget クラスは、とても単純で必須のインターフェイスを持っています。 +これらは `Cake\View\Widget\WidgetInterface` を実装しなければなりません。 +このインターフェイスを実装するには、 `render(array $data)` メソッドと +`secureFields(array $data)` メソッドが必要です。 +`render()` メソッドは、ウィジェットを構築するためのデータ配列を受け取り、 +ウィジェットの HTML 文字列を返すことが期待されています。 +`secureFields()` メソッドは、同様にデータ配列を受け取り、 +このウィジェットで保護するフィールドのリストを含む配列を返すことが期待されています。 +CakePHP がウィジェットを構築している場合、最初の引数として `Cake\View\StringTemplate` +インスタンスを取得し、その後にあなたが定義した依存関係が続くことが期待できます。 +autocomplete ウィジェットを作成したい場合、以下を実行できます。 : + +``` php +namespace App\View\Widget; + +use Cake\View\Form\ContextInterface; +use Cake\View\Widget\WidgetInterface; + +class AutocompleteWidget implements WidgetInterface +{ + + protected $_templates; + + public function __construct($templates) + { + $this->_templates = $templates; + } + + /** + * Methods that render the widget. + * + * @param array $data The data to build an input with. + * @param \Cake\View\Form\ContextInterface $context The current form context. + * + * @return string + */ + public function render(array $data, ContextInterface $context): string + { + $data += [ + 'name' => '', + ]; + + return $this->_templates->format('autocomplete', [ + 'name' => $data['name'], + 'attrs' => $this->_templates->formatAttributes($data, ['name']) + ]); + } + + public function secureFields(array $data): array + { + return [$data['name']]; + } +} +``` + +明らかに、これは非常に簡単な例ですが、独自ウィジェットの構築方法を示しています。 + +### ウィジェットの使用 + +FormHelper を読み込むときや、 +`addWidget()` メソッドを使って独自のウィジェットを読み込むことができます。 +FormHelper を読み込むとき、ウィジェットは設定として定義されます。 : + +``` php +// View クラスの中で +$this->loadHelper('Form', [ + 'widgets' => [ + 'autocomplete' => ['Autocomplete'] + ] +]); +``` + +あなたのウィジェットが他のウィジェットを必要とする場合は、それらの依存関係を宣言することによって +FormHelper に取り込ませることができます。 : + +``` php +$this->loadHelper('Form', [ + 'widgets' => [ + 'autocomplete' => [ + 'App\View\Widget\AutocompleteWidget', + 'text', + 'label' + ] + ] +]); +``` + +上記の例では、 `autocomplete` ウィジェットは `text` と `label` ウィジェットに依存します。 +ウィジェットがビューにアクセスする必要がある場合は、 `_view` 'ウィジェット' を使用してください。 +autocomplete ウィジェットが作成されると、 `text` と `label` +の名前に関連するウィジェットオブジェクトが渡されます。 +`addWidget()` メソッドを使ってウィジェットを追加すると、次のようになります。 : + +``` php +// classname の使用。 +$this->Form->addWidget( + 'autocomplete', + ['Autocomplete', 'text', 'label'] +); + +// インスタンスの使用 - 依存関係を解決する必要があります。 +// 3.6.0 より前は、ウィジェットの取得に widgetRegistry() を使用。 +$autocomplete = new AutocompleteWidget( + $this->Form->getTemplater(), + $this->Form->getWidgetLocator()->get('text'), + $this->Form->getWidgetLocator()->get('label'), +); +$this->Form->addWidget('autocomplete', $autocomplete); +``` + +追加/置換されると、ウィジェットはコントロールの 'type' として使用できます。 : + +``` php +echo $this->Form->control('search', ['type' => 'autocomplete']); +``` + +これは、 `controls()` とまったく同じように `label` と囲い込む `div` +を持つ独自ウィジェットを作成します。 +あるいは、マジックメソッドを使用してコントロールウィジェットだけを作成することもできます。 : + +``` php +echo $this->Form->autocomplete('search', $options); +``` + +## FormProtectionComponent との連携 + +`Cake\Controller\Component\FormProtectionComponent` には、 +フォームをより安全で安全にするためのいくつかの機能があります。 +コントローラーに `FormProtectionComponent` を含めるだけで、 +フォームの改ざん防止機能が自動的に有効になります。 + +FormProtectionComponent を利用する際は、前述のようにフォームを閉じる際は、 +必ず `Cake\View\Helper\FormHelper::end()` を使う必要があります。 +これにより特別な `_Token` 入力が生成されます。 + +`method` Cake\\View\\Helper\\FormHelper::**unlockField**($name) + +- `$name` - オプション。ドット区切りのフィールド名。 + +`FormProtectionComponent` によるフィールドのハッシュ化が行われないようにフィールドのロックを +解除します。またこれにより、そのフィールドを JavaScript で操作できるようになります。 +`$name` には入力のためのエンティティーのプロパティー名を指定します。 : + +``` php +$this->Form->unlockField('id'); +``` + +`method` Cake\\View\\Helper\\FormHelper::**secure**(array $fields = [], array $secureAttributes = []) + +- `$fields` - オプション。ハッシュの生成に使用するフィールドの一覧を含む配列。 + 指定がない場合、 `$this->fields` が使用されます。 +- `$secureAttributes` - オプション。生成される hidden 入力要素の中に渡す + HTML 属性の配列。 + +フォームで使用されるフィールドに基づくセキュリティーハッシュをもつ +非表示の `input` フィールドを生成し、または、保護されたフォームが使用されていない場合は +空の文字列を生成します。 +`$secureAttributes` を設定した場合、これらの HTML 属性は、 +SecurityCompnent によって生成された非表示の input タグの中にマージされます。 +これは、 `'form'` のような HTML5 属性を設定するのに特に便利です。 diff --git a/docs/ja/views/helpers/html.md b/docs/ja/views/helpers/html.md new file mode 100644 index 0000000000..2e097df406 --- /dev/null +++ b/docs/ja/views/helpers/html.md @@ -0,0 +1,885 @@ +# Html + +`class` Cake\\View\\Helper\\**HtmlHelper**(View $view, array $config = []) + +CakePHP における HtmlHelper の役割は、 HTML に関連するオプションを より簡単、高速に作成し、 +より弾力的なものに変えることです。このヘルパーを使うことで、アプリケーションの足どりはより軽くなり、 +そしてドメインのルートが置かれている場所に関して、よりフレキシブルなものになるでしょう。 + +HtmlHelper にある多くのメソッドは `$attributes` という引数を持っています。 +これにより、いかなる追加属性もタグに付け加えることができます。 +ここでは、 `$attributes` パラメーターを使用する方法の例をいくつか紹介します。 + +``` html +欲しい属性: +配列パラメーター: ['class' => 'someClass'] + +欲しい属性: +配列パラメーター: ['name' => 'foo', 'value' => 'bar'] +``` + +## 整形式の要素を挿入 + +HtmlHelper の果たすもっとも重要なタスクは、適切に整形されたマークアップの生成です。 +このセクションでは、いくつかの HtmlHelper のメソッドと、その使用方法について説明します。 + +### 文字セットのタグを作成 + +`method` Cake\\View\\Helper\\HtmlHelper::**charset**($charset=null) + +文書の文字セットを指定する meta タグを作成するために使います。 +デフォルト値は UTF-8 です。 +使用例: + +``` php +echo $this->Html->charset(); +``` + +出力結果: + +``` html + +``` + +または、 : + +``` php +echo $this->Html->charset('ISO-8859-1'); +``` + +出力結果: + +``` html + +``` + +### CSS ファイルへのリンク + +`method` Cake\\View\\Helper\\HtmlHelper::**css**(mixed $path, array $options = []) + +CSS スタイルシートへのリンク(複数可)を作成します。 +`block` オプションが `true` に設定されている場合、link タグは `css` ブロックに追加されます。 +このブロックはドキュメントの head タグの中に出力することができます。 + +`block` オプションを使うと、link 要素をどのブロックに追加するかを制御することができます。 +デフォルトでは、 `css` ブロックに追加されます。 + +`$options` 配列のキー 'rel' が 'import' に設定されていると、スタイルシートがインポートされます。 + +パスが '/' で始まらない場合、CSS をインクルードするこのメソッドは、指定された CSS ファイルが +**webroot/css** ディレクトリー内にあることを前提としています。 : + +``` php +echo $this->Html->css('forms'); +``` + +出力結果: + +``` html + +``` + +最初のパラメーターは、複数のファイルを含むように配列することができます。 : + +``` php +echo $this->Html->css(['forms', 'tables', 'menu']); +``` + +出力結果: + +``` html + + + +``` + +`プラグイン記法` を使用して、すべての読み込まれたプラグインの +CSS ファイルをインクルードすることができます。 +**plugins/DebugKit/webroot/css/toolbar.css** を含めるために、以下を使用することができます。 : + +``` php +echo $this->Html->css('DebugKit.toolbar.css'); +``` + +読み込まれたプラグインと名前を共有する CSS ファイルをインクルードするには、次の操作を実行します。 +例えば、 `Blog` プラグインを持っていて、 +**webroot/css/Blog.common.css** をインクルードしたければ、 : + +``` php +echo $this->Html->css('Blog.common.css', ['plugin' => false]); +``` + +### プログラムによる CSS の作成 + +`method` Cake\\View\\Helper\\HtmlHelper::**style**(array $data, boolean $oneline = true) + +メソッドに渡した配列のキーと値から CSS のスタイル定義を作成します。 +特に動的な CSS の作成に便利です。 : + +``` php +echo $this->Html->style([ + 'background' => '#633', + 'border-bottom' => '1px solid #000', + 'padding' => '10px' +]); +``` + +出力結果: + +``` css +background:#633; border-bottom:1px solid #000; padding:10px; +``` + +### meta タグの作成 + +`method` Cake\\View\\Helper\\HtmlHelper::**meta**(string|array $type, string $url = null, array $options = []) + +このメソッドは、 RSS または Atom フィードや、 favicon といった外部リソースとリンクする際に便利です。 +css() と同様に、 `['block' => true]` のように \$attributes パラメーターの 'block' キーを +`true` に設定することで、このタグをインラインで表示するか +`meta` ブロックに追加するかどうかを指定することができます。 + +\$attributes のパラメーターを使って "type" 属性を設定するとき、 CakePHP では、 +いくつかのショートカットを用意しています。 + +| type | 変換後の値 | +|------|----------------------| +| html | text/html | +| rss | application/rss+xml | +| atom | application/atom+xml | +| icon | image/x-icon | + +``` php +Html->meta( + 'favicon.ico', + '/favicon.ico', + ['type' => 'icon'] +); +?> +// 出力結果 (改行を追加しています) +// 注意: このヘルパーのコードは、異なる rel 属性値を必要とする +// 新旧両方のブラウザーでアイコンをダウンロードさせるための +// 2つのタグを作成します。 + + + +Html->meta( + 'Comments', + '/comments/index.rss', + ['type' => 'rss'] +); +?> +// 出力結果 (改行を追加しています) + +``` + +ここのメソッドを使用して、meta keywords と description を追加することもできます。 +例: + +``` php +Html->meta( + 'keywords', + 'ここに meta キーワードを書き込む' +); +?> +// 出力結果 + + +Html->meta( + 'description', + 'ここに何か説明を書き込む' +); +?> +// 出力結果 + +``` + +定義済みの meta タグを作成するだけでなく、link 要素を作成することもできます。 : + +``` php +Html->meta([ + 'link' => 'http://example.com/manifest', + 'rel' => 'manifest' +]); +?> +// 出力結果 + +``` + +このように呼び出されたときに meta() に提供された属性は、生成された link タグに追加されます。 + +### DOCTYPE の作成 + +`method` Cake\\View\\Helper\\HtmlHelper::**docType**(string $type = 'html5') + +(X)HTML の DOCTYPE (文書型宣言) を返します。 +次の表に従って文書型を指定してください。 + +| type | 変換された値 | +|-----------------|------------------------| +| html4-strict | HTML 4.01 Strict | +| html4-trans | HTML 4.01 Transitional | +| html4-frame | HTML 4.01 Frameset | +| html5 (default) | HTML5 | +| xhtml-strict | XHTML 1.0 Strict | +| xhtml-trans | XHTML 1.0 Transitional | +| xhtml-frame | XHTML 1.0 Frameset | +| xhtml11 | XHTML 1.1 | + +``` php +echo $this->Html->docType(); +// 出力結果: + +echo $this->Html->docType('html4-trans'); +// 出力結果: +// +``` + +### 画像のリンク + +`method` Cake\\View\\Helper\\HtmlHelper::**image**(string $path, array $options = []) + +整形された画像タグを作成します。 +指定されたパスは **webroot/img/** と相対的でなければなりません。 : + +``` php +echo $this->Html->image('cake_logo.png', ['alt' => 'CakePHP']); +``` + +出力結果: + +``` html +CakePHP +``` + +画像リンクを作成するには、 `$attributes` の `url` オプションを使ってリンク先を指定します。 : + +``` php +echo $this->Html->image("recipes/6.jpg", [ + "alt" => "Brownies", + 'url' => ['controller' => 'Recipes', 'action' => 'view', 6] +]); +``` + +出力結果: + +``` html +
    + Brownies + +``` + +電子メールの中で画像を作成したり、画像への絶対パスが必要な場合は、 +`fullBase` オプションを使用することができます。 : + +``` php +echo $this->Html->image("logo.png", ['fullBase' => true]); +``` + +出力結果: + +``` html + +``` + +読み込まれたプラグインからの画像ファイルを `プラグイン記法` を使って組み込むことができます。 +**plugins/DebugKit/webroot/img/icon.png** を組み込むために、次のように使用することができます。 : + +``` php +echo $this->Html->image('DebugKit.icon.png'); +``` + +読み込まれたプラグインと名前を共有する画像ファイルを組み込むには、次のようにしてできます。 +例えば、 `Blog` プラグインを持っていて、\*\*webroot/img/Blog.icon.png\*\* を組み込みたければ、 : + +``` php +echo $this->Html->image('Blog.icon.png', ['plugin' => false]); +``` + +### リンクの作成 + +`method` Cake\\View\\Helper\\HtmlHelper::**link**(string $title, mixed $url = null, array $options = []) + +HTML リンクを作成するための多目的なメソッドです。 +要素の属性や `$title` をエスケープするかどうかを指定するには `$options` を使用してください。 : + +``` php +echo $this->Html->link( + 'Enter', + '/pages/home', + ['class' => 'button', 'target' => '_blank'] +); +``` + +出力結果: + +``` html +Enter +``` + +絶対URLにするためには `'_full'=>true` オプションを使用してください。 : + +``` php +echo $this->Html->link( + 'Dashboard', + ['controller' => 'Dashboards', 'action' => 'index', '_full' => true] +); +``` + +出力結果: + +``` html +Dashboard +``` + +オプションで `confirm` キーを指定すると、JavaScript の `confirm()` ダイアログを表示できます。 : + +``` php +echo $this->Html->link( + '削除', + ['controller' => 'Recipes', 'action' => 'delete', 6], + ['confirm' => 'このレシピを削除してよろしいですか?'] +); +``` + +出力結果: + +``` html + + 削除 + +``` + +`link()` でクエリー文字列を作成することもできます。 : + +``` php +echo $this->Html->link('View image', [ + 'controller' => 'Images', + 'action' => 'view', + 1, + '?' => ['height' => 400, 'width' => 500] +]); +``` + +出力結果: + +``` html +View image +``` + +`$title` の HTML 特殊文字は HTML エンティティーに変換されます。 +この変換を無効にするには、 `$options` 配列の escape オプションを `false` に設定します。 : + +``` php +echo $this->Html->link( + $this->Html->image("recipes/6.jpg", ["alt" => "Brownies"]), + "recipes/view/6", + ['escape' => false] +); +``` + +出力結果: + +``` html + + Brownies + +``` + +`escape` を `false` に設定すると、リンクの属性のエスケープも無効になります。 +`escapeTitle` オプションを使うと、属性ではなくタイトルのエスケープだけを無効にすることができます。 : + +``` php +echo $this->Html->link( + $this->Html->image('recipes/6.jpg', ['alt' => 'Brownies']), + 'recipes/view/6', + ['escapeTitle' => false, 'title' => 'hi "howdy"'] +); +``` + +出力結果: + +``` html + + Brownies + +``` + +また、さまざまな種類の URL の例については、 +`Cake\View\Helper\UrlHelper::build()` メソッドをチェックしてください。 + +### 動画と音声ファイルのリンク + +`method` Cake\\View\\Helper\\HtmlHelper::**media**(string|array $path, array $options) + +オプション: + +- `type` 生成するメディア要素のタイプ。有効な値は "audio" または "video" です。 + type が指定されていない場合、メディアの種類はファイルの MIME タイプに基づいて推測されます。 +- `text` video タグ内に含めるテキスト。 +- `pathPrefix` 相対 URL に使用するパスのプレフィックス。デフォルトは 'files/' です。 +- `fullBase` 指定されている場合、src 属性はドメイン名を含む完全なアドレスを取得します。 + +整形された audio/video タグを返します。 + +``` php +Html->media('audio.mp3') ?> + +// 出力結果 + + +Html->media('video.mp4', [ + 'fullBase' => true, + 'text' => 'Fallback text' +]) ?> + +// 出力結果 + + +Html->media( + ['video.mp4', ['src' => 'video.ogg', 'type' => "video/ogg; codecs='theora, vorbis'"]], + ['autoplay'] +) ?> + +// 出力結果 + +``` + +### JavaScript ファイルへのリンク + +`method` Cake\\View\\Helper\\HtmlHelper::**script**(mixed $url, mixed $options) + +ローカルファイルまたはリモート URL のいずれかのスクリプトファイルをインクルードします。 + +デフォルトでは、script タグは、文書のインラインに追加されます。 +`$options['block']` を `true` に設定することで、これを上書きする場合は、 +script タグは代わりに文書内の他の場所で出力できる `script` ブロックに追加されます。 +どのブロック名が使用されているかを書き換えたい場合は、 +`$options['block']` を設定することで可能になります。 + +`$options['once']` は、このスクリプトをリクエストごとに1回または複数回含めるかどうかを制御します。 +デフォルトは `true` です。 + +\$options を使用して、生成された script タグに追加のプロパティーを設定することができます。 +script タグの配列を使用すると、生成されたすべての script タグに属性が適用されます。 + +この JavaScript ファイルをインクルードするメソッドは、指定された JavaScript ファイルが +**webroot/js** ディレクトリー内にあることを前提としています。 : + +``` php +echo $this->Html->script('scripts'); +``` + +出力結果: + +``` html + +``` + +**webroot/js** にないファイルを絶対パスでリンクすることもできます。 : + +``` php +echo $this->Html->script('/otherdir/script_file'); +``` + +また、リモートの URL にリンクすることができます。 : + +``` php +echo $this->Html->script('https://code.jquery.com/jquery.min.js'); +``` + +出力結果: + +``` html + +``` + +最初のパラメーターは、複数のファイルをインクルードするために配列することができます。 : + +``` php +echo $this->Html->script(['jquery', 'wysiwyg', 'scripts']); +``` + +出力結果: + +``` html + + + +``` + +`block` オプションを使って特定のブロックに script タグを追加することができます。 : + +``` php +echo $this->Html->script('wysiwyg', ['block' => 'scriptBottom']); +``` + +レイアウトの中で 'scriptBottom' に追加されたすべての script タグを出力することができます。 : + +``` php +echo $this->fetch('scriptBottom'); +``` + +`プラグイン記法` を使用して、すべての読み込まれたプラグインの script ファイルを +インクルードすることができます。 +**plugins/DebugKit/webroot/js/toolbar.js** をインクルードするために、次を使用できます。 : + +``` php +echo $this->Html->script('DebugKit.toolbar.js'); +``` + +読み込まれたプラグインと名前を共有するスクリプトファイルをインクルードするには、次の操作を実行します。 +例えば、 `Blog` プラグインを持っていて、 **webroot/js/Blog.plugins.js** をインクルードしたければ、 : + +``` php +echo $this->Html->script('Blog.plugins.js', ['plugin' => false]); +``` + +### インライン Javascript ブロックの作成 + +`method` Cake\\View\\Helper\\HtmlHelper::**scriptBlock**(string $code, array $options = []) + +PHP ビューコードから Javascript ブロックを生成するには、スクリプトブロックメソッドの1つを使用できます。 +スクリプトは、その場所で出力することも、ブロックにバッファリングすることもできます。 : + +``` php +// defer 属性付きで、一度に全てのスクリプトブロックを定義 +$this->Html->scriptBlock('alert("hi")', ['defer' => true]); + +// 後で出力するスクリプトブロックをバッファリング +$this->Html->scriptBlock('alert("hi")', ['block' => true]); +``` + +`method` Cake\\View\\Helper\\HtmlHelper::**scriptStart**(array $options = []) + +`method` Cake\\View\\Helper\\HtmlHelper::**scriptEnd**() + +`scriptStart()` メソッドを使って、 `' +]); +``` + +直接 templater を使うことでテンプレートを含む設定ファイルを読み込むことができます。 : + +``` php +// テンプレートを持つ設定ファイルを読み込む。 +$this->Html->templater()->load('my_tags'); +``` + +テンプレートのファイルを読み込む場合、ファイルは次のようになります。 : + +``` php + '' +]; +``` + +> [!WARNING] +> パーセント記号 (`%`) を含むテンプレート文字列には特別な注意が必要です。 +> この文字の先頭に `%%` のようにもう一つパーセンテージを付ける必要があります。 +> なぜなら、内部的なテンプレートは `sprintf()` で使用されるためにコンパイルされているからです。 +> 例: `
    {{content}}
    ` + +## HtmlHelper でパンくずリストを作成 + +`method` Cake\\View\\Helper\\HtmlHelper::**addCrumb**(string $name, string $link = null, mixed $options = null) + +`method` Cake\\View\\Helper\\HtmlHelper::**getCrumbs**(string $separator = '»', string $startText = false) + +`method` Cake\\View\\Helper\\HtmlHelper::**getCrumbList**(array $options = [], $startText = false) + +多くのアプリケーションでは、エンドユーザーのナビゲーションを容易にするためのパンくずリストがあります。 +HtmlHelper の助けを借りて、アプリでパンくずリストを作成することができます。 +パンくずを作るには、まずレイアウトテンプレートで次のようにします。 : + +``` php +echo $this->Html->getCrumbs(' > ', 'Home'); +``` + +`$startText` のオプションは配列も受け付けます。 +これにより、生成された最初のリンクへのさらなる制御を可能にします。 : + +``` php +echo $this->Html->getCrumbs(' > ', [ + 'text' => $this->Html->image('home.png'), + 'url' => ['controller' => 'Pages', 'action' => 'display', 'home'], + 'escape' => false +]); +``` + +`text` や `url` 以外のキーは `$options` パラメーターとして +`~HtmlHelper::link()` に渡されます。 + +今、ビューでは、各ページ上のパンくずリストを開始するために以下を追加しようと考えています: + +``` php +$this->Html->addCrumb('Users', '/users'); +$this->Html->addCrumb('Add User', ['controller' => 'Users', 'action' => 'add']); +``` + +これは、 `getCrumbs` が追加されたレイアウトで、 "\*\*Home \> Users \> Add User\*\*" の出力を追加します。 + +また、HTML リストの中で整形されたパンくずを取得することもできます。 : + +``` php +echo $this->Html->getCrumbList(); +``` + +オプションとして、 `
      ` (順不同リスト) に収まる `class` のような通常の +HTML パラメーターを使用することができ、 特別なオプションを持ちます。 +(`li` 要素の間にある) `separator` 、 `irstClass` 及び `lastClass` などです。 : + +``` php +echo $this->Html->getCrumbList( + [ + 'firstClass' => false, + 'lastClass' => 'active', + 'class' => 'breadcrumb' + ], + 'Home' +); +``` + +このメソッドはリストとその要素を生成するために `Cake\View\Helper\HtmlHelper::tag()` +を使います。 `Cake\View\Helper\HtmlHelper::getCrumbs()` と同様に動作するので、 +全てのパンくずに追加されたオプションを使用できます。 `$startText` パラメーターを使って、 +最初のパンくずのリンクやテキストを提供することができます。 +これは、常にルートのリンクを含めたい場合に便利です。このオプションは +`Cake\View\Helper\HtmlHelper::getCrumbs()` の +`$startText` オプションと同じ働きをします。 diff --git a/docs/ja/views/helpers/number.md b/docs/ja/views/helpers/number.md new file mode 100644 index 0000000000..53c242ee2d --- /dev/null +++ b/docs/ja/views/helpers/number.md @@ -0,0 +1,11 @@ +# Number + +`class` Cake\\View\\Helper\\**NumberHelper**(View $view, array $config = []) + +NumberHelper は、ビューの中で一般的なフォーマットを使用して数値の表示を可能にする、便利なメソッドで構成されています。 +これらのメソッドは、通貨、パーセンテージ、データサイズを整形したり、数値を特定精度へ整形する方法と、そして数値を整形するさらなる柔軟性も提供します。 + + + +> [!WARNING] +> 全てのシンボルは UTF-8 です。 diff --git a/docs/ja/views/helpers/paginator.md b/docs/ja/views/helpers/paginator.md new file mode 100644 index 0000000000..24c41f90b1 --- /dev/null +++ b/docs/ja/views/helpers/paginator.md @@ -0,0 +1,480 @@ +# Paginator + +`class` Cake\\View\\Helper\\**PaginatorHelper**(View $view, array $config = []) + +PaginatorHelper は、ページ番号や次ページへ/前ページへのリンクといった、 +ページ制御関連の出力をするために使用されます。これは `PaginatorComponent` +と連携して機能します。 + +ページ制御を組み込んだデータセットの作成や、ページ制御関連のクエリーについての詳細は +[ページネーション](../../controllers/pagination) を参照してください。 + +## PaginatorHelper テンプレート + +内部的に PaginatorHelper は一連のシンプルな HTML テンプレートを使用して +マークアップを生成します。これらのテンプレートを変更して、PaginatorHelper +によって生成された HTML をカスタマイズすることができます。 + +テンプレートは `{{var}}` スタイルのプレースホルダを使います。 `{{}}` +のまわりに空白を入れないことが重要です。そうしないと置き換えが機能しません。 + +### ファイルからテンプレートをロードする + +コントローラーに PaginatorHelper を追加するとき、'templates' +設定を定義して読み込むテンプレートファイルを定義することができます。 +これにより、複数のテンプレートをカスタマイズし、コードを DRY に保つことができます。 : + +``` php +// AppView.php の中で +public function initialize() +{ + ... + $this->loadHelper('Paginator', ['templates' => 'paginator-templates']); +} +``` + +これは **config/paginator-templates.php** にあるファイルをロードします。 +ファイルの外観は以下の例を参照してください。 `プラグイン記法` +を使ってプラグインからテンプレートをロードすることもできます。 : + +``` php +// AppView.php の中で +public function initialize(): void +{ + ... + $this->loadHelper('Paginator', ['templates' => 'MyPlugin.paginator-templates']); +} +``` + +テンプレートが、主となるアプリケーションのものでもプラグインのものでも、 +テンプレートファイルは次のようになります。 : + +``` text +return [ + 'number' => '{{text}}', +]; +``` + +### 実行時にテンプレートを変更する + +`method` Cake\\View\\Helper\\PaginatorHelper::**setTemplates**($templates) + +このメソッドを使用すると、実行時に PaginatorHelper で使用されるテンプレートを変更できます。 +これは、特定のメソッド呼び出しのテンプレートをカスタマイズする場合に便利です。 : + +``` php +// 現在のテンプレート値を読み込みます +$result = $this->Paginator->getTemplates('number'); + +// テンプレートを変更します +$this->Paginator->setTemplates([ + 'number' => '{{text}}' +]); +``` + +> [!WARNING] +> パーセント記号 (`%`) を含むテンプレート文字列には特別な注意が必要です。 +> この文字の前に `%%` のように別のパーセンテージを付ける必要があります。 +> なぜなら、内部的なテンプレートは `sprintf()` で使われるように変換されているからです。 +> 例: '\
      {{content}}\' + +### テンプレート名 + +PaginatorHelper は次のテンプレートを使用します。 + +- `nextActive` next() によって生成されたリンクの有効な状態。 +- `nextDisabled` next() の無効な状態。 +- `prevActive` prev() によって生成されたリンクの有効な状態。 +- `prevDisabled` prev() の無効な状態。 +- `counterRange` counter() で format == range の場合のテンプレート。 +- `counterPages` counter() で format == pages の場合のテンプレート。 +- `first` first() によって生成されたリンクに使用されるテンプレート。 +- `last` last() によって生成されたリンクに使用されるテンプレート。 +- `number` numbers() によって生成されたリンクに使用されるテンプレート。 +- `current` 現在のページで使用されているテンプレート。 +- `ellipsis` numbers() によって生成された省略記号に使用されるテンプレート。 +- `sort` 方向のないソートリンクのテンプレート。 +- `sortAsc` 昇順のソートリンクのテンプレート。 +- `sortDesc` 降順のソートリンクのテンプレート。 + +## ソートリンクの作成 + +`method` Cake\\View\\Helper\\PaginatorHelper::**sort**($key, $title = null, $options = []) + +ソート用のリンクを作成します。並べ替えと方向のクエリー文字列パラメーターをセットします。 +リンクはデフォルトでは昇順にソートされます。 `sort()` で生成されたリンクは +最初のクリックの後、 自動的に方向を切り替えます。 +結果セットが指定されたキーにより ‘asc’ にソートされている場合、返されたリンクは +‘desc’ でソートします。 + +`$options` で使えるキー: + +- `escape` コンテンツ内の HTML エンティティーをエンコードするかどうか。 デフォルトは + `true` 。 +- `model` 使用するモデル。デフォルトは `PaginatorHelper::defaultModel()` 。 +- `direction` リンクが非アクティブの時に適用するデフォルトのソート順。 +- `lock` ソート順をロック (固定) するかどうか。 デフォルトのソート順にのみ適用されます。 + デフォルトは `false` 。 + +ここで複数の投稿 (*post*) をページ制御していて、今1ページ目にいるとすると: + +``` php +echo $this->Paginator->sort('user_id'); +``` + +出力結果: + +``` html +User Id +``` + +title パラメーターを使って、リンクに付けるカスタムテキストを作ることもできます。 : + +``` php +echo $this->Paginator->sort('user_id', 'User account'); +``` + +出力結果: + +``` html +User account +``` + +リンクに対して HTML のような画像を使っている場合は、エスケープを off にする必要があります。 : + +``` php +echo $this->Paginator->sort( + 'user_id', + 'User account', + ['escape' => false] +); +``` + +出力結果: + +``` html +User account +``` + +direction オプションでリンクのデフォルトのソート順を設定できます。 +一度リンクがアクティブになると、通常のように自動的にソート順が切り替わります。 : + +``` php +echo $this->Paginator->sort('user_id', null, ['direction' => 'desc']); +``` + +出力結果: + +``` html +User Id +``` + +lock オプションでソート順を指定された順に固定できます。 : + +``` php +echo $this->Paginator->sort('user_id', null, ['direction' => 'asc', 'lock' => true]); +``` + +`method` Cake\\View\\Helper\\PaginatorHelper::**sortDir**(string $model = null, mixed $options = []) + +`method` Cake\\View\\Helper\\PaginatorHelper::**sortKey**(string $model = null, mixed $options = []) + +## ページ番号リンクの作成 + +`method` Cake\\View\\Helper\\PaginatorHelper::**numbers**($options = []) + +ページ番号の並びを返します。モジュールを使って、 +現在のページの前後 何ページまでを表示するのかを決めます。 +デフォルトでは、 現在のページのいずれかの側で最大8個までのリンクが作られます。 +ただし存在しないページは作られません。現在のページもリンクにはなりません。 + +サポートされているオプションは以下の通りです。 + +- `before` 数字の前に挿入されるコンテンツ + +- `after` 数字の後に挿入されるコンテンツ + +- `model` その番号を作る元になるモデル。デフォルトは + `PaginatorHelper::defaultModel()` + +- `modulus` 現在のページの両側に含める数字の数。 + デフォルトは 8。 + +- `first` 先頭ページへのリンクを生成したい場合、先頭から何ページ分を生成するかを整数で指定します。 + デフォルトは `false` です。文字列を指定すると、その文字列をタイトルの値として先頭ページへのリンクを生成します。 : + + ``` php + echo $this->Paginator->numbers(['first' => 'First page']); + ``` + +- `last` 最終ページヘのリンクを生成したい場合、最後から何ページ分を生成するかを整数で定義します。 + デフォルトは `false` です。 `first` オプションと 同じロジックに従います。 + `~PaginatorHelper::last()` メソッドを使って別々に定義することも可能です。 + +このメソッドを使えば出力の多くをカスタマイズできますが、 +一切パラメーターを指定せずにコールしても問題ありません。 : + +``` php +echo $this->Paginator->numbers(); +``` + +first と last オプションを使って先頭ページと最終ページへのリンクを作れます。 +以下の例ではページ制御された結果セットの中の、 +先頭から2ページと末尾から2ページのリンクを含むページリンクの並びを生成します。 : + +``` php +echo $this->Paginator->numbers(['first' => 2, 'last' => 2]); +``` + +## ジャンプ用リンクの作成 + +特定のページ番号に直接行けるリンクを作れるだけでなく、現在の直前や直後、 +および先頭や末尾へのリンクを作りたくなる場合もあるでしょう。 + +`method` Cake\\View\\Helper\\PaginatorHelper::**prev**($title = '<< Previous', $options = []) + +`method` Cake\\View\\Helper\\PaginatorHelper::**next**($title = 'Next >>', $options = []) + +`method` Cake\\View\\Helper\\PaginatorHelper::**first**($first = '<< first', $options = []) + +`method` Cake\\View\\Helper\\PaginatorHelper::**last**($last = 'last >>', $options = []) + +## ヘッダーリンクタグの作成 + +PaginatorHelper を使用すると、ページの `` 要素に改行タグを作成できます。 : + +``` php +// 現在のモデルの次ページと前ページのリンクを作成する。 +echo $this->Paginator->meta(); + +// 現在のモデルの次ページと前ページ、先頭ページと最終ページのリンクを作成する。 +echo $this->Paginator->meta(['first' => true, 'last' => true]); +``` + +## ページ制御状態の確認 + +`method` Cake\\View\\Helper\\PaginatorHelper::**current**(string $model = null) + +`method` Cake\\View\\Helper\\PaginatorHelper::**hasNext**(string $model = null) + +`method` Cake\\View\\Helper\\PaginatorHelper::**hasPrev**(string $model = null) + +`method` Cake\\View\\Helper\\PaginatorHelper::**hasPage**(int $page = 1, string $model = null) + +`method` Cake\\View\\Helper\\PaginatorHelper::**total**(string $model = null) + +## ページカウンターの生成 + +`method` Cake\\View\\Helper\\PaginatorHelper::**counter**($options = []) + +ページ制御された結果セットのためのカウンター文字列を返します。 +与えられた書式文字列と多くのオプションを使って、ページ制御された 結果セットの中の位置を表す、 +ローカライズされたアプリケーション固有の文字列を生成することができます。 + +`counter()` には多くのオプションがあります。 サポートされているのは以下のものです。 + +- `format` カウンターの書式。サポートされている書式は 'range', 'pages' およびカスタムです。 + pages のデフォルトは '1 of 10' のような出力です。 + カスタムモードでは与えられた文字列がパースされ、トークンが実際の値に置き換えられます。 + 利用できるトークンは以下の通りです。 + + - `{{page}}` - 表示された現在のページ + - `{{pages}}` - 総ページ数 + - `{{current}}` - 表示されようとしている現在のレコード数 + - `{{count}}` - 結果セットの中の全レコード数 + - `{{start}}` - 表示されようとしている先頭のレコード数 + - `{{end}}` - 表示されようとしている最終のレコード数 + - `{{model}}` - モデル名を複数系にして読みやすい書式にしたもの。 + あなたのモデルが 'RecipePage' であれば、 `{{model}}` は 'recipe pages' になります。 + + counter メソッドに対して利用できるトークンを使って、文字列だけを与えることもできます。 + たとえば以下のようにできます。 : + + ``` php + echo $this->Paginator->counter( + '{{page}} / {{pages}} ページ, {{current}} 件目 / 全 {{count}} 件, + 開始レコード番号 {{start}}, 終了レコード番号 {{end}}' + ); + ``` + + 'format' を range に設定すると '1 - 3 of 13' のように出力します。 : + + ``` php + echo $this->Paginator->counter([ + 'format' => 'range' + ]); + ``` + +- `model` ページ制御する対象のモデル。デフォルトは + `PaginatorHelper::defaultModel()` 。 + これは 'format' オプションのカスタム文字列と組み合わせて使われます。 + +## ページ制御 URL の生成 + +`method` Cake\\View\\Helper\\PaginatorHelper::**generateUrl**(array $options = [], $model = null, $full = false) + +デフォルトでは、非標準的なコンテキスト(JavaScript など)で使用する +完全なページ制御 URL 文字列を返します。 : + +``` php +echo $this->Paginator->generateUrl(['sort' => 'title']); +``` + +## 制限セレクトボックスコントロールの作成 + +`method` Cake\\View\\Helper\\PaginatorHelper::**limitControl**(array $limits = [], $default = null, array $options = []) + +`limit` クエリーパラメーターを変更するドロップダウンコントロールを作成します。 : + +``` php +// デフォルトを使用 +echo $this->Paginator->limitControl(); + +// 必要な limit オプションを定義 +echo $this->Paginator->limitControl([25 => 25, 50 => 50]); + +// カスタム制限と選択されたオプションの設定 +echo $this->Paginator->limitControl([25 => 25, 50 => 50], $user->perPage); +``` + +生成されたフォームやコントロールは、変更時に自動的に送信されます。 + +## ページ制御オプションの設定 + +`method` Cake\\View\\Helper\\PaginatorHelper::**options**($options = []) + +PaginatorHelperのすべてのオプションを設定します。サポートされているオプションは以下の通りです。: + +- `url` ページ制御アクションの URL 。 ‘url’ にはサブオプションがいくつかあります。: + + - `sort` レコードをソートする際のキー。 + - `direction` ソート順。デフォルトは ‘ASC’ です。 + - `page` 表示するページ番号。 + + 上記の例で出てきたオプションは、特定のページやソート順を強制するのに使用できます。 + このヘルパーで生成された URL に対して、追加的な URL コンテンツを追加できます。 : + + ``` php + $this->Paginator->options([ + 'url' => [ + 'sort' => 'email', + 'direction' => 'desc', + 'page' => 6, + 'lang' => 'en' + ] + ]); + ``` + + 上記の例では、ヘルパーが生成するリンク全てに経路パラメーター `en` を追加します。 + また、指定されたソートキー、ソート順、ページ番号で リンクを生成します。 + デフォルトでは、 PaginatorHelper は現在のパスとクエリーパラメーターすべてをマージします。 + +- `escape` リンクの title フィールドを HTML エスケープするかどうかを指定します。 + デフォルトは `true` です。 + +- `model` ページ制御対象のモデル名。デフォルトは + `PaginatorHelper::defaultModel()` です。 + +## 使用例 + +ユーザーに対してどのようにレコードを表示するのかは自由に決められますが、一般的には、HTML テーブルにより行われます。 +以下の例ではテーブルレイアウトを前提としていますが、ビューの中で利用可能な PaginatorHelper が、 +そのように機能を制限されているわけではありません。 + +詳細は API の中の [PaginatorHelper](https://api.cakephp.org/4.x/class-Cake.View.Helper.PaginatorHelper.html) +を参照してください。なお、前述のように、PaginatorHelper には、テーブルの列ヘッダーに統合できるソート機能もあります。 + +``` php + + + + + + + + + + + + +
      Paginator->sort('id', 'ID') ?>Paginator->sort('title', 'Title') ?>
      id ?> title) ?>
      +``` + +`PaginatorHelper` の `sort()` メソッドから出力されたリンクにより、 +ユーザーはテーブルのヘッダーをクリックして、指定されたフィールドによるデータのソートを切り替えることができます。 + +アソシエーションに基づいて列をソートすることもできます。 + +``` php + + + + + + + + + + + +
      Paginator->sort('title', 'Title') ?>Paginator->sort('Authors.name', 'Author') ?>
      title) ?> name) ?>
      +``` + +> [!NOTE] +> 関連するモデルでカラムをソートするには、 `PaginationComponent::paginate` +> プロパティーで設定する必要があります。上記の例を使用すると、 +> ページ制御を処理するコントローラーは、次のように `sortableFields` キーを設定する必要があります。 +> +> ``` php +> $this->paginate = [ +> 'sortableFields' => [ +> 'Posts.title', +> 'Authors.name', +> ], +> ]; +> ``` +> +> `sortableFields` オプションの使い方の詳細については、 +> [Control Which Fields Used For Ordering](../../controllers/pagination#control-which-fields-used-for-ordering) をご覧ください。 + +ビューにおけるページ制御の表示に関する最後のネタは、 +PaginationHelper によって提供されるページナビゲーションの追加です。 : + +``` php +// ページ番号を表示する +Paginator->numbers() ?> + +// 次ページと前ページのリンクを表示する +Paginator->prev('« Previous') ?> +Paginator->next('Next »') ?> + +// 現在のページ番号 / 全ページ数 を表示する +Paginator->counter() ?> +``` + +counter() メソッドによる文章出力は、特殊なマーカーを使用してカスタマイズできます。 : + +``` php +Paginator->counter([ + 'format' => 'ページ {{page}} / {{pages}}, 全 {{count}} レコード中の + {{current}} レコードを表示中, 先頭レコード {{start}}, 末尾 {{end}}' +]) ?> +``` + +## 複数の結果の改ページ + +[複数のクエリーをページ制御する](../../controllers/pagination#paginating-multiple-queries) 場合は、 +ページ設定関連要素を生成するときに `model` オプションを設定する必要があります。 +`PaginatorHelper` のすべてのメソッド呼び出しで `model` オプションを使用するか、 +`options()` を使用してデフォルトモデルを設定することができます。 : + +``` php +// モデルオプションを渡す +echo $this->Paginator->sort('title', ['model' => 'Articles']); + +// デフォルトモデルを設定する +$this->Paginator->options(['defaultModel' => 'Articles']); +echo $this->Paginator->sort('title'); +``` + +`model` オプションを使用すると、 `PaginatorHelper` はクエリーがページ制御されたときに定義された `scope` を自動的に使用します。 diff --git a/docs/ja/views/helpers/text.md b/docs/ja/views/helpers/text.md new file mode 100644 index 0000000000..463a860907 --- /dev/null +++ b/docs/ja/views/helpers/text.md @@ -0,0 +1,71 @@ +# Text + +`class` Cake\\View\\Helper\\**TextHelper**(View $view, array $config = []) + +TextHelper には、ビュー内のテキストをより使いやすく見やすくするためのメソッドが含まれています。 +リンクの有効化、URLのフォーマット、選択した単語やフレーズの周囲のテキストの抜粋を作成、 +テキストブロック内のキーワードのハイライト、 長いテキストの綺麗な切り詰めなどを支援します。 + +## メールアドレスのリンク化 + +`method` Cake\\View\\Helper\\TextHelper::**autoLinkEmails**(string $text, array $options = []) + +`$options` で定義されたオプションに従い、 `$text` に整形されたメールアドレスのリンクを追加します。( +`HtmlHelper::link()` を参照) : + +``` php +$myText = 'For more information regarding our world-famous ' . + 'pastries and desserts, contact info@example.com'; +$linkedText = $this->Text->autoLinkEmails($myText); +``` + +出力: + +``` text +For more information regarding our world-famous pastries and desserts, +contact info@example.com +``` + +このメソッドは自動的に入力をエスケープします。これを無効にする必要がある場合には、 `escape` オプションを使用します。 + +## URLのリンク化 + +`method` Cake\\View\\Helper\\TextHelper::**autoLinkUrls**(string $text, array $options = []) + +`autoLinkEmails()` と同様ですが、https, http, ftp, nntp から始まる文字列を見つけ、適切にリンクします。 + +このメソッドは自動的に入力をエスケープします。これを無効にする必要がある場合には、 `escape` オプションを使用します。 + +## URLとメールアドレス両方のリンク化 + +`method` Cake\\View\\Helper\\TextHelper::**autoLink**(string $text, array $options = []) + +与えられた `$text` に対して、 `autoLinkUrls()` と `autoLinkEmails()` 両方の機能を実行します。 +全てのURLとメールアドレスは与えられた `$options` を考慮して適切にリンクされます。 + +このメソッドは自動的に入力をエスケープします。これを無効にする必要がある場合には、 `escape` オプションを使用します。 + +## テキストの段落化 + +`method` Cake\\View\\Helper\\TextHelper::**autoParagraph**(string $text) + +2行改行されている場合は適切にテキストを\で囲み、 +1行改行には\を追加します。: + +``` php +$myText = 'For more information +regarding our world-famous pastries and desserts. + +contact info@example.com'; +$formattedText = $this->Text->autoParagraph($myText); +``` + +出力: + +``` html +

      For more information
      +regarding our world-famous pastries and desserts.

      +

      contact info@example.com

      +``` + + diff --git a/docs/ja/views/helpers/time.md b/docs/ja/views/helpers/time.md new file mode 100644 index 0000000000..c684633abf --- /dev/null +++ b/docs/ja/views/helpers/time.md @@ -0,0 +1,45 @@ +# Time + +`class` Cake\\View\\Helper\\**TimeHelper**(View $view, array $config = []) + +TimeHelper には時間に関係のある情報を手早く処理するための2つの役割があります。: + +1. 時間文字列のフォーマットを整えることができます。 +2. 時間の判定ができます。 + +## ヘルパーの使い方 + +TimeHelper の一般的な使い道は、ユーザーのタイムゾーンに合わせて日付と時刻を +補正することです。それでは掲示板を例にとりましょう。あなたの掲示板には +多くのユーザーがいて、世界中のどの地域からでもいつでもメッセージを投稿できます。 +時間を管理する簡単な方法は、すべての日付と時刻を GMT+0 または UTC +として保存することです。 +アプリケーションのタイムゾーンを確実に GMT+0 に設定するために +**config/bootstrap.php** の `date_default_timezone_set('UTC');` +という記述のコメントアウトを解除してください。 + +次にタイムゾーンのフィールドをユーザーのテーブルに追加して、 +ユーザーがタイムゾーンを設定できるように 必要な修正を加えます。 +これでログインしているユーザーのタイムゾーンが分かるようになり、 +TimeHelper を使って投稿日時を補正することができます。 : + +``` php +echo $this->Time->format( + $post->created, + \IntlDateFormatter::FULL, + false, + $user->time_zone +); +// GMT+0 のユーザーには 'Saturday, August 22, 2011 at 11:53:00 PM GMT' と +// 表示されます。 +// GMT-8 のユーザーには 'Saturday, August 22, 2011 at 03:53 PM GMT-8:00' と +// 表示されます。 +``` + +TimeHelper のほとんどの機能は、古いバージョンの CakePHP からアップグレードしている +アプリケーションのための下位互換性のあるインターフェースとして意図されています。 +なぜなら、 ORM は `timestamp` 型と `datetime` 型の列ごとに +`Cake\I18n\Time` インスタンスを返すので、そのメソッドを使用して +ほとんどのタスクを実行できるからです。例えば、受け入れられた書式指定文字列について +読むには、 [Cake\I18n\Time::i18nFormat()](https://api.cakephp.org/4.x/class-Cake.I18n.Time.html#i18nFormat()) +メソッドを見てください。 diff --git a/docs/ja/views/helpers/url.md b/docs/ja/views/helpers/url.md new file mode 100644 index 0000000000..8096a2b614 --- /dev/null +++ b/docs/ja/views/helpers/url.md @@ -0,0 +1,141 @@ +# Url + +`class` Cake\\View\\Helper\\**UrlHelper**(View $view, array $config = []) + +UrlHelper は他のヘルパーから URL を生成することが容易になります。 +アプリケーションのヘルパーでコアヘルパーをオーバーライドすることによって URL の生成方法を +カスタマイズする単一の場所を提供します。このやり方は [Aliasing Helpers](../../views/helpers#aliasing-helpers) +セクションをご覧ください。 + +## URL の生成 + +`method` Cake\\View\\Helper\\UrlHelper::**build**(mixed $url = null, array $options = []) + +コントローラーとアクションの組み合わせを指定することで URL を生成して返します。 +`$url` が空の場合、 `REQUEST_URI` を返します。そうでない場合、 +コントローラーとアクションの組み合わせで URL を生成します。 +`full` が `true` の場合、結果がフルベース URL で返されます。 : + +``` php +echo $this->Url->build([ + 'controller' => 'Posts', + 'action' => 'view', + 'bar', +]); + +// 出力結果 +/posts/view/bar +``` + +さらにいつかの使用例は、こちら: + +拡張子つきの URL: + +``` php +echo $this->Url->build([ + 'controller' => 'Posts', + 'action' => 'list', + '_ext' => 'rss', +]); + +// 出力結果 +/posts/list.rss +``` + +フルベース URL を前につけた ('/' から始まる) URL: + +``` php +echo $this->Url->build('/posts', ['fullBase' => true]); + +// 出力結果 +http://somedomain.com/posts +``` + +GET パラメーターとフラグメントアンカーの URL: + +``` php +echo $this->Url->build([ + 'controller' => 'Posts', + 'action' => 'search', + '?' => ['foo' => 'bar'], + '#' => 'first', +]); + +// 出力結果 +/posts/search?foo=bar#first +``` + +上記の例で使用している `?` キーは、あなたが使用しているクエリー文字列パラメーターを +明示したい場合やクエリー文字列パラメーターをあなたのルートプレースホルダーの一つと名前を +共有したい場合に便利です。 + +名前付きルートの URL: + +``` php +echo $this->Url->build(['_name' => 'product-page', 'slug' => 'i-m-slug']); + +// 以下のようにルートをセットアップすることを仮定: +// $router->connect( +// '/products/{slug}', +// [ +// 'controller' => 'Products', +// 'action' => 'view', +// ], +// [ +// '_name' => 'product-page', +// ] +// ); + +echo $this->Url->build(['_name' => 'product-page', 'slug' => 'i-m-slug']); +// Will result in: +/products/i-m-slug +``` + +第2パラメーターは、HTML エスケープやベースパスを追加するかどうかを制御するオプションを +定義できます。 : + +``` php +$this->Url->build('/posts', [ + 'escape' => false, + 'fullBase' => true, +]); +``` + +以下は、アセットタイムスタンプ付きの URL が `` で囲まれており、 +フォントをプリロードしています。注意: ファイルは存在していなければならず、 +`Configure::read('Asset.timestamp')` は、タイムスタンプを追加するために +`true` または `'force'` を返さなければなりません。 : + +``` php +echo $this->Html->meta([ + 'rel' => 'preload', + 'href' => $this->Url->assetUrl( + '/assets/fonts/yout-font-pack/your-font-name.woff2' + ), + 'as' => 'font', +]); +``` + +CSS や JavaScript、または画像ファイルの URL を生成する場合、 +これらのアセットタイプのためのヘルパーメソッドがあります。 : + +``` php +// 出力結果 /img/icon.png +$this->Url->image('icon.png'); + +// 出力結果 /js/app.js +$this->Url->script('app.js'); + +// 出力結果 /css/app.css +$this->Url->css('app.css'); + +// メソッドの呼び出し時にタイムスタンプを強制 +$this->Url->css('app.css', ['timestamp' => 'force']); + +// または、メソッド呼び出し時にタイムスタンプを無効化 +$this->Url->css('app.css', ['timestamp' => false]); +``` + +詳細は API の +[Router::url](https://api.cakephp.org/4.x/class-Cake.Routing.Router.html#_url) +を確認してください。 diff --git a/docs/ja/views/json-and-xml-views.md b/docs/ja/views/json-and-xml-views.md new file mode 100644 index 0000000000..b33e83eaf0 --- /dev/null +++ b/docs/ja/views/json-and-xml-views.md @@ -0,0 +1,252 @@ +# JSON と XML ビュー + +`JsonView` と `XmlView` を使用することで +JSON と XML レスポンスを作成できるようになり、 +それらは `Cake\Controller\Component\RequestHandlerComponent` と統合されます。 + +あなたのアプリケーションで `RequestHandlerComponent` を有効にして、 +`json` と `xml` またはいずれかの拡張子のサポートを有効にすることで、 +自動的に新しいビュークラスを使用することが出来るようになります。 +以降、このページでは `JsonView` と `XmlView` をデータビューと呼びます。 + +データビューを生成する方法は2つあります。 +一つ目は `serialize` オプションを使用する方法、二つ目は通常のテンプレートファイルを使用する方法です。 + +## あなたのアプリケーションでデータビューを有効化する + +データビュークラスを使用する前に、 +まず `Cake\Controller\Component\RequestHandlerComponent` +をコントローラーでロードする必要があります。 : + +``` php +public function initialize(): void +{ + ... + $this->loadComponent('RequestHandler'); +} +``` + +`AppController` で上記のようにすることで、コンテンツタイプによる自動切り替えが有効化されます。 +`viewClassMap` 設定でコンポーネットを設定することで、カスタムクラスをマップしたり +他のデータ型をマップするも出来ます。 + +任意で [File Extensions](../development/routing#file-extensions) で json と xml 、もしくはどちらかの拡張子を有効化することが出来ます。 +`http://example.com/articles.json` の様に、URL の末尾でファイルの拡張子として +レスポンスタイプの名前を指定することで、 `JSON` 、 `XML` もしくはそれ以外の特定の +フォーマットビューにアクセスすることが出来ます。 + +デフォルトでは [File Extensions](../development/routing#file-extensions) が無効の場合、リクエストの `Accept` ヘッダーにより、 +ユーザーにどのフォーマットをレンダリングするべきかが判断されます。例として、 `JSON` レスポンスを +レンダリングするために使用される `Accept` の設定値は `application/json` です。 + +## シリアライズキーをデータビューで使用する + +`serialize` オプションは、データビューを使用する時に、どのビュー変数がシリアライズされるべきかを示します。 +json/xml に変換する前に独自のフォーマット処理が不要な場合、 +これはコントローラーのアクションでテンプレートファイルを定義する手間を省いてくれます。 + +レスポンスを生成する前にビュー変数へ独自のフォーマット処理や操作が必要な場合は、 +テンプレートファイルを使用する必要があります。 +`serialize` の値は、文字列かシリアライズしたいビュー変数の配列のどちらかになります。 : + +``` php +namespace App\Controller; + +class ArticlesController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('RequestHandler'); + } + + public function index() + { + // シリアライズする必要があるビュー変数をセットする + $this->set('articles', $this->paginate()); + // JsonView がシリアライズするべきビュー変数を指定する + $this->viewBuilder()->setOption('serialize', 'articles'); + } +} +``` + +`serialize` をビュー変数の配列として定義し結合することも出来ます。 : + +``` php +namespace App\Controller; + +class ArticlesController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('RequestHandler'); + } + + public function index() + { + // $articles と $comments を生成する何かのコード + + // シリアライズする必要があるビュー変数をセットする + $this->set(compact('articles', 'comments')); + + // JsonView がシリアライズするべきビュー変数を指定する + $this->viewBuilder()->setOption('serialize', ['articles', 'comments']); + } +} +``` + +`XmlView` を使用するケースでは、配列として `serialize` を定義することには最上位に +`` 要素が自動的に付与されるという利点があります。 `serialize` と XmlView に +文字列を使用する時は、ビュー変数が最上位要素を一つ持つことに確認してください。 +一つも最上位要素を持たない場合、XML の生成に失敗するでしょう。 + +## テンプレートファイルをデータビューで使用する + +最終的な出力の前にビュー変数に何かの処理を施したいケースでは、テンプレートファイルを +使用する必要があります。例えば、生成された HTML を要素として持つ記事があり、 +JSON レスポンスからそれを取り除きたいとします。こういった状況ではビューファイルが役に立ちます。 : + +``` php +// コントローラーのコード +class ArticlesController extends AppController +{ + public function index() + { + $articles = $this->paginate('Articles'); + $this->set(compact('articles')); + } +} + +// ビューのコード - templates/Articles/json/index.php +foreach ($articles as &$article) { + unset($article->generated_html); +} +echo json_encode(compact('articles')); +``` + +より複雑な操作を行ったり、ヘルパーを整形に使用することも出来ます。データビュークラスは、 +ビューファイルはシリアライズされたコンテンツを出力することを前提としているため、 +レイアウトをサポートしません。 + +## XML ビューの作成 + +`class` **XmlView** + +デフォルトでは `serialize` を使用する時、XmlView は +`` ノードでシリアル化されるビュー変数をラップします。 +`rootNode` オプションを使用することで、このノードに別の名前を設定することが出来ます。 + +XmlView クラスは、XML の生成に使用するオプション(例: `tags` vs `attributes` )を +変更するための `xmlOptions` オプションをサポートしています。 + +`XmlView` の使用例は [sitemap.xml](https://www.sitemaps.org/protocol.html) を生成することです。 +このドキュメントタイプでは `rootNode` を変更し属性を設定する必要があります。 +属性は `@` プレフィックスを使用して定義されます。 : + +``` php +use Cake\View\XmlView; + +public function viewClasses(): array +{ + return [XmlView::class]; +} + +public function sitemap() +{ + $pages = $this->Pages->find()->all(); + $urls = []; + foreach ($pages as $page) { + $urls[] = [ + 'loc' => Router::url(['controller' => 'Pages', 'action' => 'view', $page->slug, '_full' => true]), + 'lastmod' => $page->modified->format('Y-m-d'), + 'changefreq' => 'daily', + 'priority' => '0.5' + ]; + } + + // 生成されたドキュメントにカスタムルートノードを定義します。 + $this->viewBuilder() + ->setOption('rootNode', 'urlset') + ->setOption('serialize', ['@xmlns', 'url']); + $this->set([ + // ルートノードで属性を定義します。 + '@xmlns' => 'http://www.sitemaps.org/schemas/sitemap/0.9', + 'url' => $urls + ]); +} +``` + +## JSON ビューの作成 + +`class` **JsonView** + +JsonView クラスは、JSON の生成に使用するビットマスクを変更するためための +`jsonOptions` オプションをサポートします。このオプションの有効な値は +[json_encode](https://php.net/json_encode) を参照してください。 + +例えば、一貫した JSON 形式で CakePHP エンティティーの検証エラーをシリアライズするには: + +``` php +// コントローラーのアクションの中で、保存に失敗した時 +$this->set('errors', $articles->errors()); +$this->viewBuilder() + ->setOption('serialize', ['errors']) + ->setOption('jsonOptions', JSON_FORCE_OBJECT); +``` + +### JSONP レスポンス + +`JsonView` を使用する時は、特別なビュー変数 `_jsonp` を使用することで +JSONP レスポンスの返すことが出来ます。これに `true` を設定することで、ビュークラスに +"callback" という名前のクエリー文字列パラメーターがセットされているかをチェックさせ、 +それ同時に提供された関数名で JSON レスポンスをラップさせることが出来ます。 +"callback" の代わりにカスタムクエリー文字列パラメーターを使用したい場合は、 +`_jsonp` に `true` の代わりの名前を指定してください。 + +## 使用例 + +リクエストのコンテンツタイプまたは拡張子によって、 +[RequestHandlerComponent](../controllers/components/request-handling) +が自動的にビューをセットするのに対して、あなたも同様にコントローラーのなかでビューマッピングを +操作することが出来ます。 : + +``` php +// src/Controller/VideosController.php +namespace App\Controller; + +use App\Controller\AppController; +use Cake\Http\Exception\NotFoundException; + +class VideosController extends AppController +{ + public function export($format = '') + { + $format = strtolower($format); + + // ビューマッピングの形式 + $formats = [ + 'xml' => 'Xml', + 'json' => 'Json', + ]; + + // 未知の形式の時はエラー + if (!isset($formats[$format])) { + throw new NotFoundException(__('Unknown format.')); + } + + // ビューに出力形式をセット + $this->viewBuilder()->setClassName($formats[$format]); + + // データを取得 + $videos = $this->Videos->find('latest')->all(); + + // ビューにデータをセット + $this->set(compact('videos')); + $this->viewBuilder()->setOption('serialize', ['videos']); + + // ダウンロードを指定 + return $this->response->withDownload('report-' . date('YmdHis') . '.' . $format); + } +} +``` diff --git a/docs/ja/views/themes.md b/docs/ja/views/themes.md new file mode 100644 index 0000000000..15abf43421 --- /dev/null +++ b/docs/ja/views/themes.md @@ -0,0 +1,70 @@ +# テーマ + +CakePHP のテーマは、テンプレートファイルを供給することに集中したシンプルなプラグインです。 +[Plugin Create Your Own](../plugins#plugin-create-your-own) のセクションをご覧ください。 +テーマには、ページの見た目や雰囲気を簡単に素早く切り替えられるようになるという利点があります。 +もし必要であれば、テンプレートファイルに追加してヘルパーやセルもまた供給することができます。 +ヘルパーやセルをテーマで使うときは、 `プラグイン記法` を使い続ける必要があります。 + +テーマを使うためには、コントローラーのアクションをテーマ名にするか +`beforeRender()` をコールバックしてください。 : + +``` php +class ExamplesController extends AppController +{ + public function beforeRender(\Cake\Event\EventInterface $event) + { + $this->viewBuilder()->setTheme('Modern'); + } +} +``` + +テーマのテンプレートファイルは、同じ名前のプラグイン内にある必要があります。 +例えば、上記のテーマは **plugins/Modern/src/Template** で見つかるでしょう。 +CakePHP は plugin/theme の名前にキャメルケースを期待しています。これは大切な +ことなので覚えておいてください。 +さらに **plugins/Modern/src/Template** の中のフォルダー構造は **templates/** と +全く同じにしてください。 + +例えば、Posts コントローラーの edit アクションのためのビューファイルは +**plugins/Modern/templates/Posts/edit.php** に配置されて、レイアウトファイルは +**plugins/Modern/templates/layout/** に配置されます。カスタマイズされた +テンプレートにプラグイン・テーマを供給することもできます。 +TagsController を含んだ 'Cms' と名付けられたプラグインがある場合、テーマ Modern は +edit テンプレートを **plugins/Modern/templates/plugin/Cms/Tags/edit.php** +に置き換えます。 + +テーマの中にビューファイルを見つけられなかった場合、CakePHP はビューファイルを +**templates/** に配置しようとします。この方法によって、マスターテンプレートファイルを +作成して、テーマフォルダーには上書きが必要なファイルだけを配置すればよくなります。 + +テーマがプラグインとしても機能する場合は、 Application の `bootstrap` メソッドの中で +読み込ませることを忘れないようにしてください。例: + +``` php +// /plugins/Modern フォルダーにあるプラグインテーマを読み込みます +$this->addPlugin('Modern'); +``` + +## テーマアセット + +テーマは CakePHP の標準のプラグインなので、必要なアセットを webroot ディレクトリーに +含めることができます。これは、テーマの配布と容易なパッケージングを可能にさせます。 +開発中、テーマのアセットへのリクエストは `Cake\Routing\Dispatcher` +によってハンドリングされます。プロダクション環境でパフォーマンスを改善するためには、 +[Symlink Assets](../deployment#symlink-assets) を推奨します。 + +CakePHP の全ての組み込みヘルパーはテーマを認識しており、正確なパスを自動的に生成します。 +テンプレートファイル同様に、ファイルがテーマフォルダーの中に無かったら +メインの webroot フォルダーをデフォルトにします。 : + +``` php +// 'purple_cupcake'という名前のテーマの時 +$this->Html->css('main.css'); + +// 以下のパスを生成する +/purple_cupcake/css/main.css + +// 以下をリンクする +plugins/PurpleCupcake/webroot/css/main.css +``` diff --git a/docs/public/basic_mvc.png b/docs/public/basic_mvc.png new file mode 100644 index 0000000000..099d13461e Binary files /dev/null and b/docs/public/basic_mvc.png differ diff --git a/en/_static/img/cakefest-2022-banner.png b/docs/public/cakefest-2022-banner.png similarity index 100% rename from en/_static/img/cakefest-2022-banner.png rename to docs/public/cakefest-2022-banner.png diff --git a/docs/public/code-coverage.png b/docs/public/code-coverage.png new file mode 100644 index 0000000000..5eff05469a Binary files /dev/null and b/docs/public/code-coverage.png differ diff --git a/en/_static/img/middleware-request.png b/docs/public/middleware-request.png similarity index 100% rename from en/_static/img/middleware-request.png rename to docs/public/middleware-request.png diff --git a/en/_static/img/middleware-setup.png b/docs/public/middleware-setup.png similarity index 100% rename from en/_static/img/middleware-setup.png rename to docs/public/middleware-setup.png diff --git a/en/_static/img/read-the-book.jpg b/docs/public/read-the-book.jpg similarity index 100% rename from en/_static/img/read-the-book.jpg rename to docs/public/read-the-book.jpg diff --git a/en/_static/img/save-cycle.png b/docs/public/save-cycle.png similarity index 100% rename from en/_static/img/save-cycle.png rename to docs/public/save-cycle.png diff --git a/en/_static/img/typical-cake-request.png b/docs/public/typical-cake-request.png similarity index 100% rename from en/_static/img/typical-cake-request.png rename to docs/public/typical-cake-request.png diff --git a/en/_static/img/validation-cycle.png b/docs/public/validation-cycle.png similarity index 100% rename from en/_static/img/validation-cycle.png rename to docs/public/validation-cycle.png diff --git a/en/404.rst b/en/404.rst deleted file mode 100644 index 6c676eac39..0000000000 --- a/en/404.rst +++ /dev/null @@ -1,7 +0,0 @@ -:orphan: True - -Not Found -######### - -The page you're looking for cannot be found. Try using the search bar to find -what you're looking for. diff --git a/en/Makefile b/en/Makefile deleted file mode 100644 index a463687658..0000000000 --- a/en/Makefile +++ /dev/null @@ -1,133 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = ../build -CONFDIR = ../config -PYTHON = python3 -SPHINX_LANG = en - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees/$(SPHINX_LANG) -c $(CONFDIR) -D language=$(SPHINX_LANG) $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html -D "exclude_patterns=*-contents.rst" $(ALLSPHINXOPTS) $(BUILDDIR)/html/$(SPHINX_LANG) - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html/$(SPHINX_LANG)." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp/$(SPHINX_LANG) - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp/$(SPHINX_LANG)." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/CakePHPCookbook.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/CakePHPCookbook.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/CakePHPCookbook" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/CakePHPCookbook" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub -D master_doc='epub-contents' $(ALLSPHINXOPTS) $(BUILDDIR)/epub/$(SPHINX_LANG) - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub/$(SPHINX_LANG)." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex/$(SPHINX_LANG) - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex/$(SPHINX_LANG)." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex/$(SPHINX_LANG) - @echo "Running LaTeX files through pdflatex..." - make -C $(BUILDDIR)/latex/$(SPHINX_LANG) all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex/$(SPHINX_LANG)." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/en/appendices.rst b/en/appendices.rst deleted file mode 100644 index ca1ddc4508..0000000000 --- a/en/appendices.rst +++ /dev/null @@ -1,41 +0,0 @@ -Appendices -########## - -Appendices contain information regarding the new features -introduced in each version and the migration path between versions. - -Migration Guides -================ - -:doc:`appendices/migration-guides` - -Backwards Compatibility Shimming -================================ - -If you need/want to shim 4.x behavior, or partially migrate in steps, check out -the `Shim plugin `__ that can help mitigate some BC breaking changes. - -Forwards Compatibility Shimming -=============================== - -Forwards compatibility shimming can prepare your 4.x app for the next major -release (5.x). - -If you already want to shim 5.x behavior into 4.x, check out the `Shim plugin -`__. This plugin aims to mitigate -some backwards compatibility breakage and help backport features from 5.x to -4.x. The closer your 3.x app is to 4.x, the smaller will be the diff of -changes, and the smoother will be the final upgrade. - -General Information -=================== - -.. toctree:: - :maxdepth: 1 - - appendices/cakephp-development-process - appendices/glossary - -.. meta:: - :title lang=en: Appendices - :keywords lang=en: migration guide,migration path,new features,glossary diff --git a/en/appendices/5-0-migration-guide.rst b/en/appendices/5-0-migration-guide.rst deleted file mode 100644 index d81e7561de..0000000000 --- a/en/appendices/5-0-migration-guide.rst +++ /dev/null @@ -1,428 +0,0 @@ -5.0 Migration Guide -################### - -CakePHP 5.0 contains breaking changes, and is not backwards compatible with 4.x -releases. Before attempting to upgrade to 5.0, first upgrade to 4.5 and resolve -all deprecation warnings. - -Refer to the :doc:`/appendices/5-0-upgrade-guide` for step by step instructions -on how to upgrade to 5.0. - -Deprecated Features Removed -=========================== - -All methods, properties and functionality that were emitting deprecation warnings -as of 4.5 have been removed. - -Breaking Changes -================ - -In addition to the removal of deprecated features there have been breaking -changes made: - -Global ------- - -- Type declarations were added to all function parameter and returns where possible. These are intended - to match the docblock annotations, but include fixes for incorrect annotations. -- Type declarations were added to all class properties where possible. These also include some fixes for - incorrect annotations. -- The ``SECOND``, ``MINUTE``, ``HOUR``, ``DAY``, ``WEEK``, ``MONTH``, ``YEAR`` constants were removed. -- Use of ``#[\AllowDynamicProperties]`` removed everywhere. It was used for the following classes: - - ``Command/Command`` - - ``Console/Shell`` - - ``Controller/Component`` - - ``Controller/Controller`` - - ``Mailer/Mailer`` - - ``View/Cell`` - - ``View/Helper`` - - ``View/View`` -- The supported database engine versions were updated: - - MySQL (5.7 or higher) - - MariaDB (10.1 or higher) - - PostgreSQL (9.6 or higher) - - Microsoft SQL Server (2012 or higher) - - SQLite 3 (3.16 or higher) - -Auth ----- - -- `Auth` has been removed. Use the `cakephp/authentication `__ and - `cakephp/authorization `__ plugins instead. - -Cache ------ - -- The ``Wincache`` engine was removed. The wincache extension is not supported - on PHP 8. - -Collection ----------- - -- ``combine()`` now throws an exception if the key path or group path doesn't exist or contains a null value. - This matches the behavior of ``indexBy()`` and ``groupBy()``. - -Console -------- - -- ``BaseCommand::__construct()`` was removed. -- ``ConsoleIntegrationTestTrait::useCommandRunner()`` was removed since it's no longer needed. -- ``Shell`` has been removed and should be replaced with `Command `__ -- ``ConsoleOptionParser::addSubcommand()`` was removed alongside the removal of - ``Shell``. Subcommands should be replaced with ``Command`` classes that - implement ``Command::defaultName()`` to define the necessary command name. -- ``BaseCommand`` now emits ``Command.beforeExecute`` and - ``Command.afterExecute`` events around the command's ``execute()`` method - being invoked by the framework. - -Connection ----------- - -- ``Connection::prepare()`` has been removed. You can use ``Connection::execute()`` - instead to execute a SQL query by specifing the SQL string, params and types in a single call. -- ``Connection::enableQueryLogging()`` has been removed. If you haven't enabled logging - through the connection config then you can later set the logger instance for the - driver to enable query logging ``$connection->getDriver()->setLogger()``. - -Controller ----------- - -- The method signature for ``Controller::__construct()`` has changed. - So you need to adjust your code accordingly if you are overriding the constructor. -- After loading components are no longer set as dynamic properties. Instead - ``Controller`` uses ``__get()`` to provide property access to components. This - change can impact applications that use ``property_exists()`` on components. -- The components' ``Controller.shutdown`` event callback has been renamed from - ``shutdown`` to ``afterFilter`` to match the controller one. This makes the callbacks more consistent. -- ``PaginatorComponent`` has been removed and should be replaced by calling ``$this->paginate()`` in your controller or - using ``Cake\Datasource\Paging\NumericPaginator`` directly -- ``RequestHandlerComponent`` has been removed. See the `4.4 migration `__ guide for how to upgrade -- ``SecurityComponent`` has been removed. Use ``FormProtectionComponent`` for form tampering protection - or ``HttpsEnforcerMiddleware`` to enforce use of HTTPS for requests instead. -- ``Controller::paginate()`` no longer accepts query options like ``contain`` for - its ``$settings`` argument. You should instead use the ``finder`` option - ``$this->paginate($this->Articles, ['finder' => 'published'])``. Or you can - create required select query before hand and then pass it to ``paginate()`` - ``$query = $this->Articles->find()->where(['is_published' => true]); $this->paginate($query);``. - -Core ----- - -- The function ``getTypeName()`` has been dropped. Use PHP's ``get_debug_type()`` instead. -- The dependency on ``league/container`` was updated to ``4.x``. This will - require the addition of typehints to your ``ServiceProvider`` implementations. -- ``deprecationWarning()`` now has a ``$version`` parameter. -- The ``App.uploadedFilesAsObjects`` configuration option has been removed - alongside of support for PHP file upload shaped arrays throughout the - framework. -- ``ClassLoader`` has been removed. Use composer to generate autoload files instead. - -Database --------- - -- The ``DateTimeType`` and ``DateType`` now always return immutable objects. - Additionally the interface for ``Date`` objects reflects the ``ChronosDate`` - interface which lacks all of the time related methods that were present in - CakePHP 4.x. -- ``DateType::setLocaleFormat()`` no longer accepts an array. -- ``Query`` now accepts only ``\Closure`` parameters instead of ``callable``. Callables can be converted - to closures using the new first-class array syntax in PHP 8.1. -- ``Query::execute()`` no longer runs results decorator callbacks. You must use ``Query::all()`` instead. -- ``TableSchemaAwareInterface`` was removed. -- ``Driver::quote()`` was removed. Use prepared statements instead. -- ``Query::orderBy()`` was added to replace ``Query::order()``. -- ``Query::groupBy()`` was added to replace ``Query::group()``. -- ``SqlDialectTrait`` has been removed and all its functionality has been moved - into the ``Driver`` class itself. -- ``CaseExpression`` has been removed and should be replaced with - ``QueryExpression::case()`` or ``CaseStatementExpression`` -- ``Connection::connect()`` has been removed. Use - ``$connection->getDriver()->connect()`` instead. -- ``Connection::disconnect()`` has been removed. Use - ``$connection->getDriver()->disconnect()`` instead. -- ``cake.database.queries`` has been added as an alternative to the ``queriesLog`` scope -- The ability to enable/disable ResultSet buffering has been removed. Results are always buffered. - -Datasource ----------- - -- The ``getAccessible()`` method was added to ``EntityInterface``. Non-ORM - implementations need to implement this method now. -- The ``aliasField()`` method was added to ``RepositoryInterface``. Non-ORM - implementations need to implement this method now. - -Event ------ - -- Event payloads must be an array. Other object such as ``ArrayAccess`` are no longer cast to array and will raise a ``TypeError`` now. -- It is recommended to adjust event handlers to be void methods and use ``$event->setResult()`` instead of returning the result - -Error ------ - -- ``ErrorHandler`` and ``ConsoleErrorHandler`` have been removed. See the `4.4 migration `__ guide for how to upgrade -- ``ExceptionRenderer`` has been removed and should be replaced with ``WebExceptionRenderer`` -- ``ErrorLoggerInterface::log()`` has been removed and should be replaced with ``ErrorLoggerInterface::logException()`` -- ``ErrorLoggerInterface::logMessage()`` has been removed and should be replaced with ``ErrorLoggerInterface::logError()`` - -Filesystem ----------- - -- The Filesystem package was removed, and ``Filesystem`` class was moved to the Utility package. - -Http ----- - -- ``ServerRequest`` is no longer compatible with ``files`` as arrays. This - behavior has been disabled by default since 4.1.0. The ``files`` data will now - always contain ``UploadedFileInterfaces`` objects. - -I18n ----- - -- ``FrozenDate`` was renamed to `Date` and ``FrozenTime`` was renamed to `DateTime`. -- ``Time`` now extends ``Cake\Chronos\ChronosTime`` and is therefore immutable. -- ``Date`` objects do not extend ``DateTimeInterface`` anymore - therefore you can't compare them with ``DateTime`` objects. - See the `cakephp/chronos release documentation `__ for more information. -- ``Date::parseDateTime()`` was removed. -- ``Date::parseTime()`` was removed. -- ``Date::setToStringFormat()`` and ``Date::setJsonEncodeFormat()`` no longer accept an array. -- ``Date::i18nFormat()`` and ``Date::nice()`` no longer accept a timezone parameter. -- Translation files for plugins with vendor prefixed names (``FooBar/Awesome``) will now have that - prefix in the file name, e.g. ``foo_bar_awesome.po`` to avoid collision with a ``awesome.po`` file - from a corresponding plugin (``Awesome``). - -Log ---- - -- Log engine config now uses ``null`` instead of ``false`` to disable scopes. - So instead of ``'scopes' => false`` you need to use ``'scopes' => null`` in your log config. - -Mailer ------- - -- ``Email`` has been removed. Use `Mailer `__ instead. -- ``cake.mailer`` has been added as an alternative to the ``email`` scope - -ORM ---- - -- ``EntityTrait::has()`` now returns ``true`` when an attribute exists and is - set to ``null``. In previous versions of CakePHP this would return ``false``. - See the release notes for 4.5.0 for how to adopt this behavior in 4.x. -- ``EntityTrait::extractOriginal()`` now returns only existing fields, similar to ``extractOriginalChanged()``. -- Finder arguments are now required to be associative arrays as they were always expected to be. -- ``TranslateBehavior`` now defaults to the ``ShadowTable`` strategy. If you are - using the ``Eav`` strategy you will need to update your behavior configuration - to retain the previous behavior. -- ``allowMultipleNulls`` option for ``isUnique`` rule now default to true matching - the original 3.x behavior. -- ``Table::query()`` has been removed in favor of query-type specific functions. -- ``Table::updateQuery()``, ``Table::selectQuery()``, ``Table::insertQuery()``, and - ``Table::deleteQuery()``) were added and return the new type-specific query objects below. -- ``SelectQuery``, ``InsertQuery``, ``UpdateQuery`` and ``DeleteQuery`` were added - which represent only a single type of query and do not allow switching between query types nor - calling functions unrelated to the specific query type. -- ``Table::_initializeSchema()`` has been removed and should be replaced by calling - ``$this->getSchema()`` inside the ``initialize()`` method. -- ``SaveOptionsBuilder`` has been removed. Use a normal array for options instead. - -Routing -------- - -- Static methods ``connect()``, ``prefix()``, ``scope()`` and ``plugin()`` of the ``Router`` have been removed and - should be replaced by calling their non-static method variants via the ``RouteBuilder`` instance. -- ``RedirectException`` has been removed. Use ``\Cake\Http\Exception\RedirectException`` instead. - -TestSuite ---------- - -- ``TestSuite`` was removed. Users should use environment variables to customize - unit test settings instead. -- ``TestListenerTrait`` was removed. PHPUnit dropped support for these listeners. - See :doc:`/appendices/phpunit10` -- ``IntegrationTestTrait::configRequest()`` now merges config when called multiple times - instead of replacing the currently present config. - -Validation ----------- - -- ``Validation::isEmpty()`` is no longer compatible with file upload shaped - arrays. Support for PHP file upload arrays has been removed from - ``ServerRequest`` as well so you should not see this as a problem outside of - tests. -- Previously, most data validation error messages were simply ``The provided value is invalid``. - Now, the data validation error messages are worded more precisely. - For example, ``The provided value must be greater than or equal to \`5\```. - -View ----- - -- ``ViewBuilder`` options are now truly associative (string keys). -- ``NumberHelper`` and ``TextHelper`` no longer accept an ``engine`` config. -- ``ViewBuilder::setHelpers()`` parameter ``$merge`` was removed. Use ``ViewBuilder::addHelpers()`` instead. -- Inside ``View::initialize()``, prefer using ``addHelper()`` instead of ``loadHelper()``. - All configured helpers will be loaded afterwards, anyway. -- ``View\Widget\FileWidget`` is no longer compatible with PHP file upload shaped - arrays. This is aligned with ``ServerRequest`` and ``Validation`` changes. -- ``FormHelper`` no longer sets ``autocomplete=off`` on CSRF token fields. This - was a workaround for a Safari bug that is no longer relevant. - -Deprecations -============ - -The following is a list of deprecated methods, properties and behaviors. These -features will continue to function in 5.x and will be removed in 6.0. - -Database --------- - -- ``Query::order()`` was deprecated. Use ``Query::orderBy()`` instead now that - ``Connection`` methods are no longer proxied. This aligns the function name - with the SQL statement. -- ``Query::group()`` was deprecated. Use ``Query::groupBy()`` instead now that - ``Connection`` methods are no longer proxied. This aligns the function name - with the SQL statement. - -ORM ---- - -- Calling ``Table::find()`` with options array is deprecated. Use `named arguments `__ - instead. For e.g. instead of ``find('all', ['conditions' => $array])`` use - ``find('all', conditions: $array)``. Similarly for custom finder options, instead - of ``find('list', ['valueField' => 'name'])`` use ``find('list', valueField: 'name')`` - or multiple named arguments like ``find(type: 'list', valueField: 'name', conditions: $array)``. - -New Features -============ - -Improved type checking ------------------------ - -CakePHP 5 leverages the expanded type system feature available in PHP 8.1+. -CakePHP also uses ``assert()`` to provide improved error messages and additional -type soundness. In production mode, you can configure PHP to not generate -code for ``assert()`` yielding improved application performance. See the -:ref:`symlink-assets` for how to do this. - -Collection ----------- - -- Added ``unique()`` which filters out duplicate value specified by provided callback. -- ``reject()`` now supports a default callback which filters out truthy values which is - the inverse of the default behavior of ``filter()`` - -Core ----- - -- The ``services()`` method was added to ``PluginInterface``. -- ``PluginCollection::addFromConfig()`` has been added to :ref:`simplify plugin loading `. - -Database --------- - -- ``ConnectionManager`` now supports read and write connection roles. Roles can be configured - with ``read`` and ``write`` keys in the connection config that override the shared config. -- ``Query::all()`` was added which runs result decorator callbacks and returns a result set for select queries. -- ``Query::comment()`` was added to add a SQL comment to the executed query. This makes it easier to debug queries. -- ``EnumType`` was added to allow mapping between PHP backed enums and a string or integer column. -- ``getMaxAliasLength()`` and ``getConnectionRetries()`` were added - to ``DriverInterface``. -- Supported drivers now automatically add auto-increment only to integer primary keys named "id" instead - of all integer primary keys. Setting 'autoIncrement' to false always disables on all supported drivers. - -Http ----- - -- Added support for `PSR-17 `__ factories - interface. This allows ``cakephp/http`` to provide a client implementation to - libraries that allow automatic interface resolution like php-http. -- Added ``CookieCollection::__get()`` and ``CookieCollection::__isset()`` to add - ergonomic ways to access cookies without exceptions. - -ORM ---- - -Required Entity Fields ----------------------- - -Entities have a new opt-in functionality that allows making entities handle -properties more strictly. The new behavior is called 'required fields'. When -enabled, accessing properties that are not defined in the entity will raise -exceptions. This impacts the following usage:: - - $entity->get(); - $entity->has(); - $entity->getOriginal(); - isset($entity->attribute); - $entity->attribute; - -Fields are considered defined if they pass ``array_key_exists``. This includes -null values. Because this can be a tedious to enable feature, it was deferred to -5.0. We'd like any feedback you have on this feature as we're considering making -this the default behavior in the future. - - -Typed Finder Parameters ------------------------ - -Table finders can now have typed arguments as required instead of an options array. -For e.g. a finder for fetching posts by category or user:: - - public function findByCategoryOrUser(SelectQuery $query, array $options) - { - if (isset($options['categoryId'])) { - $query->where(['category_id' => $options['categoryId']]); - } - if (isset($options['userId'])) { - $query->where(['user_id' => $options['userId']]); - } - - return $query; - } - -can now be written as:: - - public function findByCategoryOrUser(SelectQuery $query, ?int $categoryId = null, ?int $userId = null) - { - if ($categoryId) { - $query->where(['category_id' => $categoryId]); - } - if ($userId) { - $query->where(['user_id' => $userId]); - } - - return $query; - } - -The finder can then be called as ``find('byCategoryOrUser', userId: $somevar)``. -You can even include the special named arguments for setting query clauses. -``find('byCategoryOrUser', userId: $somevar, conditions: ['enabled' => true])``. - -A similar change has been applied to the ``RepositoryInterface::get()`` method:: - - public function view(int $id) - { - $author = $this->Authors->get($id, [ - 'contain' => ['Books'], - 'finder' => 'latest', - ]); - } - -can now be written as:: - - public function view(int $id) - { - $author = $this->Authors->get($id, contain: ['Books'], finder: 'latest'); - } - -TestSuite ---------- - -- ``IntegrationTestTrait::requestAsJson()`` has been added to set JSON headers for the next request. - -Plugin Installer ----------------- -- The plugin installer has been updated to automatically handle class autoloading - for your app plugins. So you can remove the namespace to path mappings for your - plugins from your ``composer.json`` and just run ``composer dumpautoload``. diff --git a/en/appendices/5-0-upgrade-guide.rst b/en/appendices/5-0-upgrade-guide.rst deleted file mode 100644 index 5f34ec911c..0000000000 --- a/en/appendices/5-0-upgrade-guide.rst +++ /dev/null @@ -1,74 +0,0 @@ -5.0 Upgrade Guide -################# - -First, check that your application is running on latest CakePHP 4.x version. - -Fix Deprecation Warnings -======================== - -Once your application is running on latest CakePHP 4.x, enable deprecation warnings in **config/app.php**:: - - 'Error' => [ - 'errorLevel' => E_ALL, - ] - -Now that you can see all the warnings, make sure these are fixed before proceeding with the upgrade. - -Some potentially impactful deprecations you should make sure you have addressed -are: - -- ``Table::query()`` was deprecated in 4.5.0. Use ``selectQuery()``, - ``updateQuery()``, ``insertQuery()`` and ``deleteQuery()`` instead. - -Upgrade to PHP 8.1 -================== - -If you are not running on **PHP 8.1 or higher**, you will need to upgrade PHP before updating CakePHP. - -.. note:: - CakePHP 5.0 requires **a minimum of PHP 8.1**. - -.. _upgrade-tool-use: - -Use the Upgrade Tool -==================== - -.. note:: - The upgrade tool only works on applications running on latest CakePHP 4.x. You cannot run the upgrade tool after updating to CakePHP 5.0. - -Because CakePHP 5 leverages union types and ``mixed``, there are many -backwards incompatible changes concerning method signatures and file renames. -To help expedite fixing these tedious changes there is an upgrade CLI tool: - -.. code-block:: console - - # Install the upgrade tool - git clone https://github.com/cakephp/upgrade - cd upgrade - git checkout 5.x - composer install --no-dev - -With the upgrade tool installed you can now run it on your application or -plugin:: - - bin/cake upgrade rector --rules cakephp50 - bin/cake upgrade rector --rules chronos3 - -Update CakePHP Dependency -========================= - -After applying rector refactorings you need to upgrade CakePHP, its plugins, PHPUnit -and maybe other dependencies in your ``composer.json``. -This process heavily depends on your application so we recommend you compare your -``composer.json`` with what is present in `cakephp/app -`__. - -After the version strings are adjusted in your ``composer.json`` execute -``composer update -W`` and check its output. - -Update app files based upon latest app template -=============================================== - -Next, ensure the rest of your application has been updated to be based upon the -latest version of `cakephp/app -`__. diff --git a/en/appendices/5-1-migration-guide.rst b/en/appendices/5-1-migration-guide.rst deleted file mode 100644 index caac02853f..0000000000 --- a/en/appendices/5-1-migration-guide.rst +++ /dev/null @@ -1,198 +0,0 @@ -5.1 Migration Guide -################### - -The 5.1.0 release is a backwards compatible with 5.0. It adds new functionality -and introduces new deprecations. Any functionality deprecated in 5.x will be -removed in 6.0.0. - -Behavior Changes -================ - -- Connection now creates unique read and write drivers if the keys ``read`` or - ``write`` are present in the config regardless of values. -- FormHelper no longer generates ``aria-required`` attributes on input elements - that also have the ``required`` attribute set. The ``aria-required`` attribute - is redundant on these elements and generates HTML validation warnings. If you - are using ``aria-required`` attribute in styling or scripting you'll need to - update your application. -- Adding associations with duplicate names will now raise exceptions. You can - use ``$table->associations()->has()`` to conditionally define associations if - required. -- Text Utility and TextHelper methods around truncation and maximum length are using - a UTF-8 character for ``ellipsis`` instead of ``...`` legacy characters. -- ``TableSchema::setColumnType()`` now throws an exception if the specified column - does not exist. -- ``PluginCollection::addPlugin()`` now throws an exception if a plugin of the same - name is already added. -- ``TestCase::loadPlugins()`` will now clear out any previously loaded plugins. So - you must specify all plugins required for any subsequent tests. -- The hashing algorithm for ``Cache`` configurations that use ``groups``. Any - keys will have new group prefix hashes generated which will cause cache - misses. Consider an incremental deploy to avoid operating on an entirely cold - cache. -- ``FormHelper::getFormProtector()`` now returns ``null`` in addition to its - previous types. This allows dynamic view code to run with fewer errors and - shouldn't impact most applications. -- The default value for ``valueSeparator`` in ``Table::findList()`` is now - a single space instead of ``;``. -- ``ErrorLogger`` uses ``Psr\Log\LogTrait`` now. -- ``Database\QueryCompiler::$_orderedUnion`` was removed. - -Deprecations -============ - -I18n ----- - -- The ``_cake_core_`` cache config key has been renamed to ``_cake_translations_``. - -Mailer ------- - -- ``Mailer::setMessage()`` is deprecated. It has unintuitive behavior and very - low usage. - - -New Features -============ - -Cache ------ - -- ``RedisEngine`` now supports a ``tls`` option that enables connecting to redis - over a TLS connection. You can use the ``ssl_ca``, ``ssl_cert`` and - ``ssl_key`` options to define the TLS context for redis. - -Command -------- - -- ``bin/cake plugin list`` has been added to list all available plugins, - their load configuration and version. -- Optional ``Command`` arguments can now have a ``default`` value. -- ``BannerHelper`` was added. This command helper can format text as a banner - with a coloured background and padding. -- Additional default styles for ``info.bg``, ``warning.bg``, ``error.bg`` and - ``success.bg`` were added to ``ConsoleOutput``. - -Console -------- - -- ``Arguments::getBooleanOption()`` and ``Arguments::getMultipleOption()`` were added. -- ``Arguments::getArgument()`` will now raise an exception if an unknown - argument name is provided. This helps prevent mixing up option/argument names. - - -Controller ----------- - -- Components can now use the DI container to have dependencies resolved and - provided as constructor parameters just like Controllers and Commands do. - -Core ----- - -- ``PluginConfig`` was added. Use this class to get all available plugins, their load config and versions. -- The ``toString``, ``toInt``, ``toBool`` functions were added. They give you - a typesafe way to cast request data or other input and return ``null`` when conversion fails. -- ``pathCombine()`` was added to help build paths without worrying about duplicate and trailing slashes. -- A new ``events`` hook was added to the ``BaseApplication`` as well as the ``BasePlugin`` class. This hook - is the recommended way to register global event listeners for you application. See :ref:`Registering Listeners ` - -Database --------- - -- Support for ``point``, ``linestring``, ``polygon`` and ``geometry`` types were - added. These types are useful when working with geospatial or cartesian - co-ordinates. Sqlite support uses text columns under the hood and lacks - functions to manipulate data as geospatial values. -- ``SelectQuery::__debugInfo()`` now includes which connection role the query - is for. -- ``SelectQuery::intersect()`` and ``SelectQuery::intersectAll()`` were added. - These methods enable queries using ``INTERSECT`` and ``INTERSECT ALL`` - conjunctions to be expressed. -- New supports features were added for ``intersect``, ``intersect-all`` and - ``set-operations-order-by`` features. -- The ability to fetch records without buffering which existed in 4.x has been restored. - Methods ``SelectQuery::enableBufferedResults()``, ``SelectQuery::disableBufferedResults()`` - and ``SelectQuery::isBufferedResultsEnabled()`` have been re-added. - -Datasource ----------- - -- ``RulesChecker::remove()``, ``removeCreate()``, ``removeUpdate()``, and - ``removeDelete()`` methods were added. These methods allow you to remove rules - by name. - - -Http ----- - -- ``SecurityHeadersMiddleware::setPermissionsPolicy()`` was added. This method - adds the ability to define ``permissions-policy`` header values. -- ``Client`` now emits ``HttpClient.beforeSend`` and ``HttpClient.afterSend`` - events when requests are sent. You can use these events to perform logging, - caching or collect telemetry. -- ``Http\Server::terminate()`` was added. This method triggers the - ``Server.terminate`` event which can be used to run logic after the response - has been sent in fastcgi environments. In other environments the - ``Server.terminate`` event runs *before* the response has been sent. - -I18n ----- - -- ``Number::formatter()`` and ``currency()`` now accept a ``roundingMode`` - option to override how rounding is done. -- The ``toDate``, and ``toDateTime`` functions were added. They give you - a typesafe way to cast request data or other input and return ``null`` when - conversion fails. - -ORM ---- - -- Setting the ``preserveKeys`` option on association finder queries. This can be - used with ``formatResults()`` to replace association finder results with an - associative array. -- SQLite columns with names containing ``json`` can now be mapped to ``JsonType``. - This is currently an opt-in feature which is enabled by setting the ``ORM.mapJsonTypeForSqlite`` - configure value to ``true`` in your app. - -TestSuite ---------- - -- CakePHP as well as the app template have been updated to use PHPUnit ``^10.5.5 || ^11.1.3"``. -- ``ConnectionHelper`` methods are now all static. This class has no state and - its methods were updated to be static. -- ``LogTestTrait`` was added. This new trait makes it easy to capture logs in - your tests and make assertions on the presence or absence of log messages. -- ``IntegrationTestTrait::replaceRequest()`` was added. - -Utility -------- - -- ``Hash::insert()`` and ``Hash::remove()`` now accept ``ArrayAccess`` objects along with ``array`` data. - -Validation ----------- - -- ``Validation::enum()`` and ``Validator::enum()`` were added. These validation - methods simplify validating backed enum values. -- ``Validation::enumOnly()`` and ``Validation::enumExcept()`` were added to check for specific cases - and further simplify validating backed enum values. - -View ----- - -- View cells now emit events around their actions ``Cell.beforeAction`` and - ``Cell.afterAction``. -- ``NumberHelper::format()`` now accepts a ``roundingMode`` option to override how - rounding is done. - -Helpers -------- - -- ``TextHelper::autoLinkUrls()`` has options added for better link label printing: - * ``stripProtocol``: Strips ``http://`` and ``https://`` from the beginning of the link. Default off. - * ``maxLength``: The maximum length of the link label. Default off. - * ``ellipsis``: The string to append to the end of the link label. Defaults to UTF8 version. -- ``HtmlHelper::meta()`` can now create a meta tag containing the current CSRF - token using ``meta('csrfToken')``. diff --git a/en/appendices/5-2-migration-guide.rst b/en/appendices/5-2-migration-guide.rst deleted file mode 100644 index 66567f439a..0000000000 --- a/en/appendices/5-2-migration-guide.rst +++ /dev/null @@ -1,137 +0,0 @@ -5.2 Migration Guide -################### - -The 5.2.0 release is a backwards compatible with 5.0. It adds new functionality -and introduces new deprecations. Any functionality deprecated in 5.x will be -removed in 6.0.0. - -Behavior Changes -================ - -- ``ValidationSet::add()`` will now raise errors when a rule is added with - a name that is already defined. This change aims to prevent rules from being - overwritten by accident. -- ``Http\Session`` will now raise an exception when an invalid session preset is - used. -- ``FormProtectionComponent`` now raises ``Cake\Controller\Exception\FormProtectionException``. This - class is a subclass of ``BadRequestException``, and offers the benefit of - being filterable from logging. -- ``NumericPaginator::paginate()`` now uses the ``finder`` option even when a ``SelectQuery`` instance is passed to it. - -Deprecations -============ - -Console -------- - -- ``Arguments::getMultipleOption()`` is deprecated. Use ``getArrayOption()`` - instead. - -Datasource ----------- - -- The ability to cast an ``EntityInterface`` instance to string has been deprecated. - You should ``json_encode()`` the entity instead. - -- Mass assigning multiple entity fields using ``EntityInterface::set()`` is deprecated. - Use ``EntityInterface::patch()`` instead. For e.g. change usage like - ``$entity->set(['field1' => 'value1', 'field2' => 'value2'])`` to - ``$entity->patch(['field1' => 'value1', 'field2' => 'value2'])``. - -Event ------ - -- Returning values from event listeners / callbacks is deprecated. Use ``$event->setResult()`` - instead or ``$event->stopPropogation()`` to just stop the event propogation. - -View ----- - -- The ``errorClass`` option of ``FormHelper`` has been deprecated in favour of - using a template string. To upgrade move your ``errorClass`` definition to - a template set. See :ref:`customizing-templates`. - - -New Features -============ - -Console -------- - -- The ``cake counter_cache`` command was added. This command can be used to - regenerate counters for models that use ``CounterCacheBehavior``. -- ``ConsoleIntegrationTestTrait::debugOutput()`` makes it easier to debug - integration tests for console commands. -- ``ConsoleInputArgument`` now supports a ``separator`` option. This option - allows positional arguments to be delimited with a character sequence like - ``,``. CakePHP will split the positional argument into an array when arguments - are parsed. -- ``Arguments::getArrayArgumentAt()``, and ``Arguments::getArrayArgument()`` - were added. These methods allow you to read ``separator`` delimitered - positional arguments as arrays. -- ``ConsoleInputOption`` now supports a ``separator`` option. This option - allows option values to be delimited with a character sequence like - ``,``. CakePHP will split the option value into an array when arguments - are parsed. -- ``Arguments::getArrayArgumentAt()``, ``Arguments::getArrayArgument()``, and - ``Arguments::getArrayOption()`` - were added. These methods allow you to read ``separator`` delimitered - positional arguments as arrays. - -Database --------- - -- The ``nativeuuid`` type was added. This type enables ``uuid`` columns to be - used in Mysql connections with MariaDB. In all other drivers, ``nativeuuid`` - is an alias for ``uuid``. -- ``Cake\Database\Type\JsonType::setDecodingOptions()`` was added. This method - lets you define the value for the ``$flags`` argument of ``json_decode()``. -- ``CounterCacheBehavior::updateCounterCache()`` was added. This method allows - you to update the counter cache values for all records of the configured - associations. ``CounterCacheCommand`` was also added to do the same through the - console. -- ``Cake\Database\Driver::quote()`` was added. This method provides a way to - quote values to be used in SQL queries where prepared statements cannot be - used. - -Datasource ----------- - -- Application rules can now use ``Closure`` to define the validation message. - This allows you to create dynamic validation messages based on the entity - state and validation rule options. - -Error ------ - -- Custom exceptions can have specific error handling logic defined in - ``ErrorController``. - -ORM ---- - -- ``CounterCacheBehavior::updateCounterCache()`` has been added. This method - allows you to update the counter cache values for all records of the configured - associations. -- ``BelongsToMany::setJunctionProperty()`` and ``getJunctionProperty()`` were - added. These methods allow you to customize the ``_joinData`` property that is - used to hydrate junction table records. -- ``Table::findOrCreate()`` now accepts an array as second argument to directly pass data in. - -TestSuite ---------- - -- ``TestFixture::$strictFields`` was added. Enabling this property will make - fixtures raise an error if a fixture's record list contains fields that do not - exist in the schema. - -View ----- - -- ``FormHelper::deleteLink()`` has been added as convenience wrapper for delete links in - templates using ``DELETE`` method. -- ``HtmlHelper::importmap()`` was added. This method allows you to define - import maps for your JavaScript files. -- ``FormHelper`` now uses the ``containerClass`` template to apply a class to - the form control div. The default value is ``input``. - diff --git a/en/appendices/cakephp-development-process.rst b/en/appendices/cakephp-development-process.rst deleted file mode 100644 index aebec961b0..0000000000 --- a/en/appendices/cakephp-development-process.rst +++ /dev/null @@ -1,68 +0,0 @@ -CakePHP Development Process -########################### - -CakePHP projects broadly follow `semver `__. This means that: - -- Releases are numbered in the form of **A.B.C** -- **A** releases are *major releases*. They contain breaking changes and will - require non-trivial amounts of work to upgrade to from a lower **A** release. -- **A.B** releases are *feature releases*. Each version will be backwards - compatible but may introduce new deprecations. If a breaking change is - absolutely required it will be noted in the migration guide for that release. -- **A.B.C** releases are *patch* releases. They should be backwards compatible - with the previous patch release. The exception to this rule is if a security - issue is discovered and the only solution is to break an existing API. - -See the :doc:`/contributing/backwards-compatibility` for what we consider to be -backwards compatible and a breaking changes. - -Major Releases -============== - -Major releases introduce new features and can remove functionality deprecated in -an earlier release. These releases live in ``next`` branches that match their -version number such as ``5.next``. Once released they are promoted into ``master`` -and then ``5.next`` branch is used for future feature releases. - -Feature Releases -================ - -Feature releases are where new features or extensions to existing features are -shipped. Each release series receiving updates will have a ``next`` branch. For -example ``4.next``. If you would like to contribute a new feature please target -these branches. - -Patch Releases -============== - -Patch releases fix bugs in existing code/documentation and should always be -compatible with earlier patch releases from the same feature release. These -releases are created from the stable branches. Stable branches are often named -after the release series such as ``3.x``. - -Release Cadence -=============== - -- *Major Releases* are delivered approximately every two to three years. This timeframe - forces us to be deliberate and considerate with our breaking changes and gives - time for the community to keep up without feeling like they are being left - behind. -- *Feature Releases* are delivered every five to eight months. -- *Patch Releases* Are initially delivered every two weeks. As a feature release - matures this cadence relaxes to a monthly schedule. - -Deprecation Policy -================== - -Before a feature can be removed in a major release it needs to be deprecated. -When a behavior is deprecated in release **A.x** it will continue to work for -remainder of all **A.x** releases. Deprecations are generally indicated via PHP -warnings. You can enable deprecation warnings by adding ``E_USER_DEPRECATED`` to -your application's ``Error.level`` value. - -Once deprecated behavior is not removed until the next major release. For -example behavior deprecated in ``4.1`` will be removed in ``5.0``. - -.. meta:: - :title lang=en: CakePHP Development Process - :keywords lang=en: maintenance branch,community interaction,community feature,necessary feature,stable release,ticket system,advanced feature,power users,feature set,chat irc,leading edge,router,new features,members,attempt,development branches,branch development diff --git a/en/appendices/glossary.rst b/en/appendices/glossary.rst deleted file mode 100644 index a901484a1d..0000000000 --- a/en/appendices/glossary.rst +++ /dev/null @@ -1,105 +0,0 @@ -Glossary -######## - -.. glossary:: - - CDN - Content Delivery Network. A 3rd party vendor you can pay to help - distribute your content to data centers around the world. This helps - put your static assets closer to geographically distributed users. - - columns - Used in the ORM when referring to the table columns in an database - table. - - CSRF - Cross Site Request Forgery. Prevents replay attacks, double - submissions and forged requests from other domains. - - DI Container - In ``Application::services()`` you can configure application services - and their dependencies. Application services are automatically injected - into Controller actions, and Command Constructors. See - :doc:`/development/dependency-injection`. - - DSN - Data Source Name. A connection string format that is formed like a URI. - CakePHP supports DSNs for Cache, Database, Log and Email connections. - - dot notation - Dot notation defines an array path, by separating nested levels with ``.`` - For example:: - - Cache.default.engine - - Would point to the following value:: - - [ - 'Cache' => [ - 'default' => [ - 'engine' => 'File' - ] - ] - ] - - DRY - Don't repeat yourself. Is a principle of software development aimed at - reducing repetition of information of all kinds. In CakePHP DRY is used - to allow you to code things once and re-use them across your - application. - - fields - A generic term used to describe both entity properties, or database - columns. Often used in conjunction with the FormHelper. - - HTML attributes - An array of key => values that are composed into HTML attributes. For example:: - - // Given - ['class' => 'my-class', 'target' => '_blank'] - - // Would generate - class="my-class" target="_blank" - - If an option can be minimized or accepts its name as the value, then ``true`` - can be used:: - - // Given - ['checked' => true] - - // Would generate - checked="checked" - - PaaS - Platform as a Service. Platform as a Service providers will provide - cloud based hosting, database and caching resources. Some popular - providers include Heroku, EngineYard and PagodaBox - - properties - Used when referencing columns mapped onto an ORM entity. - - plugin syntax - Plugin syntax refers to the dot separated class name indicating classes - are part of a plugin:: - - // The plugin is "DebugKit", and the class name is "Toolbar". - 'DebugKit.Toolbar' - - // The plugin is "AcmeCorp/Tools", and the class name is "Toolbar". - 'AcmeCorp/Tools.Toolbar' - - routes.php - A file in ``config`` directory that contains routing configuration. - This file is included before each request is processed. - It should connect all the routes your application needs so - requests can be routed to the correct controller + action. - - routing array - An array of attributes that are passed to :php:meth:`Router::url()`. - They typically look like:: - - ['controller' => 'Posts', 'action' => 'view', 5] - -.. meta:: - :title lang=en: Glossary - :keywords lang=en: html attributes,array class,array controller,glossary glossary,target blank,fields,properties,columns,dot notation,routing configuration,forgery,replay,router,syntax,config,submissions diff --git a/en/appendices/migration-guides.rst b/en/appendices/migration-guides.rst deleted file mode 100644 index 2a86606039..0000000000 --- a/en/appendices/migration-guides.rst +++ /dev/null @@ -1,14 +0,0 @@ -Migration Guides -################ - -Migration guides contain information regarding the new features introduced in -each version and the migration path between 5.x minor releases. - -.. toctree:: - :maxdepth: 1 - - ./5-0-upgrade-guide - ./5-0-migration-guide - ./5-1-migration-guide - ./5-2-migration-guide - ./phpunit10 diff --git a/en/appendices/phpunit10.rst b/en/appendices/phpunit10.rst deleted file mode 100644 index 1cd56d3d3a..0000000000 --- a/en/appendices/phpunit10.rst +++ /dev/null @@ -1,65 +0,0 @@ -PHPUnit 10 Upgrade -################## - -With CakePHP 5 the minimum PHPUnit version has changed from ``^8.5 || ^9.3`` to ``^10.1``. -This introduces a few breaking changes from PHPUnit as well as from CakePHP's side. - -phpunit.xml adjustments -======================= - -It is recommended to let PHPUnit update its configuration file via the following command:: - - vendor/bin/phpunit --migrate-configuration - -.. note:: - - Make sure you are already on PHPUnit 10 via ``vendor/bin/phpunit --version`` before executing this command! - -With this command out of the way your ``phpunit.xml`` already has most of the recommended changes present. - -New event system ----------------- - -PHPUnit 10 removed the old hook system and introduced a new `Event system -`_ -which requires the following code in your ``phpunit.xml`` to be adjusted from:: - - - - - -to:: - - - - - -``->withConsecutive()`` has been removed -======================================== - -You can convert the removed ``->withConsecutive()`` method to a -working interim solution like you can see here:: - - ->withConsecutive(['firstCallArg'], ['secondCallArg']) - -should be converted to:: - - ->with( - ...self::withConsecutive(['firstCallArg'], ['secondCallArg']) - ) - -the static ``self::withConsecutive()`` method has been added via the ``Cake\TestSuite\PHPUnitConsecutiveTrait`` -to the base ``Cake\TestSuite\TestCase`` class so you don't have to manually add that trait to your Testcase classes. - -data providers have to be static -================================ - -If your testcases leverage the data provider feature of PHPUnit then -you have to adjust your data providers to be static:: - - public function myProvider(): array - -should be converted to:: - - public static function myProvider(): array - diff --git a/en/bake.rst b/en/bake.rst deleted file mode 100644 index e72d85bf25..0000000000 --- a/en/bake.rst +++ /dev/null @@ -1,4 +0,0 @@ -Bake Console -############ - -This page has `moved `__. diff --git a/en/bake/development.rst b/en/bake/development.rst deleted file mode 100644 index bc8928e0a7..0000000000 --- a/en/bake/development.rst +++ /dev/null @@ -1,4 +0,0 @@ -Extending Bake -############## - -This page has `moved `__. diff --git a/en/bake/usage.rst b/en/bake/usage.rst deleted file mode 100644 index 11f3f0eef3..0000000000 --- a/en/bake/usage.rst +++ /dev/null @@ -1,4 +0,0 @@ -Code Generation with Bake -######################### - -This page has `moved `__. diff --git a/en/chronos.rst b/en/chronos.rst deleted file mode 100644 index 727f82ced5..0000000000 --- a/en/chronos.rst +++ /dev/null @@ -1,4 +0,0 @@ -Chronos -======= - -This page has `moved `__. diff --git a/en/console-commands.rst b/en/console-commands.rst deleted file mode 100644 index c40f966c9c..0000000000 --- a/en/console-commands.rst +++ /dev/null @@ -1,183 +0,0 @@ -Console Commands -################ - -.. php:namespace:: Cake\Console - -In addition to a web framework, CakePHP also provides a console framework for -creating command line tools & applications. Console applications are ideal for -handling a variety of background & maintenance tasks that leverage your existing -application configuration, models, plugins and domain logic. - -CakePHP provides several console tools for interacting with CakePHP features -like i18n and routing that enable you to introspect your application and -generate related files. - -The CakePHP Console -=================== - -The CakePHP Console uses a dispatcher-type system to load commands, parse -their arguments and invoke the correct command. While the examples below use -bash the CakePHP console is compatible with any \*nix shell and windows. - -A CakePHP application contains **src/Command** directory that contain its commands. -It also comes with an executable in the **bin** directory: - -.. code-block:: console - - $ cd /path/to/app - $ bin/cake - -.. note:: - - For Windows, the command needs to be ``bin\cake`` (note the backslash). - -Running the Console with no arguments will list out available commands. You -could then run the any of the listed commands by using its name: - -.. code-block:: console - - # run server command - bin/cake server - - # run migrations command - bin/cake migrations -h - - # run bake (with plugin prefix) - bin/cake bake.bake -h - -Plugin commands can be invoked without a plugin prefix if the commands's name -does not overlap with an application or framework command. In the case that two -plugins provide a command with the same name, the first loaded plugin will get -the short alias. You can always use the ``plugin.command`` format to -unambiguously reference a command. - -Console Applications -==================== - -By default CakePHP will automatically discover all the commands in your -application and its plugins. You may want to reduce the number of exposed -commands, when building standalone console applications. You can use your -``Application``'s ``console()`` hook to limit which commands are exposed and -rename commands that are exposed:: - - // in src/Application.php - namespace App; - - use App\Command\UserCommand; - use App\Command\VersionCommand; - use Cake\Console\CommandCollection; - use Cake\Http\BaseApplication; - - class Application extends BaseApplication - { - public function console(CommandCollection $commands): CommandCollection - { - // Add by classname - $commands->add('user', UserCommand::class); - - // Add instance - $commands->add('version', new VersionCommand()); - - return $commands; - } - } - -In the above example, the only commands available would be ``help``, ``version`` -and ``user``. See the :ref:`plugin-commands` section for how to add commands in -your plugins. - -.. note:: - - When adding multiple commands that use the same Command class, the ``help`` - command will display the shortest option. - -.. _renaming-commands: -.. index:: nested commands, subcommands - -Renaming Commands -================= - -There are cases where you will want to rename commands, to create nested -commands or subcommands. While the default auto-discovery of commands will not -do this, you can register your commands to create any desired naming. - -You can customize the command names by defining each command in your plugin:: - - public function console(CommandCollection $commands): CommandCollection - { - // Add commands with nested naming - $commands->add('user dump', UserDumpCommand::class); - $commands->add('user:show', UserShowCommand::class); - - // Rename a command entirely - $commands->add('lazer', UserDeleteCommand::class); - - return $commands; - } - -When overriding the ``console()`` hook in your application, remember to -call ``$commands->autoDiscover()`` to add commands from CakePHP, your -application, and plugins. - -If you need to rename/remove any attached commands, you can use the -``Console.buildCommands`` event on your application event manager to modify the -available commands. - -Commands -======== - -See the :doc:`/console-commands/commands` chapter on how to create your first -command. Then learn more about commands: - -.. toctree:: - :maxdepth: 1 - - console-commands/commands - console-commands/input-output - console-commands/option-parsers - console-commands/cron-jobs - -CakePHP Provided Commands -========================= - -.. toctree:: - :maxdepth: 1 - - console-commands/cache - console-commands/completion - console-commands/counter-cache - console-commands/i18n - console-commands/plugin - console-commands/schema-cache - console-commands/routes - console-commands/server - console-commands/repl - -Routing in the Console Environment -================================== - -In command-line interface (CLI), specifically your console commands, -``env('HTTP_HOST')`` and other webbrowser specific environment variables are not -set. - -If you generate reports or send emails that make use of ``Router::url()`` those -will contain the default host ``http://localhost/`` and thus resulting in -invalid URLs. In this case you need to specify the domain manually. -You can do that using the Configure value ``App.fullBaseUrl`` from your -bootstrap or config, for example. - -For sending emails, you should provide Email class with the host you want to -send the email with:: - - use Cake\Mailer\Email; - - $email = new Email(); - $email->setDomain('www.example.org'); - -This asserts that the generated message IDs are valid and fit to the domain the -emails are sent from. - - -.. meta:: - :title lang=en: Shells, Tasks & Console Tools - :keywords lang=en: shell scripts,system shell,application classes,background tasks,line script,cron job,request response,system path,acl,new projects,commands,specifics,parameters,i18n,cakephp,directory,maintenance,ideal,applications,mvc diff --git a/en/console-commands/cache.rst b/en/console-commands/cache.rst deleted file mode 100644 index 4bb1ea3761..0000000000 --- a/en/console-commands/cache.rst +++ /dev/null @@ -1,14 +0,0 @@ -Cache Tool -########## - -To help you better manage cached data from a CLI environment, a console command -is available for clearing cached data your application has:: - - // Clear one cache config - bin/cake cache clear - - // Clear all cache configs - bin/cake cache clear_all - - // Clear one cache group - bin/cake cache clear_group diff --git a/en/console-commands/commands.rst b/en/console-commands/commands.rst deleted file mode 100644 index 9493a24b5c..0000000000 --- a/en/console-commands/commands.rst +++ /dev/null @@ -1,574 +0,0 @@ -Command Objects -############### - -.. php:namespace:: Cake\Console -.. php:class:: Command - -CakePHP comes with a number of built-in commands for speeding up your -development, and automating routine tasks. You can use these same libraries to -create commands for your application and plugins. - -Creating a Command -================== - -Let's create our first Command. For this example, we'll create a -simple Hello world command. In your application's **src/Command** directory create -**HelloCommand.php**. Put the following code inside it:: - - out('Hello world.'); - - return static::CODE_SUCCESS; - } - } - -Command classes must implement an ``execute()`` method that does the bulk of -their work. This method is called when a command is invoked. Lets call our first -command application directory, run: - -.. code-block:: console - - bin/cake hello - -You should see the following output:: - - Hello world. - -Our ``execute()`` method isn't very interesting let's read some input from the -command line:: - - addArgument('name', [ - 'help' => 'What is your name', - ]); - - return $parser; - } - - public function execute(Arguments $args, ConsoleIo $io): int - { - $name = $args->getArgument('name'); - $io->out("Hello {$name}."); - - return static::CODE_SUCCESS; - } - } - - -After saving this file, you should be able to run the following command: - -.. code-block:: console - - bin/cake hello jillian - - # Outputs - Hello jillian - -Changing the Default Command Name -================================= - -CakePHP will use conventions to generate the name your commands use on the -command line. If you want to overwrite the generated name implement the -``defaultName()`` method in your command:: - - public static function defaultName(): string - { - return 'oh_hi'; - } - -The above would make our ``HelloCommand`` accessible by ``cake oh_hi`` instead -of ``cake hello``. - -Defining Arguments and Options -============================== - -As we saw in the last example, we can use the ``buildOptionParser()`` hook -method to define arguments. We can also define options. For example, we could -add a ``yell`` option to our ``HelloCommand``:: - - // ... - protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser - { - $parser - ->addArgument('name', [ - 'help' => 'What is your name', - ]) - ->addOption('yell', [ - 'help' => 'Shout the name', - 'boolean' => true, - ]); - - return $parser; - } - - public function execute(Arguments $args, ConsoleIo $io): int - { - $name = $args->getArgument('name'); - if ($args->getOption('yell')) { - $name = mb_strtoupper($name); - } - $io->out("Hello {$name}."); - - return static::CODE_SUCCESS; - } - -See the :doc:`/console-commands/option-parsers` section for more information. - -Creating Output -=============== - -Commands are provided a ``ConsoleIo`` instance when executed. This object allows -you to interact with ``stdout``, ``stderr`` and create files. See the -:doc:`/console-commands/input-output` section for more information. - -Using Models in Commands -======================== - -You'll often need access to your application's business logic in console -commands. You can load models in commands, just as you would in a controller -using ``$this->fetchTable()`` since command use the ``LocatorAwareTrait``:: - - addArgument('name', [ - 'help' => 'What is your name' - ]); - - return $parser; - } - - public function execute(Arguments $args, ConsoleIo $io): int - { - $name = $args->getArgument('name'); - $user = $this->fetchTable()->findByUsername($name)->first(); - - $io->out(print_r($user, true)); - - return static::CODE_SUCCESS; - } - } - -The above command, will fetch a user by username and display the information -stored in the database. - -Exit Codes and Stopping Execution -================================= - -When your commands hit an unrecoverable error you can use the ``abort()`` method -to terminate execution:: - - // ... - public function execute(Arguments $args, ConsoleIo $io): int - { - $name = $args->getArgument('name'); - if (strlen($name) < 5) { - // Halt execution, output to stderr, and set exit code to 1 - $io->error('Name must be at least 4 characters long.'); - $this->abort(); - } - - return static::CODE_SUCCESS; - } - -You can also use ``abort()`` on the ``$io`` object to emit a message and code:: - - public function execute(Arguments $args, ConsoleIo $io): int - { - $name = $args->getArgument('name'); - if (strlen($name) < 5) { - // Halt execution, output to stderr, and set exit code to 99 - $io->abort('Name must be at least 4 characters long.', 99); - } - - return static::CODE_SUCCESS; - } - -You can pass any desired exit code into ``abort()``. - -.. tip:: - - Avoid exit codes 64 - 78, as they have specific meanings described by - ``sysexits.h``. Avoid exit codes above 127, as these are used to indicate - process exit by signal, such as SIGKILL or SIGSEGV. - - You can read more about conventional exit codes in the sysexit manual page - on most Unix systems (``man sysexits``), or the ``System Error Codes`` help - page in Windows. - -Calling other Commands -====================== - -You may need to call other commands from your command. You can use -``executeCommand`` to do that:: - - // You can pass an array of CLI options and arguments. - $this->executeCommand(OtherCommand::class, ['--verbose', 'deploy']); - - // Can pass an instance of the command if it has constructor args - $command = new OtherCommand($otherArgs); - $this->executeCommand($command, ['--verbose', 'deploy']); - -.. note:: - - When calling ``executeCommand()`` in a loop, it is recommended to pass in the - parent command's ``ConsoleIo`` instance as the optional 3rd argument to - avoid a potential "open files" limit that could occur in some environments. - -Setting Command Description -=========================== - -You may want to set a command description via:: - - class UserCommand extends Command - { - public static function getDescription(): string - { - return 'My custom description'; - } - } - -This will show your description in the Cake CLI: - -.. code-block:: console - - bin/cake - - App: - - user - └─── My custom description - -As well as in the help section of your command: - -.. code-block:: console - - cake user --help - My custom description - - Usage: - cake user [-h] [-q] [-v] - -.. _console-integration-testing: - -Testing Commands -================ - -To make testing console applications easier, CakePHP comes with a -``ConsoleIntegrationTestTrait`` trait that can be used to test console applications -and assert against their results. - -To get started testing your console application, create a test case that uses the -``Cake\TestSuite\ConsoleIntegrationTestTrait`` trait. This trait contains a method -``exec()`` that is used to execute your command. You can pass the same string -you would use in the CLI to this method. - -Let's start with a very simple command, located in -**src/Command/UpdateTableCommand.php**:: - - namespace App\Command; - - use Cake\Command\Command; - use Cake\Console\Arguments; - use Cake\Console\ConsoleIo; - use Cake\Console\ConsoleOptionParser; - - class UpdateTableCommand extends Command - { - protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser - { - $parser->setDescription('My cool console app'); - - return $parser; - } - } - -To write an integration test for this command, we would create a test case in -**tests/TestCase/Command/UpdateTableTest.php** that uses the -``Cake\TestSuite\ConsoleIntegrationTestTrait`` trait. This command doesn't do much at the -moment, but let's just test that our command's description is displayed in ``stdout``:: - - namespace App\Test\TestCase\Command; - - use Cake\TestSuite\ConsoleIntegrationTestTrait; - use Cake\TestSuite\TestCase; - - class UpdateTableCommandTest extends TestCase - { - use ConsoleIntegrationTestTrait; - - public function testDescriptionOutput() - { - $this->exec('update_table --help'); - $this->assertOutputContains('My cool console app'); - } - } - -Our test passes! While this is very trivial example, it shows that creating an -integration test case for console applications can follow command line -conventions. Let's continue by adding more logic to our command:: - - namespace App\Command; - - use Cake\Command\Command; - use Cake\Console\Arguments; - use Cake\Console\ConsoleIo; - use Cake\Console\ConsoleOptionParser; - use Cake\I18n\DateTime; - - class UpdateTableCommand extends Command - { - protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser - { - $parser - ->setDescription('My cool console app') - ->addArgument('table', [ - 'help' => 'Table to update', - 'required' => true - ]); - - return $parser; - } - - public function execute(Arguments $args, ConsoleIo $io): int - { - $table = $args->getArgument('table'); - $this->fetchTable($table)->updateQuery() - ->set([ - 'modified' => new DateTime() - ]) - ->execute(); - - return static::CODE_SUCCESS; - } - } - -This is a more complete command that has required options and relevant logic. -Modify your test case to the following snippet of code:: - - namespace Cake\Test\TestCase\Command; - - use Cake\Command\Command; - use Cake\I18n\DateTime; - use Cake\TestSuite\ConsoleIntegrationTestTrait; - use Cake\TestSuite\TestCase; - - class UpdateTableCommandTest extends TestCase - { - use ConsoleIntegrationTestTrait; - - protected $fixtures = [ - // assumes you have a UsersFixture - 'app.Users', - ]; - - public function testDescriptionOutput() - { - $this->exec('update_table --help'); - $this->assertOutputContains('My cool console app'); - } - - public function testUpdateModified() - { - $now = new DateTime('2017-01-01 00:00:00'); - DateTime::setTestNow($now); - - $this->loadFixtures('Users'); - - $this->exec('update_table Users'); - $this->assertExitCode(Command::CODE_SUCCESS); - - $user = $this->getTableLocator()->get('Users')->get(1); - $this->assertSame($user->modified->timestamp, $now->timestamp); - - DateTime::setTestNow(null); - } - } - -As you can see from the ``testUpdateModified`` method, we are testing that our -command updates the table that we are passing as the first argument. First, we -assert that the command exited with the proper status code, ``0``. Then we check -that our command did its work, that is, updated the table we provided and set -the ``modified`` column to the current time. - -Remember, ``exec()`` will take the same string you type into your CLI, so you -can include options and arguments in your command string. - -Testing Interactive Commands ----------------------------- - -Consoles are often interactive. Testing interactive commands with the -``Cake\TestSuite\ConsoleIntegrationTestTrait`` trait only requires passing the -inputs you expect as the second parameter of ``exec()``. They should be -included as an array in the order that you expect them. - -Continuing with our example command, let's add an interactive confirmation. -Update the command class to the following:: - - namespace App\Command; - - use Cake\Command\Command; - use Cake\Console\Arguments; - use Cake\Console\ConsoleIo; - use Cake\Console\ConsoleOptionParser; - use Cake\I18n\DateTime; - - class UpdateTableCommand extends Command - { - protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser - { - $parser - ->setDescription('My cool console app') - ->addArgument('table', [ - 'help' => 'Table to update', - 'required' => true - ]); - - return $parser; - } - - public function execute(Arguments $args, ConsoleIo $io): int - { - $table = $args->getArgument('table'); - if ($io->ask('Are you sure?', 'n', ['y', 'n']) !== 'y') { - $io->error('You need to be sure.'); - $this->abort(); - } - $this->fetchTable($table)->updateQuery() - ->set([ - 'modified' => new DateTime() - ]) - ->execute(); - - return static::CODE_SUCCESS; - } - } - -Now that we have an interactive command, we can add a test case that tests -that we receive the proper response, and one that tests that we receive an -incorrect response. Remove the ``testUpdateModified`` method and, add the following methods to -**tests/TestCase/Command/UpdateTableCommandTest.php**:: - - - public function testUpdateModifiedSure() - { - $now = new DateTime('2017-01-01 00:00:00'); - DateTime::setTestNow($now); - - $this->loadFixtures('Users'); - - $this->exec('update_table Users', ['y']); - $this->assertExitCode(Command::CODE_SUCCESS); - - $user = $this->getTableLocator()->get('Users')->get(1); - $this->assertSame($user->modified->timestamp, $now->timestamp); - - DateTime::setTestNow(null); - } - - public function testUpdateModifiedUnsure() - { - $user = $this->getTableLocator()->get('Users')->get(1); - $original = $user->modified->timestamp; - - $this->exec('my_console best_framework', ['n']); - $this->assertExitCode(Command::CODE_ERROR); - $this->assertErrorContains('You need to be sure.'); - - $user = $this->getTableLocator()->get('Users')->get(1); - $this->assertSame($original, $user->timestamp); - } - -In the first test case, we confirm the question, and records are updated. In the -second test we don't confirm and records are not updated, and we can check that -our error message was written to ``stderr``. - -Assertion methods ------------------ - -The ``Cake\TestSuite\ConsoleIntegrationTestTrait`` trait provides a number of -assertion methods that make help assert against console output:: - - // assert that the command exited as success - $this->assertExitSuccess(); - - // assert that the command exited as an error - $this->assertExitError(); - - // assert that the command exited with the expected code - $this->assertExitCode($expected); - - // assert that stdout contains a string - $this->assertOutputContains($expected); - - // assert that stderr contains a string - $this->assertErrorContains($expected); - - // assert that stdout matches a regular expression - $this->assertOutputRegExp($expected); - - // assert that stderr matches a regular expression - $this->assertErrorRegExp($expected); - -Debug Helpers -------------- - -You can use ``debugOutput()`` to output the exit code, stdout and stderr of the -last run command:: - - $this->exec('update_table Users'); - $this->assertExitCode(Command::CODE_SUCCESS); - $this->debugOutput(); - -.. versionadded:: 4.2.0 - The ``debugOutput()`` method was added. - - -Lifecycle Callbacks -=================== - -Like Controllers, Commands offer lifecycle events that allow you to observe -the framework calling your application code. Commands have: - -- ``Command.beforeExecute`` Is called before a command's ``execute()`` method - is. The event is passed the ``ConsoleArguments`` parameter as ``args``. This - event cannot be stopped or have its result replaced. -- ``Command.afterExecute`` Is called after a command's ``execute()`` method is - complete. The event contains ``ConsoleArguments`` as ``args`` and the command - result as ``result``. This event cannot be stopped or have its result - replaced. diff --git a/en/console-commands/completion.rst b/en/console-commands/completion.rst deleted file mode 100644 index c197d5e4a2..0000000000 --- a/en/console-commands/completion.rst +++ /dev/null @@ -1,187 +0,0 @@ -Completion Tool -################ - -Working with the console gives the developer a lot of possibilities but having -to completely know and write those commands can be tedious. Especially when -developing new shells where the commands differ per minute iteration. The -Completion Shells aids in this matter by providing an API to write completion -scripts for shells like bash, zsh, fish etc. - -Sub Commands -============ - -The Completion Shell consists of a number of sub commands to assist the -developer creating its completion script. Each for a different step in the -autocompletion process. - -Commands --------- - -For the first step commands outputs the available Shell Commands, including -plugin name when applicable. (All returned possibilities, for this and the other -sub commands, are separated by a space.) For example:: - - bin/cake Completion commands - -Returns:: - - acl api bake command_list completion console i18n schema server test testsuite upgrade - -Your completion script can select the relevant commands from that list to -continue with. (For this and the following sub commands.) - -subCommands ------------ - -Once the preferred command has been chosen subCommands comes in as the second -step and outputs the possible sub command for the given shell command. For -example:: - - bin/cake Completion subcommands bake - -Returns:: - - controller db_config fixture model plugin project test view - -options -------- - -As the third and final options outputs options for the given (sub) command as -set in getOptionParser. (Including the default options inherited from Shell.) -For example:: - - bin/cake Completion options bake - -Returns:: - - --help -h --verbose -v --quiet -q --everything --connection -c --force -f --plugin -p --prefix --theme -t - -You can also pass an additional argument being the shell sub-command : it will -output the specific options of this sub-command. - -How to enable Bash autocompletion for the CakePHP Console -========================================================= - -First, make sure the **bash-completion** library is installed. If not, you do it -with the following command:: - - apt-get install bash-completion - -Create a file named **cake** in **/etc/bash_completion.d/** and put the -:ref:`bash-completion-file-content` inside it. - -Save the file, then restart your console. - -.. note:: - - If you are using MacOS X, you can install the **bash-completion** library - using **homebrew** with the command ``brew install bash-completion``. - The target directory for the **cake** file will be - **/usr/local/etc/bash_completion.d/**. - -.. _bash-completion-file-content: - -Bash Completion file content ----------------------------- - -This is the code you need to put inside the **cake** file in the correct location -in order to get autocompletion when using the CakePHP console: - -.. code-block:: bash - - # - # Bash completion file for CakePHP console - # - - _cake() - { - local cur prev opts cake - COMPREPLY=() - cake="${COMP_WORDS[0]}" - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" - - if [[ "$cur" == -* ]] ; then - if [[ ${COMP_CWORD} = 1 ]] ; then - opts=$(${cake} Completion options) - elif [[ ${COMP_CWORD} = 2 ]] ; then - opts=$(${cake} Completion options "${COMP_WORDS[1]}") - else - opts=$(${cake} Completion options "${COMP_WORDS[1]}" "${COMP_WORDS[2]}") - fi - - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - return 0 - fi - - if [[ ${COMP_CWORD} = 1 ]] ; then - opts=$(${cake} Completion commands) - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - return 0 - fi - - if [[ ${COMP_CWORD} = 2 ]] ; then - opts=$(${cake} Completion subcommands $prev) - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - if [[ $COMPREPLY = "" ]] ; then - _filedir - return 0 - fi - return 0 - fi - - opts=$(${cake} Completion fuzzy "${COMP_WORDS[@]:1}") - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - if [[ $COMPREPLY = "" ]] ; then - _filedir - return 0 - fi - return 0; - } - - complete -F _cake cake bin/cake - -Using autocompletion -==================== - -Once enabled, the autocompletion can be used the same way than for other -built-in commands, using the **TAB** key. -Three type of autocompletion are provided. The following output are from a fresh CakePHP install. - -Commands --------- - -Sample output for commands autocompletion: - -.. code-block:: console - - $ bin/cake - bake i18n schema_cache routes - console migrations plugin server - -Subcommands ------------ - -Sample output for subcommands autocompletion: - -.. code-block:: console - - $ bin/cake bake - behavior helper command - cell mailer command_helper - component migration template - controller migration_snapshot test - fixture model - form plugin - -Options -------- - -Sample output for subcommands options autocompletion: - -.. code-block:: console - - $ bin/cake bake - - -c --everything --force --help --plugin -q -t -v - --connection -f -h -p --prefix --quiet --theme --verbose - diff --git a/en/console-commands/counter-cache.rst b/en/console-commands/counter-cache.rst deleted file mode 100644 index cc50932bcf..0000000000 --- a/en/console-commands/counter-cache.rst +++ /dev/null @@ -1,24 +0,0 @@ -CounterCache Tool -################# - -The CounterCacheCommand provides a CLI tool for rebuilding the counter caches -in your application and plugin models. It can be used in maintenance and -recovery operations, or to populate new counter caches added to your -application. - -.. code-block:: console - - bin/cake counter_cache --assoc Comments Articles - -This would rebuild the ``Comments`` related counters on the ``Articles`` table. -For very large tables you may need to rebuild counters in batches. You can use -the ``--limit`` and ``--page`` options to incrementally rebuild counter state. - -.. code-block:: console - - bin/cake counter_cache --assoc Comments --limit 100 --page 2 Articles - -When ``limit`` and ``page`` are used, records will be ordered by the table's -primary key. - -.. versionadded:: 5.2.0 diff --git a/en/console-commands/cron-jobs.rst b/en/console-commands/cron-jobs.rst deleted file mode 100644 index 6b29f9d01a..0000000000 --- a/en/console-commands/cron-jobs.rst +++ /dev/null @@ -1,43 +0,0 @@ -Running Shells as Cron Jobs -########################### - -A common thing to do with a shell is making it run as a cronjob to -clean up the database once in a while or send newsletters. This is -trivial to setup, for example:: - - */5 * * * * cd /full/path/to/root && bin/cake myshell myparam - # * * * * * command to execute - # │ │ │ │ │ - # │ │ │ │ │ - # │ │ │ │ \───── day of week (0 - 6) (0 to 6 are Sunday to Saturday, - # | | | | or use names) - # │ │ │ \────────── month (1 - 12) - # │ │ \─────────────── day of month (1 - 31) - # │ \──────────────────── hour (0 - 23) - # \───────────────────────── min (0 - 59) - -You can see more info here: https://en.wikipedia.org/wiki/Cron - -.. tip:: - - Use ``-q`` (or `--quiet`) to silence any output for cronjobs. - -Cron Jobs on Shared Hosting ---------------------------- - -On some shared hostings ``cd /full/path/to/root && bin/cake mycommand myparam`` -might not work. Instead you can use -``php /full/path/to/root/bin/cake.php mycommand myparam``. - -.. note:: - - register_argc_argv has to be turned on by including ``register_argc_argv - = 1`` in your php.ini. If you cannot change register_argc_argv globally, - you can tell the cron job to use your own configuration by - specifying it with ``-d register_argc_argv=1`` parameter. Example: ``php - -d register_argc_argv=1 /full/path/to/root/bin/cake.php myshell - myparam`` - -.. meta:: - :title lang=en: Running Shells as cronjobs - :keywords lang=en: cronjob,bash script,crontab diff --git a/en/console-commands/i18n.rst b/en/console-commands/i18n.rst deleted file mode 100644 index 4cd828bfdb..0000000000 --- a/en/console-commands/i18n.rst +++ /dev/null @@ -1,92 +0,0 @@ -I18N Tool -######### - -The i18n features of CakePHP use `po files `_ -as their translation source. PO files integrate with commonly used translation tools -like `Poedit `_. - -The i18n commands provides a quick way to generate po template files. -These templates files can then be given to translators so they can translate the -strings in your application. Once you have translations done, pot files can be -merged with existing translations to help update your translations. - -Generating POT Files -==================== - -POT files can be generated for an existing application using the ``extract`` -command. This command will scan your entire application for ``__()`` style -function calls, and extract the message string. Each unique string in your -application will be combined into a single POT file: - -.. code-block:: console - - bin/cake i18n extract - -The above will run the extraction command. The result of this command will be the -file **resources/locales/default.pot**. You use the pot file as a template for creating -po files. If you are manually creating po files from the pot file, be sure to -correctly set the ``Plural-Forms`` header line. - -Generating POT Files for Plugins --------------------------------- - -You can generate a POT file for a specific plugin using: - -.. code-block:: console - - bin/cake i18n extract --plugin - -This will generate the required POT files used in the plugins. - -Extracting from multiple folders at once ----------------------------------------- - -Sometimes, you might need to extract strings from more than one directory of -your application. For instance, if you are defining some strings in the -``config`` directory of your application, you probably want to extract strings -from this directory as well as from the ``src`` directory. You can do it by -using the ``--paths`` option. It takes a comma-separated list of absolute paths -to extract: - -.. code-block:: console - - bin/cake i18n extract --paths /var/www/app/config,/var/www/app/src - -Excluding Folders ------------------ - -You can pass a comma separated list of folders that you wish to be excluded. -Any path containing a path segment with the provided values will be ignored: - -.. code-block:: console - - bin/cake i18n extract --exclude vendor,tests - -Skipping Overwrite Warnings for Existing POT Files --------------------------------------------------- - -By adding ``--overwrite``, the shell script will no longer warn you if a POT -file already exists and will overwrite by default: - -.. code-block:: console - - bin/cake i18n extract --overwrite - -Extracting Messages from the CakePHP Core Libraries ---------------------------------------------------- - -By default, the extract shell script will ask you if you like to extract -the messages used in the CakePHP core libraries. Set ``--extract-core`` to yes -or no to set the default behavior: - -.. code-block:: console - - bin/cake i18n extract --extract-core yes - - // or - - bin/cake i18n extract --extract-core no - -.. meta:: - :title lang=en: I18N command - :keywords lang=en: pot files,locale default,translation tools,message string,app locale,php class,validation,i18n,translations,command,models diff --git a/en/console-commands/input-output.rst b/en/console-commands/input-output.rst deleted file mode 100644 index 3d03c3cd03..0000000000 --- a/en/console-commands/input-output.rst +++ /dev/null @@ -1,375 +0,0 @@ -Command Input/Output -#################### - -.. php:namespace:: Cake\Console -.. php:class:: ConsoleIo - -CakePHP provides the ``ConsoleIo`` object to commands so that they can -interactively read user input and output information to the user. - -.. _command-helpers: - -Command Helpers -=============== - -Command Helpers can be accessed and used from any command:: - - // Output some data as a table. - $io->helper('Table')->output($data); - - // Get a helper from a plugin. - $io->helper('Plugin.HelperName')->output($data); - -You can also get instances of helpers and call any public methods on them:: - - // Get and use the Progress Helper. - $progress = $io->helper('Progress'); - $progress->increment(10); - $progress->draw(); - -Creating Helpers -================ - -While CakePHP comes with a few command helpers you can create more in your -application or plugins. As an example, we'll create a simple helper to generate -fancy headings. First create the **src/Command/Helper/HeadingHelper.php** and put -the following in it:: - - _io->out($marker . ' ' . $args[0] . ' ' . $marker); - } - } - -We can then use this new helper in one of our shell commands by calling it:: - - // With ### on either side - $this->helper('Heading')->output(['It works!']); - - // With ~~~~ on either side - $this->helper('Heading')->output(['It works!', '~', 4]); - -Helpers generally implement the ``output()`` method which takes an array of -parameters. However, because Console Helpers are vanilla classes they can -implement additional methods that take any form of arguments. - -.. note:: - Helpers can also live in ``src/Shell/Helper`` for backwards compatibility. - -Built-In Helpers -================ - -Table Helper ------------- - -The TableHelper assists in making well formatted ASCII art tables. Using it is -pretty simple:: - - $data = [ - ['Header 1', 'Header', 'Long Header'], - ['short', 'Longish thing', 'short'], - ['Longer thing', 'short', 'Longest Value'], - ]; - $io->helper('Table')->output($data); - - // Outputs - +--------------+---------------+---------------+ - | Header 1 | Header | Long Header | - +--------------+---------------+---------------+ - | short | Longish thing | short | - | Longer thing | short | Longest Value | - +--------------+---------------+---------------+ - -You can use the ```` formatting tag in tables to right align -content:: - - $data = [ - ['Name', 'Total Price'], - ['Cake Mix', '1.50'], - ]; - $io->helper('Table')->output($data); - - // Outputs - +----------+-------------+ - | Name 1 | Total Price | - +----------+-------------+ - | Cake Mix | 1.50 | - +----------+-------------+ - -Progress Helper ---------------- - -The ProgressHelper can be used in two different ways. The simple mode lets you -provide a callback that is invoked until the progress is complete:: - - $io->helper('Progress')->output(['callback' => function ($progress) { - // Do work here. - $progress->increment(20); - $progress->draw(); - }]); - -You can control the progress bar more by providing additional options: - -- ``total`` The total number of items in the progress bar. Defaults - to 100. -- ``width`` The width of the progress bar. Defaults to 80. -- ``callback`` The callback that will be called in a loop to advance the - progress bar. - -An example of all the options in use would be:: - - $io->helper('Progress')->output([ - 'total' => 10, - 'width' => 20, - 'callback' => function ($progress) { - $progress->increment(2); - $progress->draw(); - } - ]); - -The progress helper can also be used manually to increment and re-render the -progress bar as necessary:: - - $progress = $io->helper('Progress'); - $progress->init([ - 'total' => 10, - 'width' => 20, - ]); - - $progress->increment(4); - $progress->draw(); - -Banner Helper -------------- - -The ``BannerHelper`` can be used to format one or more lines of text into -a banner with a background and horizontal padding:: - - $io->helper('Banner') - ->withPadding(5) - ->withStyle('success.bg') - ->output(['Work complete']); - -.. versionadded:: 5.1.0 - The ``BannerHelper`` was added in 5.1 - -Getting User Input -================== - -.. php:method:: ask($question, $choices = null, $default = null) - -When building interactive console applications you'll need to get user input. -CakePHP provides a way to do this:: - - // Get arbitrary text from the user. - $color = $io->ask('What color do you like?'); - - // Get a choice from the user. - $selection = $io->askChoice('Red or Green?', ['R', 'G'], 'R'); - -Selection validation is case-insensitive. - -Creating Files -============== - -.. php:method:: createFile($path, $contents) - -Creating files is often important part of many console commands that help -automate development and deployment. The ``createFile()`` method gives you -a simple interface for creating files with interactive confirmation:: - - // Create a file with confirmation on overwrite - $io->createFile('bower.json', $stuff); - - // Force overwriting without asking - $io->createFile('bower.json', $stuff, true); - -Creating Output -=============== - -.. php:method:out($message, $newlines, $level) -.. php:method:err($message, $newlines) - -Writing to ``stdout`` and ``stderr`` is another common operation in CakePHP:: - - // Write to stdout - $io->out('Normal message'); - - // Write to stderr - $io->err('Error message'); - -In addition to vanilla output methods, CakePHP provides wrapper methods that -style output with appropriate ANSI colors:: - - // Green text on stdout - $io->success('Success message'); - - // Cyan text on stdout - $io->info('Informational text'); - - // Blue text on stdout - $io->comment('Additional context'); - - // Red text on stderr - $io->error('Error text'); - - // Yellow text on stderr - $io->warning('Warning text'); - -Color formatting will automatically be disabled if ``posix_isatty`` returns -true, or if the ``NO_COLOR`` environment variable is set. - -``ConsoleIo`` provides two convenience methods regarding the output level:: - - // Would only appear when verbose output is enabled (-v) - $io->verbose('Verbose message'); - - // Would appear at all levels. - $io->quiet('Quiet message'); - -You can also create blank lines or draw lines of dashes:: - - // Output 2 newlines - $io->out($io->nl(2)); - - // Draw a horizontal line - $io->hr(); - -Lastly, you can update the current line of text on the screen:: - - $io->out('Counting down'); - $io->out('10', 0); - for ($i = 9; $i > 0; $i--) { - sleep(1); - $io->overwrite($i, 0, 2); - } - -.. note:: - It is important to remember, that you cannot overwrite text - once a new line has been output. - -.. _shell-output-level: - -Output Levels -============= - -Console applications often need different levels of verbosity. For example, when -running as a cron job, most output is un-necessary. You can use output levels to -flag output appropriately. The user of the shell, can then decide what level of -detail they are interested in by setting the correct flag when calling the -command. There are 3 levels: - -* ``QUIET`` - Only absolutely important information should be marked for quiet - output. -* ``NORMAL`` - The default level, and normal usage. -* ``VERBOSE`` - Mark messages that may be too noisy for everyday use, but - helpful for debugging as ``VERBOSE``. - -You can mark output as follows:: - - // Would appear at all levels. - $io->out('Quiet message', 1, ConsoleIo::QUIET); - $io->quiet('Quiet message'); - - // Would not appear when quiet output is toggled. - $io->out('normal message', 1, ConsoleIo::NORMAL); - $io->out('loud message', 1, ConsoleIo::VERBOSE); - $io->verbose('Verbose output'); - - // Would only appear when verbose output is enabled. - $io->out('extra message', 1, ConsoleIo::VERBOSE); - $io->verbose('Verbose output'); - -You can control the output level of commands, by using the ``--quiet`` and -``--verbose`` options. These options are added by default, and allow you to -consistently control output levels inside your CakePHP comands. - -The ``--quiet`` and ``--verbose`` options also control how logging data is -output to stdout/stderr. Normally info and higher log messages are output to -stdout/stderr. When ``--verbose`` is used, debug logs will be output to stdout. -When ``--quiet`` is used, only warning and higher log messages will be output to -stderr. - -Styling Output -============== - -Styling output is done by including tags - just like HTML - in your output. -These tags will be replaced with the correct ansi code sequence, or -stripped if you are on a console that doesn't support ansi codes. There -are several built-in styles, and you can create more. The built-in ones are - -* ``success`` Success messages. Green text. -* ``error`` Error messages. Red text. -* ``warning`` Warning messages. Yellow text. -* ``info`` Informational messages. Cyan text. -* ``comment`` Additional text. Blue text. -* ``question`` Text that is a question, added automatically by shell. -* ``info.bg`` White background with cyan text. -* ``warning.bg`` Yellow background with black text. -* ``error.bg`` Red background with black text. -* ``success.bg`` Green background with black text. - -You can create additional styles using ``$io->setStyle()``. To declare a -new output style you could do:: - - $io->setStyle('flashy', ['text' => 'magenta', 'blink' => true]); - -This would then allow you to use a ```` tag in your shell output, and if -ansi colors are enabled, the following would be rendered as blinking magenta -text ``$this->out('Whoooa Something went wrong');``. When -defining styles you can use the following colors for the ``text`` and -``background`` attributes: - -* black -* blue -* cyan -* green -* magenta -* red -* white -* yellow - -You can also use the following options as boolean switches, setting them to a -truthy value enables them. - -* blink -* bold -* reverse -* underline - -Adding a style makes it available on all instances of ConsoleOutput as well, -so you don't have to redeclare styles for both stdout and stderr objects. - -.. versionchanged:: 5.1.0 - The ``info.bg``, ``warning.bg``, ``error.bg``, and ``success.bg`` were added. - -Turning Off Coloring -==================== - -Although coloring is pretty, there may be times when you want to turn it off, -or force it on:: - - $io->outputAs(ConsoleOutput::RAW); - -The above will put the output object into raw output mode. In raw output mode, -no styling is done at all. There are three modes you can use. - -* ``ConsoleOutput::COLOR`` - Output with color escape codes in place. -* ``ConsoleOutput::PLAIN`` - Plain text output, known style tags will be - stripped from the output. -* ``ConsoleOutput::RAW`` - Raw output, no styling or formatting will be done. - This is a good mode to use if you are outputting XML or, want to debug why - your styling isn't working. - -By default on \*nix systems ConsoleOutput objects default to color output. -On Windows systems, plain output is the default unless the ``ANSICON`` -environment variable is present. diff --git a/en/console-commands/option-parsers.rst b/en/console-commands/option-parsers.rst deleted file mode 100644 index 9d5152becc..0000000000 --- a/en/console-commands/option-parsers.rst +++ /dev/null @@ -1,367 +0,0 @@ -Option Parsers -############## - -.. php:namespace:: Cake\Console -.. php:class:: ConsoleOptionParser - -Console applications typically take options and arguments as the primary way to -get information from the terminal into your commands. - -Defining an OptionParser -======================== - -Commands and Shells provide a ``buildOptionParser($parser)`` hook method that -you can use to define the options and arguments for your commands:: - - protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser - { - // Define your options and arguments. - - // Return the completed parser - return $parser; - } - -Shell classes use the ``getOptionParser()`` hook method to define their option -parser:: - - public function getOptionParser() - { - // Get an empty parser from the framework. - $parser = parent::getOptionParser(); - - // Define your options and arguments. - - // Return the completed parser - return $parser; - } - - -Using Arguments -=============== - -.. php:method:: addArgument($name, $params = []) - -Positional arguments are frequently used in command line tools, -and ``ConsoleOptionParser`` allows you to define positional -arguments as well as make them required. You can add arguments -one at a time with ``$parser->addArgument();`` or multiple at once -with ``$parser->addArguments();``:: - - $parser->addArgument('model', ['help' => 'The model to bake']); - -You can use the following options when creating an argument: - -* ``help`` The help text to display for this argument. -* ``required`` Whether this parameter is required. -* ``index`` The index for the arg, if left undefined the argument will be put - onto the end of the arguments. If you define the same index twice the - first option will be overwritten. -* ``choices`` An array of valid choices for this argument. If left empty all - values are valid. An exception will be raised when parse() encounters an - invalid value. -* ``separator`` A character sequence that separates arguments that should be - parsed into an array. - -Arguments that have been marked as required will throw an exception when -parsing the command if they have been omitted. So you don't have to -handle that in your shell. - -.. versionadded:: 5.2.0 - The ``separator`` option was added. - -Adding Multiple Arguments -------------------------- - -.. php:method:: addArguments(array $args) - -If you have an array with multiple arguments you can use -``$parser->addArguments()`` to add multiple arguments at once. :: - - $parser->addArguments([ - 'node' => ['help' => 'The node to create', 'required' => true], - 'parent' => ['help' => 'The parent node', 'required' => true], - ]); - -As with all the builder methods on ConsoleOptionParser, addArguments -can be used as part of a fluent method chain. - -Validating Arguments --------------------- - -When creating positional arguments, you can use the ``required`` flag, to -indicate that an argument must be present when a shell is called. -Additionally you can use ``choices`` to force an argument to be from a list of -valid choices:: - - $parser->addArgument('type', [ - 'help' => 'The type of node to interact with.', - 'required' => true, - 'choices' => ['aro', 'aco'], - ]); - -The above will create an argument that is required and has validation on the -input. If the argument is either missing, or has an incorrect value an exception -will be raised and the shell will be stopped. - -Using Options -============= - -.. php:method:: addOption($name, array $options = []) - -Options or flags are used in command line tools to provide unordered key/value -arguments for your commands. Options can define both verbose and short aliases. -They can accept a value (e.g ``--connection=default``) or be boolean options -(e.g ``--verbose``). Options are defined with the ``addOption()`` method:: - - $parser->addOption('connection', [ - 'short' => 'c', - 'help' => 'connection', - 'default' => 'default', - ]); - -The above would allow you to use either ``cake myshell --connection=other``, -``cake myshell --connection other``, or ``cake myshell -c other`` -when invoking the shell. - -Boolean switches do not accept or consume values, and their presence just -enables them in the parsed parameters:: - - $parser->addOption('no-commit', ['boolean' => true]); - -This option when used like ``cake mycommand --no-commit something`` would have -a value of ``true``, and 'something' would be a treated as a positional -argument. - -When creating options you can use the following options to define the behavior -of the option: - -* ``short`` - The single letter variant for this option, leave undefined for - none. -* ``help`` - Help text for this option. Used when generating help for the - option. -* ``default`` - The default value for this option. If not defined the default - will be ``true``. -* ``boolean`` - The option uses no value, it's just a boolean switch. - Defaults to ``false``. -* ``multiple`` - The option can be provided multiple times. The parsed option - will be an array of values when this option is enabled. -* ``separator`` - A character sequence that the option value is split into an - array with. -* ``choices`` - An array of valid choices for this option. If left empty all - values are valid. An exception will be raised when parse() encounters an - invalid value. - - -.. versionadded:: 5.2.0 - The ``separator`` option was added. - -Adding Multiple Options ------------------------ - -.. php:method:: addOptions(array $options) - -If you have an array with multiple options you can use ``$parser->addOptions()`` -to add multiple options at once. :: - - $parser->addOptions([ - 'node' => ['short' => 'n', 'help' => 'The node to create'], - 'parent' => ['short' => 'p', 'help' => 'The parent node'], - ]); - -As with all the builder methods on ConsoleOptionParser, addOptions can be used -as part of a fluent method chain. - -Validating Options ------------------- - -Options can be provided with a set of choices much like positional arguments -can be. When an option has defined choices, those are the only valid choices -for an option. All other values will raise an ``InvalidArgumentException``:: - - $parser->addOption('accept', [ - 'help' => 'What version to accept.', - 'choices' => ['working', 'theirs', 'mine'], - ]); - -Using Boolean Options ---------------------- - -Options can be defined as boolean options, which are useful when you need to -create some flag options. Like options with defaults, boolean options always -include themselves into the parsed parameters. When the flags are present they -are set to ``true``, when they are absent they are set to ``false``:: - - $parser->addOption('verbose', [ - 'help' => 'Enable verbose output.', - 'boolean' => true - ]); - -The following option would always have a value in the parsed parameter. When not -included its default value would be ``false``, and when defined it will be -``true``. - -Building a ConsoleOptionParser from an Array --------------------------------------------- - -.. php:method:: buildFromArray($spec) - -Option parsers can also be defined as arrays. Within the array, you can define -keys for ``arguments``, ``options``, ``description`` and ``epilog``. The values -for arguments, and options, should follow the format that -:php:func:`Cake\\Console\\ConsoleOptionParser::addArguments()` and -:php:func:`Cake\\Console\\ConsoleOptionParser::addOptions()` use. You can also -use ``buildFromArray`` on its own, to build an option parser:: - - public function getOptionParser() - { - return ConsoleOptionParser::buildFromArray([ - 'description' => [ - __("Use this command to grant ACL permissions. Once executed, the "), - __("ARO specified (and its children, if any) will have ALLOW access "), - __("to the specified ACO action (and the ACO's children, if any).") - ], - 'arguments' => [ - 'aro' => ['help' => __('ARO to check.'), 'required' => true], - 'aco' => ['help' => __('ACO to check.'), 'required' => true], - 'action' => ['help' => __('Action to check')], - ], - ]); - } - -Merging Option Parsers ----------------------- - -.. php:method:: merge($spec) - -When building a group command, you maybe want to combine several parsers for -this:: - - $parser->merge($anotherParser); - -Note that the order of arguments for each parser must be the same, and that -options must also be compatible for it work. So do not use keys for different -things. - -Getting Help from Shells -======================== - -By defining your options and arguments with the option parser CakePHP can -automatically generate rudimentary help information and add a ``--help`` and -``-h`` to each of your commands. Using one of these options will allow you to -see the generated help content: - -.. code-block:: console - - bin/cake bake --help - bin/cake bake -h - -Would both generate the help for bake. You can also get help for nested -commands: - -.. code-block:: console - - bin/cake bake model --help - bin/cake bake model -h - -The above would get you the help specific to bake's model command. - -Getting Help as XML -------------------- - -When building automated tools or development tools that need to interact with -CakePHP shell commands, it's nice to have help available in a machine parse-able format. -By providing the ``xml`` option when requesting help you can have help content -returned as XML: - -.. code-block:: console - - cake bake --help xml - cake bake -h xml - -The above would return an XML document with the generated help, options, and -arguments for the selected shell. A sample XML document would -look like: - -.. code-block:: xml - - - - bake fixture - Generate fixtures for use with the test suite. You can use - `bake fixture all` to bake all fixtures. - - Omitting all arguments and options will enter into an interactive - mode. - - - - - - - - - - - - - - - - - -Customizing Help Output -======================= - -You can further enrich the generated help content by adding a description, and -epilog. - -Set the Description -------------------- - -.. php:method:: setDescription($text) - -The description displays above the argument and option information. By passing -in either an array or a string, you can set the value of the description:: - - // Set multiple lines at once - $parser->setDescription(['line one', 'line two']); - - // Read the current value - $parser->getDescription(); - -Set the Epilog --------------- - -.. php:method:: setEpilog($text) - -Gets or sets the epilog for the option parser. The epilog is displayed after the -argument and option information. By passing in either an array or a string, you -can set the value of the epilog:: - - // Set multiple lines at once - $parser->setEpilog(['line one', 'line two']); - - // Read the current value - $parser->getEpilog(); diff --git a/en/console-commands/plugin.rst b/en/console-commands/plugin.rst deleted file mode 100644 index 4a9fe5a5b5..0000000000 --- a/en/console-commands/plugin.rst +++ /dev/null @@ -1,65 +0,0 @@ -.. _plugin-shell: - -Plugin Tool -########### - -The plugin tool allows you to load and unload plugins via the command prompt. -If you need help, run: - -.. code-block:: console - - bin/cake plugin --help - -Loading Plugins ---------------- - -Via the ``Load`` task you are able to load plugins in your -**config/bootstrap.php**. You can do this by running: - -.. code-block:: console - - bin/cake plugin load MyPlugin - -This will add the following to your **src/Application.php**:: - - // In the bootstrap method add: - $this->addPlugin('MyPlugin'); - - -Unloading Plugins ------------------ - -You can unload a plugin by specifying its name: - -.. code-block:: console - - bin/cake plugin unload MyPlugin - -This will remove the line ``$this->addPlugin('MyPlugin',...)`` from -**src/Application.php**. - -Plugin Assets -------------- - -CakePHP by default serves plugins assets using the ``AssetMiddleware`` middleware. -While this is a good convenience, it is recommended to symlink / copy -the plugin assets under app's webroot so that they can be directly served by the -web server without invoking PHP. You can do this by running: - -.. code-block:: console - - bin/cake plugin assets symlink - -Running the above command will symlink all plugins assets under app's webroot. -On Windows, which doesn't support symlinks, the assets will be copied in -respective folders instead of being symlinked. - -You can symlink assets of one particular plugin by specifying its name: - -.. code-block:: console - - bin/cake plugin assets symlink MyPlugin - -.. meta:: - :title lang=en: Plugin tool - :keywords lang=en: plugin,assets,tool,load,unload diff --git a/en/console-commands/repl.rst b/en/console-commands/repl.rst deleted file mode 100644 index 48465ba3bd..0000000000 --- a/en/console-commands/repl.rst +++ /dev/null @@ -1,50 +0,0 @@ -Interactive Console (REPL) -########################## - -CakePHP offers -`REPL(Read Eval Print Loop) plugin `__ to let -you explore some CakePHP and your application in an interactive console. - -.. note:: - - The plugin was shipped with the CakePHP app skeleton before 4.3. - -You can start the interactive console using: - -.. code-block:: console - - bin/cake console - -This will bootstrap your application and start an interactive console. At this -point you can interact with your application code and execute queries using your -application's models: - -.. code-block:: console - - bin/cake console - - >>> $articles = Cake\Datasource\FactoryLocator::get('Table')->get('Articles'); - // object(Cake\ORM\Table)( - // - // ) - >>> $articles->find()->all(); - -Since your application has been bootstrapped you can also test routing using the -REPL:: - - >>> Cake\Routing\Router::parse('/articles/view/1'); - // [ - // 'controller' => 'Articles', - // 'action' => 'view', - // 'pass' => [ - // 0 => '1' - // ], - // 'plugin' => NULL - // ] - -You can also test generating URLs:: - - >>> Cake\Routing\Router::url(['controller' => 'Articles', 'action' => 'edit', 99]); - // '/articles/edit/99' - -To quit the REPL you can use ``CTRL-C`` or by typing ``exit``. diff --git a/en/console-commands/routes.rst b/en/console-commands/routes.rst deleted file mode 100644 index d3c0986984..0000000000 --- a/en/console-commands/routes.rst +++ /dev/null @@ -1,40 +0,0 @@ -Routes Tool -########### - -The routes tool provides a simple to use CLI interface for testing and debugging -routes. You can use it to test how routes are parsed, and what URLs routing -parameters will generate. - -Getting a List of all Routes ----------------------------- - -.. code-block:: console - - bin/cake routes - -Testing URL parsing -------------------- - -You can quickly see how a URL will be parsed using the ``check`` method: - -.. code-block:: console - - bin/cake routes check /articles/edit/1 - -If your route contains any query string parameters remember to surround the URL -in quotes: - -.. code-block:: console - - bin/cake routes check "/articles/?page=1&sort=title&direction=desc" - -Testing URL Generation ----------------------- - -You can see the URL a :term:`routing array` will generate using the -``generate`` method: - -.. code-block:: console - - bin/cake routes generate controller:Articles action:edit 1 - diff --git a/en/console-commands/schema-cache.rst b/en/console-commands/schema-cache.rst deleted file mode 100644 index df891ab6a5..0000000000 --- a/en/console-commands/schema-cache.rst +++ /dev/null @@ -1,30 +0,0 @@ -Schema Cache Tool -################# - -The SchemaCacheCommand provides a simple CLI tool for managing your application's -metadata caches. In deployment situations it is helpful to rebuild the metadata -cache in-place without clearing the existing cache data. You can do this by -running: - -.. code-block:: console - - bin/cake schema_cache build --connection default - -This will rebuild the metadata cache for all tables on the ``default`` -connection. If you only need to rebuild a single table you can do that by -providing its name: - -.. code-block:: console - - bin/cake schema_cache build --connection default articles - -In addition to building cached data, you can use the SchemaCacheShell to remove -cached metadata as well: - -.. code-block:: console - - # Clear all metadata - bin/cake schema_cache clear - - # Clear a single table - bin/cake schema_cache clear articles diff --git a/en/console-commands/server.rst b/en/console-commands/server.rst deleted file mode 100644 index 7c3eb62f6e..0000000000 --- a/en/console-commands/server.rst +++ /dev/null @@ -1,30 +0,0 @@ -Server Tool -########### - -The ``ServerCommand`` lets you stand up a simple webserver using the built in PHP -webserver. While this server is *not* intended for production use it can -be handy in development when you want to quickly try an idea out and don't want -to spend time configuring Apache or Nginx. You can start the server command with: - -.. code-block:: console - - bin/cake server - -You should see the server boot up and attach to port 8765. You can visit the -CLI server by visiting ``http://localhost:8765`` -in your web-browser. You can close the server by pressing ``CTRL-C`` in your -terminal. - -.. note:: - - Try ``bin/cake server -H 0.0.0.0`` if the server is unreachable from other hosts. - -Changing the Port and Document Root -=================================== - -You can customize the port and document root using options: - -.. code-block:: console - - bin/cake server --port 8080 --document_root path/to/app - diff --git a/en/contents.rst b/en/contents.rst deleted file mode 100644 index 9ccea509b6..0000000000 --- a/en/contents.rst +++ /dev/null @@ -1,106 +0,0 @@ -Contents -######## - -.. toctree:: - :hidden: - - index - -.. toctree:: - :caption: Preface - - intro - quickstart - appendices/migration-guides - tutorials-and-examples - contributing - release-policy - -.. toctree:: - :caption: Getting Started - - installation - development/configuration - development/application - development/dependency-injection - development/routing - controllers/request-response - controllers/middleware - controllers - views - orm - -.. toctree:: - :caption: Using CakePHP - - core-libraries/caching - console-commands - development/debugging - deployment - core-libraries/email - development/errors - core-libraries/events - core-libraries/internationalization-and-localization - core-libraries/logging - core-libraries/form - controllers/pagination - plugins - development/rest - security - development/sessions - development/testing - core-libraries/validation - -.. toctree:: - :caption: Utility Classes - - core-libraries/app - core-libraries/collections - core-libraries/hash - core-libraries/httpclient - core-libraries/inflector - core-libraries/number - core-libraries/plugin - core-libraries/registry-objects - core-libraries/text - core-libraries/time - core-libraries/xml - -.. toctree:: - :caption: Plugins & Packages - - standalone-packages - Authentication - Authorization - Bake - Debug Kit - Migrations - Elasticsearch - Phinx - Chronos - Queue - -.. toctree:: - :caption: Other - - core-libraries/global-constants-and-functions - appendices - -.. toctree:: - :hidden: - - topics - chronos - debug-kit - elasticsearch - bake - bake/development - bake/usage - migrations - phinx - -.. todolist:: - -.. meta:: - :title lang=en: Contents - :keywords lang=en: core libraries,ref search,commands,deployment,appendices,glossary,models diff --git a/en/contributing.rst b/en/contributing.rst deleted file mode 100644 index e0ca55d733..0000000000 --- a/en/contributing.rst +++ /dev/null @@ -1,18 +0,0 @@ -Contributing -############ - -There are a number of ways you can contribute to CakePHP. The following sections -cover the various ways you can contribute to CakePHP: - -.. toctree:: - :maxdepth: 1 - - contributing/documentation - contributing/tickets - contributing/code - contributing/cakephp-coding-conventions - contributing/backwards-compatibility - -.. meta:: - :title lang=en: Contributing - :keywords lang=en: coding conventions,documentation,maxdepth diff --git a/en/contributing/backwards-compatibility.rst b/en/contributing/backwards-compatibility.rst deleted file mode 100644 index e6151605e3..0000000000 --- a/en/contributing/backwards-compatibility.rst +++ /dev/null @@ -1,206 +0,0 @@ -Backwards Compatibility Guide -############################# - -Ensuring that you can upgrade your applications easily and smoothly is important -to us. That's why we only break compatibility at major release milestones. -You might be familiar with `semantic versioning `_, which is -the general guideline we use on all CakePHP projects. In short, semantic -versioning means that only major releases (such as 2.0, 3.0, 4.0) can break -backwards compatibility. Minor releases (such as 2.1, 3.1, 3.2) may introduce new -features, but are not allowed to break compatibility. Bug fix releases (such as 2.1.2, -3.0.1) do not add new features, but fix bugs or enhance performance only. - -.. note:: - - Deprecations are removed with the next major version of the framework. - It is advised that you adapt to deprecations as they are introduced to - ensure future upgrades are easier. - -To clarify what changes you can expect in each release tier we have more -detailed information for developers using CakePHP, and for developers working on -CakePHP that helps set expectations of what can be done in minor releases. Major -releases can have as many breaking changes as required. - -Migration Guides -================ - -For each major and minor release, the CakePHP team will provide a migration -guide. These guides explain the new features and any breaking changes that are -in each release. They can be found in the :doc:`/appendices` section of the -cookbook. - -Using CakePHP -============= - -If you are building your application with CakePHP, the following guidelines -explain the stability you can expect. - -Interfaces ----------- - -Outside of major releases, interfaces provided by CakePHP will **not** have any -existing methods changed. New methods may be added, but no existing methods will -be changed. - -Classes -------- - -Classes provided by CakePHP can be constructed and have their public methods and -properties used by application code and outside of major releases backwards -compatibility is ensured. - -.. note:: - - Some classes in CakePHP are marked with the ``@internal`` API doc tag. These - classes are **not** stable and do not have any backwards compatibility - promises. - -In minor releases, new methods may be added to classes, and existing methods may -have new arguments added. Any new arguments will have default values, but if -you've overridden methods with a differing signature you may see fatal errors. -Methods that have new arguments added will be documented in the migration guide -for that release. - -The following table outlines several use cases and what compatibility you can -expect from CakePHP: - -+-------------------------------+--------------------------+ -| If you... | Backwards compatibility? | -+===============================+==========================+ -| Typehint against the class | Yes | -+-------------------------------+--------------------------+ -| Create a new instance | Yes | -+-------------------------------+--------------------------+ -| Extend the class | Yes | -+-------------------------------+--------------------------+ -| Access a public property | Yes | -+-------------------------------+--------------------------+ -| Call a public method | Yes | -+-------------------------------+--------------------------+ -| **Extend a class and...** | -+-------------------------------+--------------------------+ -| Override a public property | Yes | -+-------------------------------+--------------------------+ -| Access a protected property | No [1]_ | -+-------------------------------+--------------------------+ -| Override a protected property | No [1]_ | -+-------------------------------+--------------------------+ -| Override a protected method | No [1]_ | -+-------------------------------+--------------------------+ -| Call a protected method | No [1]_ | -+-------------------------------+--------------------------+ -| Add a public property | No | -+-------------------------------+--------------------------+ -| Add a public method | No | -+-------------------------------+--------------------------+ -| Add an argument | No [1]_ | -| to an overridden method | | -+-------------------------------+--------------------------+ -| Add a default argument value | Yes | -| to an existing method | | -| argument | | -+-------------------------------+--------------------------+ - -Working on CakePHP -================== - -If you are helping make CakePHP even better please keep the following guidelines -in mind when adding/changing functionality: - -In a minor release you can: - -+-------------------------------+--------------------------+ -| In a minor release can you... | -+===============================+==========================+ -| **Classes** | -+-------------------------------+--------------------------+ -| Remove a class | No | -+-------------------------------+--------------------------+ -| Remove an interface | No | -+-------------------------------+--------------------------+ -| Remove a trait | No | -+-------------------------------+--------------------------+ -| Make final | No | -+-------------------------------+--------------------------+ -| Make abstract | No | -+-------------------------------+--------------------------+ -| Change name | Yes [2]_ | -+-------------------------------+--------------------------+ -| **Properties** | -+-------------------------------+--------------------------+ -| Add a public property | Yes | -+-------------------------------+--------------------------+ -| Remove a public property | No | -+-------------------------------+--------------------------+ -| Add a protected property | Yes | -+-------------------------------+--------------------------+ -| Remove a protected property | Yes [3]_ | -+-------------------------------+--------------------------+ -| **Methods** | -+-------------------------------+--------------------------+ -| Add a public method | Yes | -+-------------------------------+--------------------------+ -| Remove a public method | No | -+-------------------------------+--------------------------+ -| Add a protected method | Yes | -+-------------------------------+--------------------------+ -| Move to parent class | Yes | -+-------------------------------+--------------------------+ -| Remove a protected method | Yes [3]_ | -+-------------------------------+--------------------------+ -| Reduce visibility | No | -+-------------------------------+--------------------------+ -| Change method name | Yes [2]_ | -+-------------------------------+--------------------------+ -| Add a new argument with | Yes | -| default value | | -+-------------------------------+--------------------------+ -| Add a new required argument | No | -| to an existing method. | | -+-------------------------------+--------------------------+ -| Remove a default value from | No | -| an existing argument | | -+-------------------------------+--------------------------+ -| Change method type void | Yes | -+-------------------------------+--------------------------+ - -.. [1] Your code *may* be broken by minor releases. Check the migration guide - for details. -.. [2] You can change a class/method name as long as the old name remains - available. This is generally avoided unless renaming has significant - benefit. -.. [3] Avoid whenever possible. Any removals need to be documented in - the migration guide. - -Deprecations -============ - -In each minor release, features may be deprecated. If features are deprecated, -API documentation and runtime warnings will be added. Runtime errors help you -locate code that needs to be updated before it breaks. If you wish to disable -runtime warnings you can do so using the ``Error.errorLevel`` configuration -value:: - - // in config/app.php - // ... - 'Error' => [ - 'errorLevel' => E_ALL ^ E_USER_DEPRECATED, - ] - // ... - -Will disable runtime deprecation warnings. - -.. _experimental-features: - -Experimental Features -===================== - -Experimental features are **not included** in the above backwards compatibility -promises. Experimental features can have breaking changes made in minor releases -as long as they remain experimental. Experimental features can be identified by -the warning in the book and the usage of ``@experimental`` in the API -documentation. - -Experimental features are intended to help gather feedback on how a feature -works before it becomes stable. Once the interfaces and behavior has been vetted -with the community the experimental flags will be removed. diff --git a/en/contributing/cakephp-coding-conventions.rst b/en/contributing/cakephp-coding-conventions.rst deleted file mode 100644 index 0e09182c56..0000000000 --- a/en/contributing/cakephp-coding-conventions.rst +++ /dev/null @@ -1,652 +0,0 @@ -Coding Standards -################ - -CakePHP developers will use the `PSR-12 coding style guide -`_ in addition to the following rules as -coding standards. - -It is recommended that others developing CakeIngredients follow the same -standards. - -You can use the `CakePHP Code Sniffer -`_ to check that your code -follows required standards. - -Adding New Features -=================== - -No new features should be added, without having their own tests – which -should be passed before committing them to the repository. - -IDE Setup -========= - -Please make sure your IDE is set up to "trim right" on whitespaces. -There should be no trailing spaces per line. - -Most modern IDEs also support an ``.editorconfig`` file. The CakePHP app -skeleton ships with it by default. It already contains best practise defaults. - -We recommend to use the `IdeHelper `_ plugin if you -want to maximize IDE compatibility. It will assist to keep the annotations up-to-date which will make -the IDE fully understand how all classes work together and provides better type-hinting and auto-completion. - -Indentation -=========== - -Four spaces will be used for indentation. - -So, indentation should look like this:: - - // base level - // level 1 - // level 2 - // level 1 - // base level - -Or:: - - $booleanVariable = true; - $stringVariable = 'moose'; - if ($booleanVariable) { - echo 'Boolean value is true'; - if ($stringVariable === 'moose') { - echo 'We have encountered a moose'; - } - } - -In cases where you're using a multi-line function call use the following -guidelines: - -* Opening parenthesis of a multi-line function call must be the last content on - the line. -* Only one argument is allowed per line in a multi-line function call. -* Closing parenthesis of a multi-line function call must be on a line by itself. - -As an example, instead of using the following formatting:: - - $matches = array_intersect_key($this->_listeners, - array_flip(preg_grep($matchPattern, - array_keys($this->_listeners), 0))); - -Use this instead:: - - $matches = array_intersect_key( - $this->_listeners, - array_flip( - preg_grep($matchPattern, array_keys($this->_listeners), 0) - ) - ); - -Line Length -=========== - -It is recommended to keep lines at approximately 100 characters long for better -code readability. A limit of 80 or 120 characters makes it necessary to -distribute complex logic or expressions by function, as well as give functions -and objects shorter, more expressive names. Lines must not be -longer than 120 characters. - -In short: - -* 100 characters is the soft limit. -* 120 characters is the hard limit. - -Control Structures -================== - -Control structures are for example "``if``", "``for``", "``foreach``", -"``while``", "``switch``" etc. Below, an example with "``if``":: - - if ((expr_1) || (expr_2)) { - // action_1; - } elseif (!(expr_3) && (expr_4)) { - // action_2; - } else { - // default_action; - } - -* In the control structures there should be 1 (one) space before the first - parenthesis and 1 (one) space between the last parenthesis and the opening - bracket. -* Always use curly brackets in control structures, even if they are not needed. - They increase the readability of the code, and they give you fewer logical - errors. -* Opening curly brackets should be placed on the same line as the control - structure. Closing curly brackets should be placed on new lines, and they - should have same indentation level as the control structure. The statement - included in curly brackets should begin on a new line, and code contained - within it should gain a new level of indentation. -* Inline assignments should not be used inside of the control structures. - -:: - - // wrong = no brackets, badly placed statement - if (expr) statement; - - // wrong = no brackets - if (expr) - statement; - - // good - if (expr) { - statement; - } - - // wrong = inline assignment - if ($variable = Class::function()) { - statement; - } - - // good - $variable = Class::function(); - if ($variable) { - statement; - } - -Ternary Operator ----------------- - -Ternary operators are permissible when the entire ternary operation fits on one -line. Longer ternaries should be split into ``if else`` statements. Ternary -operators should not ever be nested. Optionally parentheses can be used around -the condition check of the ternary for clarity:: - - // Good, simple and readable - $variable = isset($options['variable']) ? $options['variable'] : true; - - // Nested ternaries are bad - $variable = isset($options['variable']) ? isset($options['othervar']) ? true : false : false; - -Template Files --------------- - -In template files developers should use keyword control structures. -Keyword control structures are easier to read in complex template files. Control -structures can either be contained in a larger PHP block, or in separate PHP -tags:: - - You are the admin user.

      '; - endif; - ?> -

      The following is also acceptable:

      - -

      You are the admin user.

      - - -Comparison -========== - -Always try to be as strict as possible. If a non-strict test is deliberate it -might be wise to comment it as such to avoid confusing it for a mistake. - -For testing if a variable is null, it is recommended to use a strict check:: - - if ($value === null) { - // ... - } - -The value to check against should be placed on the right side:: - - // not recommended - if (null === $this->foo()) { - // ... - } - - // recommended - if ($this->foo() === null) { - // ... - } - -Function Calls -============== - -Functions should be called without space between function's name and starting -parenthesis. There should be one space between every parameter of a function -call:: - - $var = foo($bar, $bar2, $bar3); - -As you can see above there should be one space on both sides of equals sign (=). - -Method Definition -================= - -Example of a method definition:: - - public function someFunction($arg1, $arg2 = '') - { - if (expr) { - statement; - } - - return $var; - } - -Parameters with a default value, should be placed last in function definition. -Try to make your functions return something, at least ``true`` or ``false``, so -it can be determined whether the function call was successful:: - - public function connection($dns, $persistent = false) - { - if (is_array($dns)) { - $dnsInfo = $dns; - } else { - $dnsInfo = BD::parseDNS($dns); - } - - if (!($dnsInfo) || !($dnsInfo['phpType'])) { - return $this->addError(); - } - - return true; - } - -There are spaces on both side of the equals sign. - -Bail Early -========== - -Try to avoid unnecessary nesting by bailing early:: - - public function run(array $data) - { - ... - if (!$success) { - return false; - } - - ... - } - - public function check(array $data) - { - ... - if (!$success) { - throw new RuntimeException(/* ... */); - } - - ... - } - -This helps to keep the logic sequential which improves readability. - -Typehinting ------------ - -Arguments that expect objects, arrays or callbacks (callable) can be typehinted. -We only typehint public methods, though, as typehinting is not cost-free:: - - /** - * Some method description. - * - * @param \Cake\ORM\Table $table The table class to use. - * @param array $array Some array value. - * @param callable $callback Some callback. - * @param bool $boolean Some boolean value. - */ - public function foo(Table $table, array $array, callable $callback, $boolean) - { - } - -Here ``$table`` must be an instance of ``\Cake\ORM\Table``, ``$array`` must be -an ``array`` and ``$callback`` must be of type ``callable`` (a valid callback). - -Note that if you want to allow ``$array`` to be also an instance of -``\ArrayObject`` you should not typehint as ``array`` accepts only the primitive -type:: - - /** - * Some method description. - * - * @param array|\ArrayObject $array Some array value. - */ - public function foo($array) - { - } - -Anonymous Functions (Closures) ------------------------------- - -Defining anonymous functions follows the `PSR-12 -`_ coding style guide, where they are -declared with a space after the `function` keyword, and a space before and after -the `use` keyword:: - - $closure = function ($arg1, $arg2) use ($var1, $var2) { - // code - }; - -Method Chaining -=============== - -Method chaining should have multiple methods spread across separate lines, and -indented with four spaces:: - - $email->from('foo@example.com') - ->to('bar@example.com') - ->subject('A great message') - ->send(); - -Commenting Code -=============== - -All comments should be written in English, and should in a clear way describe -the commented block of code. - -Comments can include the following `phpDocumentor `_ -tags: - -* `@deprecated `_ - Using the ``@version `` format, where ``version`` - and ``description`` are mandatory. Version refers to the one it got deprecated in. -* `@example `_ -* `@ignore `_ -* `@internal `_ -* `@link `_ -* `@see `_ -* `@since `_ -* `@version `_ - -PhpDoc tags are very much like JavaDoc tags in Java. Tags are only processed if -they are the first thing in a DocBlock line, for example:: - - /** - * Tag example. - * - * @author this tag is parsed, but this @version is ignored - * @version 1.0 this tag is also parsed - */ - -:: - - /** - * Example of inline phpDoc tags. - * - * This function works hard with foo() to rule the world. - * - * @return void - */ - function bar() - { - } - - /** - * Foo function. - * - * @return void - */ - function foo() - { - } - -Comment blocks, with the exception of the first block in a file, should always -be preceded by a newline. - -Variable Types --------------- - -Variable types for use in DocBlocks: - -Type - Description -mixed - A variable with undefined (or multiple) type. -int - Integer type variable (whole number). -float - Float type (point number). -bool - Logical type (true or false). -string - String type (any value in " " or ' '). -null - Null type. Usually used in conjunction with another type. -array - Array type. -object - Object type. A specific class name should be used if possible. -resource - Resource type (returned by for example mysql\_connect()). - Remember that when you specify the type as mixed, you should indicate - whether it is unknown, or what the possible types are. -callable - Callable function. - -You can also combine types using the pipe char:: - - int|bool - -For more than two types it is usually best to just use ``mixed``. - -When returning the object itself (for example, for chaining), one should use ``$this`` -instead:: - - /** - * Foo function. - * - * @return $this - */ - public function foo() - { - return $this; - } - -Including Files -=============== - -``include``, ``require``, ``include_once`` and ``require_once`` do not have -parentheses:: - - // wrong = parentheses - require_once('ClassFileName.php'); - require_once ($class); - - // good = no parentheses - require_once 'ClassFileName.php'; - require_once $class; - -When including files with classes or libraries, use only and always the -`require\_once `_ function. - -PHP Tags -======== - -Always use long tags (````) instead of short tags (````). The short -echo should be used in template files where appropriate. - -Short Echo ----------- - -The short echo should be used in template files in place of `` - - // good = spaces, no semicolon - - -As of PHP 5.4 the short echo tag (```_ -* FTP: `ftp://ftp.example.com `_ - -The "example.com" domain name has been reserved for this (see :rfc:`2606`) and -is recommended for use in documentation or as examples. - -Files ------ - -File names which do not contain classes should be lowercased and underscored, -for example:: - - long_file_name.php - -Casting -------- - -For casting we use: - -Type - Description -(bool) - Cast to boolean. -(int) - Cast to integer. -(float) - Cast to float. -(string) - Cast to string. -(array) - Cast to array. -(object) - Cast to object. - -Please use ``(int)$var`` instead of ``intval($var)`` and ``(float)$var`` instead -of ``floatval($var)`` when applicable. - -Constants ---------- - -Constants should be defined in capital letters:: - - define('CONSTANT', 1); - -If a constant name consists of multiple words, they should be separated by an -underscore character, for example:: - - define('LONG_NAMED_CONSTANT', 2); - -Enums ------ - -Enum cases are defined in ``CamelCase`` style:: - - enum ArticleStatus: string - { - case Published = 'Y'; - case NotPublishedYet = 'N'; - } - -Careful when using empty()/isset() -================================== - -While ``empty()`` often seems correct to use, it can mask errors -and cause unintended effects when ``'0'`` and ``0`` are given. When variables or -properties are already defined, the usage of ``empty()`` is not recommended. -When working with variables, it is better to rely on type-coercion to boolean -instead of ``empty()``:: - - function manipulate($var) - { - // Not recommended, $var is already defined in the scope - if (empty($var)) { - // ... - } - - // Use boolean type coercion - if (!$var) { - // ... - } - if ($var) { - // ... - } - } - -When dealing with defined properties you should favour ``null`` checks over -``empty()``/``isset()`` checks:: - - class Thing - { - private $property; // Defined - - public function readProperty() - { - // Not recommended as the property is defined in the class - if (!isset($this->property)) { - // ... - } - // Recommended - if ($this->property === null) { - - } - } - } - -When working with arrays, it is better to merge in defaults over using -``empty()`` checks. By merging in defaults, you can ensure that required keys -are defined:: - - function doWork(array $array) - { - // Merge defaults to remove need for empty checks. - $array += [ - 'key' => null, - ]; - - // Not recommended, the key is already set - if (isset($array['key'])) { - // ... - } - - // Recommended - if ($array['key'] !== null) { - // ... - } - } - -.. meta:: - :title lang=en: Coding Standards - :keywords lang=en: curly brackets,indentation level,logical errors,control structures,control structure,expr,coding standards,parenthesis,foreach,readability,moose,new features,repository,developers diff --git a/en/contributing/code.rst b/en/contributing/code.rst deleted file mode 100644 index 074c3f9170..0000000000 --- a/en/contributing/code.rst +++ /dev/null @@ -1,145 +0,0 @@ -Code -#### - -Patches and pull requests are a great way to contribute code back to CakePHP. -Pull requests can be created in GitHub, and are preferred over patch files in -ticket comments. - -Initial Setup -============= - -Before working on patches for CakePHP, it's a good idea to get your environment -setup. You'll need the following software: - -* Git -* PHP |minphpversion| or greater -* PHPUnit 5.7.0 or greater - -Set up your user information with your name/handle and working email address:: - - git config --global user.name 'Bob Barker' - git config --global user.email 'bob.barker@example.com' - -.. note:: - - If you are new to Git, we highly recommend you to read the excellent and - free `ProGit `_ book. - -Get a clone of the CakePHP source code from GitHub: - -* If you don't have a `GitHub `_ account, create one. -* Fork the `CakePHP repository `_ by clicking - the **Fork** button. - -After your fork is made, clone your fork to your local machine:: - - git clone git@github.com:YOURNAME/cakephp.git - -Add the original CakePHP repository as a remote repository. You'll use this -later to fetch changes from the CakePHP repository. This will let you stay up -to date with CakePHP:: - - cd cakephp - git remote add upstream git://github.com/cakephp/cakephp.git - -Now that you have CakePHP setup you should be able to define a ``$test`` -:ref:`database connection `, and -:ref:`run all the tests `. - -Working on a Patch -================== - -Each time you want to work on a bug, feature or enhancement create a topic -branch. - -The branch you create should be based on the version that your fix/enhancement -is for. For example if you are fixing a bug in ``3.x`` you would want to use the -``master`` branch as the base for your branch. If your change is a bug fix for -the 2.x release series, you should use the ``2.x`` branch:: - - # fixing a bug on 3.x - git fetch upstream - git checkout -b ticket-1234 upstream/master - - # fixing a bug on 2.x - git fetch upstream - git checkout -b ticket-1234 upstream/2.x - -.. tip:: - - Use a descriptive name for your branch. Referencing the ticket or feature - name is a good convention. Examples include ``ticket-1234`` and ``feature-awesome``. - -The above will create a local branch based on the upstream (CakePHP) 2.x branch. -Work on your fix, and make as many commits as you need; but keep in mind the -following: - -* Follow the :doc:`/contributing/cakephp-coding-conventions`. -* Add a test case to show the bug is fixed, or that the new feature works. -* Keep your commits logical, and write clear commit messages that provide - context on what you changed and why. - -Submitting a Pull Request -========================= - -Once your changes are done and you're ready for them to be merged into CakePHP, -you'll want to update your branch:: - - # Rebase fix on top of master - git checkout master - git fetch upstream - git merge upstream/master - git checkout - git rebase master - -This will fetch + merge in any changes that have happened in CakePHP since you -started. It will then rebase - or replay your changes on top of the current -code. You might encounter a conflict during the ``rebase``. If the rebase quits -early you can see which files are conflicted/un-merged with ``git status``. -Resolve each conflict, and then continue the rebase:: - - git add # do this for each conflicted file. - git rebase --continue - -Check that all your tests continue to pass. Then push your branch to your fork:: - - git push origin - -If you've rebased after pushing your branch, you'll need to use force push:: - - git push --force origin - -Once your branch is on GitHub, you can submit a pull request on GitHub. - -Choosing Where Your Changes will be Merged Into ------------------------------------------------ - -When making pull requests you should make sure you select the correct base -branch, as you cannot edit it once the pull request is created. - -* If your change is a **bugfix** and doesn't introduce new functionality and - only corrects existing behavior that is present in the current release. Then - choose **master** as your merge target. -* If your change is a **new feature** or an addition to the framework, then you - should choose the branch with the next version number. For example if the - current stable release is ``4.0.0``, the branch accepting new features will - be ``4.next``. -* If your change is a breaks existing functionality, or APIs then you'll have - to choose then next major release. For example, if the current release is - ``4.0.0`` then the next time existing behavior can be broken will be in - ``5.x`` so you should target that branch. - -.. note:: - - Remember that all code you contribute to CakePHP will be licensed under the - MIT License, and the `Cake Software Foundation - `_ will become the owner of any - contributed code. Contributors should follow the `CakePHP Community - Guidelines `_. - -All bug fixes merged into a maintenance branch will also be merged into upcoming -releases periodically by the core team. - -.. meta:: - :title lang=en: Code - :keywords lang=en: cakephp source code,code patches,test ref,descriptive name,bob barker,initial setup,global user,database connection,clone,repository,user information,enhancement,back patches,checkout diff --git a/en/contributing/documentation.rst b/en/contributing/documentation.rst deleted file mode 100644 index 1b6dce5881..0000000000 --- a/en/contributing/documentation.rst +++ /dev/null @@ -1,483 +0,0 @@ -Documentation -############# - -Contributing to the documentation is simple. The files are hosted on -https://github.com/cakephp/docs. Feel free to fork the repo, add your -changes/improvements/translations and give back by issuing a pull request. -You can even edit the docs online with GitHub, without ever downloading the -files -- the "Improve this Doc" button on any given page will direct you to -GitHub's online editor for that page. - -CakePHP documentation is -`continuously integrated `_, -and deployed after each pull request is merged. - -Translations -============ - -Email the docs team (docs at cakephp dot org) or hop on IRC -(#cakephp on freenode) to discuss any translation efforts you would -like to participate in. - -New Translation Language ------------------------- - -We want to provide translations that are as complete as possible. However, there -may be times where a translation file is not up-to-date. You should always -consider the English version as the authoritative version. - -If your language is not in the current languages, please contact us through -Github and we will consider creating a skeleton folder for it. The following -sections are the first one you should consider translating as these -files don't change often: - -- index.rst -- intro.rst -- quickstart.rst -- installation.rst -- /intro folder -- /tutorials-and-examples folder - -Reminder for Docs Administrators --------------------------------- - -The structure of all language folders should mirror the English folder -structure. If the structure changes for the English version, we should apply -those changes in the other languages. - -For example, if a new English file is created in **en/file.rst**, we should: - -- Add the file in all other languages : **fr/file.rst**, **zh/file.rst**, ... -- Delete the content, but keeping the ``title``, ``meta`` information and - eventual ``toc-tree`` elements. The following note will be added while nobody - has translated the file:: - - File Title - ########## - - .. note:: - The documentation is not currently supported in XX language for this - page. - - Please feel free to send us a pull request on - `Github `_ or use the **Improve This Doc** - button to directly propose your changes. - - You can refer to the English version in the select top menu to have - information about this page's topic. - - // If toc-tree elements are in the English version - .. toctree:: - :maxdepth: 1 - - one-toc-file - other-toc-file - - .. meta:: - :title lang=xx: File Title - :keywords lang=xx: title, description,... - -Translator tips ---------------- - -- Browse and edit in the language you want the content to be - translated to - otherwise you won't see what has already been - translated. -- Feel free to dive right in if your chosen language already - exists on the book. -- Use `Informal Form `_. -- Translate both the content and the title at the same time. -- Do compare to the English content before submitting a correction - (if you correct something, but don't integrate an 'upstream' change - your submission won't be accepted). -- If you need to write an English term, wrap it in ```` tags. - For example, "asdf asdf *Controller* asdf" or "asdf asdf Kontroller - (*Controller*) asfd". -- Do not submit partial translations. -- Do not edit a section with a pending change. -- Do not use - `HTML entities `_ - for accented characters, the book uses UTF-8. -- Do not significantly change the markup (HTML) or add new content. -- If the original content is missing some info, submit an edit for - that first. - -Documentation Formatting Guide -============================== - -The new CakePHP documentation is written with -`ReST formatted text `_. ReST -(Re Structured Text) is a plain text markup syntax similar to markdown, or -textile. To maintain consistency it is recommended that when adding to the -CakePHP documentation you follow the guidelines here on how to format and -structure your text. - -Line Length ------------ - -Lines of text should be wrapped at 80 columns. The only exception should be -long URLs, and code snippets. - -Headings and Sections ---------------------- - -Section headers are created by underlining the title with punctuation characters -at least the length of the text. - -- ``#`` Is used to denote page titles. -- ``=`` Is used for sections in a page. -- ``-`` Is used for subsections. -- ``~`` Is used for sub-subsections. -- ``^`` Is used for sub-sub-subsections. - -Headings should not be nested more than 5 levels deep. Headings should be -preceded and followed by a blank line. - -Paragraphs ----------- - -Paragraphs are simply blocks of text, with all the lines at the same level of -indentation. Paragraphs should be separated by one blank line. - -Inline Markup -------------- - -* One asterisk: *text* for emphasis (italics) - We'll use it for general highlighting/emphasis. - - * ``*text*``. - -* Two asterisks: **text** for strong emphasis (boldface) - We'll use it for working directories, bullet list subject, table names and - excluding the following word "table". - - * ``**/config/Migrations**``, ``**articles**``, etc. - -* Two backquotes: ``text`` for code samples - We'll use it for names of method options, names of table columns, object - names, excluding the following word "object" and for method/function - names -- include "()". - - * ````cascadeCallbacks````, ````true````, ````id````, - ````PagesController````, ````config()````, etc. - -If asterisks or backquotes appear in running text and could be confused with -inline markup delimiters, they have to be escaped with a backslash. - -Inline markup has a few restrictions: - -* It **may not** be nested. -* Content may not start or end with whitespace: ``* text*`` is wrong. -* Content must be separated from surrounding text by non-word characters. Use a - backslash escaped space to work around that: ``onelong\ *bolded*\ word``. - -Lists ------ - -List markup is very similar to markdown. Unordered lists are indicated by -starting a line with a single asterisk and a space. Numbered lists can be -created with either numerals, or ``#`` for auto numbering:: - - * This is a bullet - * So is this. But this line - has two lines. - - 1. First line - 2. Second line - - #. Automatic numbering - #. Will save you some time. - -Indented lists can also be created, by indenting sections and separating them -with an empty line:: - - * First line - * Second line - - * Going deeper - * Whoah - - * Back to the first level. - -Definition lists can be created by doing the following:: - - term - definition - CakePHP - An MVC framework for PHP - -Terms cannot be more than one line, but definitions can be multi-line and all -lines should be indented consistently. - -Links ------ - -There are several kinds of links, each with their own uses. - -External Links -~~~~~~~~~~~~~~ - -Links to external documents can be done with the following:: - - `External Link to php.net `_ - -The resulting link would look like this: `External Link to php.net `_ - -Links to Other Pages -~~~~~~~~~~~~~~~~~~~~ - -.. rst:role:: doc - - Other pages in the documentation can be linked to using the ``:doc:`` role. - You can link to the specified document using either an absolute or relative - path reference. You should omit the ``.rst`` extension. For example, if - the reference ``:doc:`form``` appears in the document ``core-helpers/html``, - then the link references ``core-helpers/form``. If the reference was - ``:doc:`/core-helpers```, it would always reference ``/core-helpers`` - regardless of where it was used. - -Cross Referencing Links -~~~~~~~~~~~~~~~~~~~~~~~ - -.. rst:role:: ref - - You can cross reference any arbitrary title in any document using the - ``:ref:`` role. Link label targets must be unique across the entire - documentation. When creating labels for class methods, it's best to use - ``class-method`` as the format for your link label. - - The most common use of labels is above a title. Example:: - - .. _label-name: - - Section heading - --------------- - - More content here. - - Elsewhere you could reference the above section using ``:ref:`label-name```. - The link's text would be the title that the link preceded. You can also - provide custom link text using ``:ref:`Link text ```. - -Prevent Sphinx to Output Warnings -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Sphinx will output warnings if a file is not referenced in a toc-tree. It's -a great way to ensure that all files have a link directed to them, but -sometimes, you don't need to insert a link for a file, eg. for our -`epub-contents` and `pdf-contents` files. In those cases, you can add -``:orphan:`` at the top of the file, to suppress warnings that the file is not -in the toc-tree. - -Describing Classes and their Contents -------------------------------------- - -The CakePHP documentation uses the `phpdomain -`_ to provide custom -directives for describing PHP objects and constructs. Using these directives -and roles is required to give proper indexing and cross referencing features. - -Describing Classes and Constructs ---------------------------------- - -Each directive populates the index, and or the namespace index. - -.. rst:directive:: .. php:global:: name - - This directive declares a new PHP global variable. - -.. rst:directive:: .. php:function:: name(signature) - - Defines a new global function outside of a class. - -.. rst:directive:: .. php:const:: name - - This directive declares a new PHP constant, you can also use it nested - inside a class directive to create class constants. - -.. rst:directive:: .. php:exception:: name - - This directive declares a new Exception in the current namespace. The - signature can include constructor arguments. - -.. rst:directive:: .. php:class:: name - - Describes a class. Methods, attributes, and constants belonging to the class - should be inside this directive's body:: - - .. php:class:: MyClass - - Class description - - .. php:method:: method($argument) - - Method description - - Attributes, methods and constants don't need to be nested. They can also just - follow the class declaration:: - - .. php:class:: MyClass - - Text about the class - - .. php:method:: methodName() - - Text about the method - - .. seealso:: :rst:dir:`php:method`, :rst:dir:`php:attr`, :rst:dir:`php:const` - -.. rst:directive:: .. php:method:: name(signature) - - Describe a class method, its arguments, return value, and exceptions:: - - .. php:method:: instanceMethod($one, $two) - - :param string $one: The first parameter. - :param string $two: The second parameter. - :returns: An array of stuff. - :throws: InvalidArgumentException - - This is an instance method. - -.. rst:directive:: .. php:staticmethod:: ClassName::methodName(signature) - - Describe a static method, its arguments, return value and exceptions, - see :rst:dir:`php:method` for options. - -.. rst:directive:: .. php:attr:: name - - Describe an property/attribute on a class. - -Prevent Sphinx to Output Warnings -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Sphinx will output warnings if a function is referenced in multiple files. It's -a great way to ensure that you did not add a function two times, but -sometimes, you actually want to write a function in two or more files, eg. -`debug object` is referenced in `/development/debugging` and in -`/core-libraries/global-constants-and-functions`. In this case, you can add -``:noindex:`` under the function debug to suppress warnings. Keep only -one reference **without** ``:no-index:`` to still have the function referenced:: - - .. php:function:: debug(mixed $var, boolean $showHtml = null, $showFrom = true) - :noindex: - -Cross Referencing -~~~~~~~~~~~~~~~~~ - -The following roles refer to PHP objects and links are generated if a -matching directive is found: - -.. rst:role:: php:func - - Reference a PHP function. - -.. rst:role:: php:global - - Reference a global variable whose name has ``$`` prefix. - -.. rst:role:: php:const - - Reference either a global constant, or a class constant. Class constants - should be preceded by the owning class:: - - DateTime has an :php:const:`DateTime::ATOM` constant. - -.. rst:role:: php:class - - Reference a class by name:: - - :php:class:`ClassName` - -.. rst:role:: php:meth - - Reference a method of a class. This role supports both kinds of methods:: - - :php:meth:`DateTime::setDate` - :php:meth:`Classname::staticMethod` - -.. rst:role:: php:attr - - Reference a property on an object:: - - :php:attr:`ClassName::$propertyName` - -.. rst:role:: php:exc - - Reference an exception. - -Source Code ------------ - -Literal code blocks are created by ending a paragraph with ``::``. The literal -block must be indented, and like all paragraphs be separated by single lines:: - - This is a paragraph:: - - while ($i--) { - doStuff() - } - - This is regular text again. - -Literal text is not modified or formatted, save that one level of indentation -is removed. - -Notes and Warnings ------------------- - -There are often times when you want to inform the reader of an important tip, -special note or a potential hazard. Admonitions in sphinx are used for just -that. There are fives kinds of admonitions. - -* ``.. tip::`` Tips are used to document or re-iterate interesting or important - information. The content of the directive should be written in complete - sentences and include all appropriate punctuation. -* ``.. note::`` Notes are used to document an especially important piece of - information. The content of the directive should be written in complete - sentences and include all appropriate punctuation. -* ``.. warning::`` Warnings are used to document potential stumbling blocks, or - information pertaining to security. The content of the directive should be - written in complete sentences and include all appropriate punctuation. -* ``.. versionadded:: X.Y.Z`` "Version added" admonitions are used to display notes - specific to new features added at a specific version, ``X.Y.Z`` being the version on - which the said feature was added. -* ``.. deprecated:: X.Y.Z`` As opposed to "version added" admonitions, "deprecated" - admonition are used to notify of a deprecated feature, ``X.Y.Z`` being the version on - which the said feature was deprecated. - -All admonitions are made the same:: - - .. note:: - - Indented and preceded and followed by a blank line. Just like a - paragraph. - - This text is not part of the note. - -Samples -~~~~~~~ - -.. tip:: - - This is a helpful tid-bit you probably forgot. - -.. note:: - - You should pay attention here. - -.. warning:: - - It could be dangerous. - -.. versionadded:: 4.0.0 - - This awesome feature was added in version 4.0.0 - -.. deprecated:: 4.0.1 - - This old feature was deprecated on version 4.0.1 - -.. meta:: - :title lang=en: Documentation - :keywords lang=en: partial translations,translation efforts,html entities,text markup,asfd,asdf,structured text,english content,markdown,formatted text,dot org,repo,consistency,translator,freenode,textile,improvements,syntax,cakephp,submission diff --git a/en/contributing/tickets.rst b/en/contributing/tickets.rst deleted file mode 100644 index 4de327cc0c..0000000000 --- a/en/contributing/tickets.rst +++ /dev/null @@ -1,50 +0,0 @@ -Tickets -####### - -Getting feedback and help from the community in the form of tickets is an -extremely important part of the CakePHP development process. All of CakePHP's -tickets are hosted on `GitHub `_. - -Reporting Bugs -============== - -Well written bug reports are very helpful. There are a few steps to help create -the best bug report possible: - -* **Do**: Please `search `_ - for a similar existing ticket, and ensure someone hasn't already reported your - issue, or that it hasn't already been fixed in the repository. -* **Do**: Please include detailed instructions on **how to reproduce the bug**. - This could be in the form of a test-case or a snippet of code that - demonstrates the issue. Not having a way to reproduce an issue means it's less - likely to get fixed. -* **Do**: Please give as many details as possible about your environment: (OS, - PHP version, CakePHP version). -* **Don't**: Please don't use the ticket system to ask support questions. Both the support channel on the - `CakePHP Slack workspace `__ and the #cakephp IRC channel on `Freenode `__ have many - developers available to help answer your questions. Also have a look at - `Stack Overflow `__ or the `official CakePHP forum `__. - -Reporting Security Issues -========================= - -If you've found a security issue in CakePHP, please use the following procedure -instead of the normal bug reporting system. Instead of using the bug tracker, -mailing list or IRC please send an email to **security [at] cakephp.org**. -Emails sent to this address go to the CakePHP core team on a private mailing -list. - -For each report, we try to first confirm the vulnerability. Once confirmed, the -CakePHP team will take the following actions: - -* Acknowledge to the reporter that we've received the issue, and are working on - a fix. We ask that the reporter keep the issue confidential until we announce - it. -* Get a fix/patch prepared. -* Prepare a post describing the vulnerability, and the possible exploits. -* Release new versions of all affected versions. -* Prominently feature the problem in the release announcement. - -.. meta:: - :title lang=en: Tickets - :keywords lang=en: bug reporting system,code snippet,reporting security,private mailing,release announcement,google,ticket system,core team,security issue,bug tracker,irc channel,test cases,support questions,bug report,security issues,bug reports,exploits,vulnerability,repository diff --git a/en/controllers.rst b/en/controllers.rst deleted file mode 100644 index 117dfbb245..0000000000 --- a/en/controllers.rst +++ /dev/null @@ -1,609 +0,0 @@ -Controllers -########### - -.. php:namespace:: Cake\Controller - -.. php:class:: Controller - -Controllers are the 'C' in MVC. After routing has been applied and the correct -controller has been found, your controller's action is called. Your controller -should handle interpreting the request data, making sure the correct models -are called, and the right response or view is rendered. Controllers can be -thought of as middle layer between the Model and View. You want to keep your -controllers thin, and your models fat. This will help you reuse -your code and makes your code easier to test. - -Commonly, a controller is used to manage the logic around a single model. For -example, if you were building a site for an online bakery, you might have a -RecipesController managing your recipes and an IngredientsController managing your -ingredients. However, it's also possible to have controllers work with more than -one model. In CakePHP, a controller is named after the primary model it -handles. - -Your application's controllers extend the ``AppController`` class, which in turn -extends the core :php:class:`Controller` class. The ``AppController`` -class can be defined in **src/Controller/AppController.php** and it should -contain methods that are shared between all of your application's controllers. - -Controllers provide a number of methods that handle requests. These are called -*actions*. By default, each public method in -a controller is an action, and is accessible from a URL. An action is responsible -for interpreting the request and creating the response. Usually responses are -in the form of a rendered view, but there are other ways to create responses as -well. - -.. _app-controller: - -The App Controller -================== - -As stated in the introduction, the ``AppController`` class is the parent class -to all of your application's controllers. ``AppController`` itself extends the -:php:class:`Cake\\Controller\\Controller` class included in CakePHP. -``AppController`` is defined in **src/Controller/AppController.php** as -follows:: - - namespace App\Controller; - - use Cake\Controller\Controller; - - class AppController extends Controller - { - } - -Controller attributes and methods created in your ``AppController`` will be -available in all controllers that extend it. Components (which you'll -learn about later) are best used for code that is used in many (but not -necessarily all) controllers. - -You can use your ``AppController`` to load components that will be used in every -controller in your application. CakePHP provides a ``initialize()`` method that -is invoked at the end of a Controller's constructor for this kind of use:: - - namespace App\Controller; - - use Cake\Controller\Controller; - - class AppController extends Controller - { - public function initialize(): void - { - // Always enable the FormProtection component. - $this->loadComponent('FormProtection'); - } - } - -Request Flow -============ - -When a request is made to a CakePHP application, CakePHP's -:php:class:`Cake\\Routing\\Router` and :php:class:`Cake\\Routing\\Dispatcher` -classes use :ref:`routes-configuration` to find and create the correct -controller instance. The request data is encapsulated in a request object. -CakePHP puts all of the important request information into the ``$this->request`` -property. See the section on :ref:`cake-request` for more information on the -CakePHP request object. - -Controller Actions -================== - -Controller actions are responsible for converting the request parameters into a -response for the browser/user making the request. CakePHP uses conventions to -automate this process and remove some boilerplate code you would otherwise need -to write. - -By convention, CakePHP renders a view with an inflected version of the action -name. Returning to our online bakery example, our RecipesController might contain the -``view()``, ``share()``, and ``search()`` actions. The controller would be found -in **src/Controller/RecipesController.php** and contain:: - - // src/Controller/RecipesController.php - - class RecipesController extends AppController - { - public function view($id) - { - // Action logic goes here. - } - - public function share($customerId, $recipeId) - { - // Action logic goes here. - } - - public function search($query) - { - // Action logic goes here. - } - } - -The template files for these actions would be **templates/Recipes/view.php**, -**templates/Recipes/share.php**, and **templates/Recipes/search.php**. The -conventional view file name is the lowercased and underscored version of the -action name. - -Controller actions generally use -``Controller::set()`` to create a context that -``View`` uses to render the view layer. Because of the conventions that -CakePHP uses, you don't need to create and render the view manually. Instead, -once a controller action has completed, CakePHP will handle rendering and -delivering the View. - -If for some reason you'd like to skip the default behavior, you can return a -:php:class:`Cake\\Http\\Response` object from the action with the fully -created response. - -In order for you to use a controller effectively in your own application, we'll -cover some of the core attributes and methods provided by CakePHP's controllers. - -Interacting with Views -====================== - -Controllers interact with views in a number of ways. First, they -are able to pass data to the views, using ``Controller::set()``. You can also -decide which view class to use, and which view file should be -rendered from the controller. - -.. _setting-view_variables: - -Setting View Variables ----------------------- - -.. php:method:: set(string $var, mixed $value) - -The ``Controller::set()`` method is the main way to send data from your -controller to your view. Once you've used ``Controller::set()``, the variable -can be accessed in your view:: - - // First you pass data from the controller: - - $this->set('color', 'pink'); - - // Then, in the view, you can utilize the data: - ?> - - You have selected icing for the cake. - -The ``Controller::set()`` method also takes an -associative array as its first parameter. This can often be a quick way to -assign a set of information to the view:: - - $data = [ - 'color' => 'pink', - 'type' => 'sugar', - 'base_price' => 23.95, - ]; - - // Make $color, $type, and $base_price - // available to the view: - - $this->set($data); - -Keep in mind that view vars are shared among all parts rendered by your view. -They will be available in all parts of the view: the template, the layout and -all elements inside the former two. - -Setting View Options --------------------- - -If you want to customize the view class, layout/template paths, helpers or the -theme that will be used when rendering the view, you can use the -``viewBuilder()`` method to get a builder. This builder can be used to define -properties of the view before it is created:: - - $this->viewBuilder() - ->addHelper('MyCustom') - ->setTheme('Modern') - ->setClassName('Modern.Admin'); - -The above shows how you can load custom helpers, set the theme and use a custom -view class. - -Rendering a View ----------------- - -.. php:method:: render(string $view, string $layout) - -The ``Controller::render()`` method is automatically called at the end of each requested -controller action. This method performs all the view logic (using the data -you've submitted using the ``Controller::set()`` method), places the view inside its -``View::$layout``, and serves it back to the end user. - -The default view file used by render is determined by convention. -If the ``search()`` action of the RecipesController is requested, -the view file in **templates/Recipes/search.php** will be rendered:: - - namespace App\Controller; - - class RecipesController extends AppController - { - // ... - public function search() - { - // Render the view in templates/Recipes/search.php - return $this->render(); - } - // ... - } - -Although CakePHP will automatically call it after every action's logic -(unless you've called ``$this->disableAutoRender()``), you can use it to specify -an alternate view file by specifying a view file name as first argument of -``Controller::render()`` method. - -If ``$view`` starts with '/', it is assumed to be a view or -element file relative to the **templates** folder. This allows -direct rendering of elements, very useful in AJAX calls:: - - // Render the element in templates/element/ajaxreturn.php - $this->render('/element/ajaxreturn'); - -The second parameter ``$layout`` of ``Controller::render()`` allows you to specify the layout -with which the view is rendered. - -Rendering a Specific Template -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In your controller, you may want to render a different view than the -conventional one. You can do this by calling ``Controller::render()`` directly. Once you -have called ``Controller::render()``, CakePHP will not try to re-render the view:: - - namespace App\Controller; - - class PostsController extends AppController - { - public function my_action() - { - $this->render('custom_file'); - } - } - -This would render **templates/Posts/custom_file.php** instead of -**templates/Posts/my_action.php**. - -You can also render views inside plugins using the following syntax: -``$this->render('PluginName.PluginController/custom_file')``. -For example:: - - namespace App\Controller; - - class PostsController extends AppController - { - public function myAction() - { - $this->render('Users.UserDetails/custom_file'); - } - } - -This would render **plugins/Users/templates/UserDetails/custom_file.php** - -.. _controller-viewclasses: - -Content Type Negotiation -======================== - -.. php:method:: addViewClasses() - -Controllers can define a list of view classes they support. After the -controller's action is complete CakePHP will use the view list to perform -content-type negotiation with either :ref:`file-extensions` or ``Accept`` -headers. This enables your application to re-use the same controller action to -render an HTML view or render a JSON or XML response. To define the list of -supported view classes for a controller is done with the ``addViewClasses()`` -method:: - - namespace App\Controller; - - use Cake\View\JsonView; - use Cake\View\XmlView; - - class PostsController extends AppController - { - public function initialize(): void - { - parent::initialize(); - - $this->addViewClasses([JsonView::class, XmlView::class]); - } - } - -The application's ``View`` class is automatically used as a fallback when no -other view can be selected based on the request's ``Accept`` header or routing -extension. If your application only supports content types for a specific -actions, you can call ``addClasses()`` within your action too:: - - public function export(): void - { - // Use a custom CSV view for data exports. - $this->addViewClasses([CsvView::class]); - - // Rest of the action code - } - -If within your controller actions you need to process the request or load data -differently based on the content type you can use -:ref:`check-the-request`:: - - // In a controller action - - // Load additional data when preparing JSON responses - if ($this->request->is('json')) { - $query->contain('Authors'); - } - -In case your app need more complex logic to decide which view classes to use -then you can override the ``Controller::viewClasses()`` method and return -an array of view classes as required. - -.. note:: - View classes must implement the static ``contentType()`` hook method to - participate in content-type negotiation. - -Content Type Negotiation Fallbacks -================================== - -If no View can be matched with the request's content type preferences, CakePHP -will use the base ``View`` class. If you want to require content-type -negotiation, you can use the ``NegotiationRequiredView`` which sets a ``406`` status -code:: - - public function initialize(): void - { - parent::initialize(); - - // Require Accept header negotiation or return a 406 response. - $this->addViewClasses([JsonView::class, NegotiationRequiredView::class]); - } - -You can use the ``TYPE_MATCH_ALL`` content type value to build your own fallback -view logic:: - - namespace App\View; - - use Cake\View\View; - - class CustomFallbackView extends View - { - public static function contentType(): string - { - return static::TYPE_MATCH_ALL; - } - - } - -It is important to remember that match-all views are applied only *after* -content-type negotiation is attempted. - -Using AjaxView -============== - -In applications that use hypermedia or AJAX clients, you often need to render -view contents without the wrapping layout. You can use the ``AjaxView`` that -is bundled with the application skeleton:: - - // In a controller action, or in beforeRender. - if ($this->request->is('ajax')) { - $this->viewBuilder()->setClassName('Ajax'); - } - -``AjaxView`` will respond as ``text/html`` and use the ``ajax`` layout. -Generally this layout is minimal or contains client specific markup. This -replaces usage of ``RequestHandlerComponent`` automatically using the -``AjaxView`` in 4.x. - -Redirecting to Other Pages -========================== - -.. php:method:: redirect(string|array $url, integer $status) - -The ``redirect()`` method adds a ``Location`` header and sets the status code of -a response and returns it. You should return the response created by -``redirect()`` to have CakePHP send the redirect instead of completing the -controller action and rendering a view. - -You can redirect using :term:`routing array` values:: - - return $this->redirect([ - 'controller' => 'Orders', - 'action' => 'confirm', - $order->id, - '?' => [ - 'product' => 'pizza', - 'quantity' => 5 - ], - '#' => 'top' - ]); - -Or using a relative or absolute URL:: - - return $this->redirect('/orders/confirm'); - - return $this->redirect('http://www.example.com'); - -Or to the referer page:: - - return $this->redirect($this->referer()); - -By using the second parameter you can define a status code for your redirect:: - - // Do a 301 (moved permanently) - return $this->redirect('/order/confirm', 301); - - // Do a 303 (see other) - return $this->redirect('/order/confirm', 303); - -See the :ref:`redirect-component-events` section for how to redirect out of -a life-cycle handler. - -Loading Additional Tables/Models -================================ - -.. php:method:: fetchTable(string $alias, array $config = []) - -The ``fetchTable()`` method comes handy when you need to use an ORM table that is not -the controller's default one:: - - // In a controller method. - $recentArticles = $this->fetchTable('Articles')->find('all', - limit: 5, - order: 'Articles.created DESC' - ) - ->all(); - -.. php:method:: fetchModel(string|null $modelClass = null, string|null $modelType = null) - -The ``fetchModel()`` method is useful to load non ORM models or ORM tables that -are not the controller's default:: - - // ModelAwareTrait need to be explicity added to your controler first for fetchModel() to work. - use ModelAwareTrait; - - // Get an ElasticSearch model - $articles = $this->fetchModel('Articles', 'Elastic'); - - // Get a webservices model - $github = $this->fetchModel('GitHub', 'Webservice'); - - // If you skip the 2nd argument it will by default try to load a ORM table. - $authors = $this->fetchModel('Authors'); - -.. versionadded:: 4.5.0 - -Paginating a Model -================== - -.. php:method:: paginate() - -This method is used for paginating results fetched by your models. -You can specify page sizes, model find conditions and more. See the -:doc:`pagination ` section for more details on -how to use ``paginate()``. - -The ``$paginate`` attribute gives you a way to customize how ``paginate()`` -behaves:: - - class ArticlesController extends AppController - { - protected array $paginate = [ - 'Articles' => [ - 'conditions' => ['published' => 1], - ], - ]; - } - -Configuring Components to Load -============================== - -.. php:method:: loadComponent($name, $config = []) - -In your Controller's ``initialize()`` method you can define any components you -want loaded, and any configuration data for them:: - - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('Flash'); - $this->loadComponent('Comments', Configure::read('Comments')); - } - -.. _controller-life-cycle: - -Request Life-cycle Callbacks -============================ - -CakePHP controllers trigger several events/callbacks that you can use to insert -logic around the request life-cycle: - -Event List ----------- - -* ``Controller.initialize`` -* ``Controller.startup`` -* ``Controller.beforeRedirect`` -* ``Controller.beforeRender`` -* ``Controller.shutdown`` - -Controller Callback Methods ---------------------------- - -By default the following callback methods are connected to related events if the -methods are implemented by your controllers - -.. php:method:: beforeFilter(EventInterface $event) - - Called during the ``Controller.initialize`` event which occurs before every - action in the controller. It's a handy place to check for an active session - or inspect user permissions. - - .. note:: - - The beforeFilter() method will be called for missing actions. - - Returning a response from a ``beforeFilter`` method will not prevent other - listeners of the same event from being called. You must explicitly - :ref:`stop the event `. - -.. php:method:: beforeRender(EventInterface $event) - - Called during the ``Controller.beforeRender`` event which occurs after - controller action logic, but before the view is rendered. This callback is - not used often, but may be needed if you are calling - :php:meth:`Cake\\Controller\\Controller::render()` manually before the end - of a given action. - -.. php:method:: afterFilter(EventInterface $event) - - Called during the ``Controller.shutdown`` event which is triggered after - every controller action, and after rendering is complete. This is the last - controller method to run. - -In addition to controller life-cycle callbacks, :doc:`/controllers/components` -also provide a similar set of callbacks. - -Remember to call ``AppController``'s callbacks within child controller callbacks -for best results:: - - //use Cake\Event\EventInterface; - public function beforeFilter(EventInterface $event) - { - parent::beforeFilter($event); - } - -.. _controller-middleware: - -Controller Middleware -===================== - -.. php:method:: middleware($middleware, array $options = []) - -:doc:`Middleware ` can be defined globally, in -a routing scope or within a controller. To define middleware for a specific -controller use the ``middleware()`` method from your controller's -``initialize()`` method:: - - public function initialize(): void - { - parent::initialize(); - - $this->middleware(function ($request, $handler) { - // Do middleware logic. - - // Make sure you return a response or call handle() - return $handler->handle($request); - }); - } - -Middleware defined by a controller will be called **before** ``beforeFilter()`` and action methods are called. - -More on Controllers -=================== - -.. toctree:: - :maxdepth: 1 - - controllers/pages-controller - controllers/components - -.. meta:: - :title lang=en: Controllers - :keywords lang=en: correct models,controller class,controller controller,core library,single model,request data,middle man,bakery,mvc,attributes,logic,recipes diff --git a/en/controllers/components.rst b/en/controllers/components.rst deleted file mode 100644 index 44ec1be50a..0000000000 --- a/en/controllers/components.rst +++ /dev/null @@ -1,368 +0,0 @@ -Components -########## - -Components are packages of logic that are shared between controllers. -CakePHP comes with a fantastic set of core components you can use to aid in -various common tasks. You can also create your own components. If you find -yourself wanting to copy and paste things between controllers, you should -consider creating your own component to contain the functionality. Creating -components keeps controller code clean and allows you to reuse code between -different controllers. - -For more information on the components included in CakePHP, check out the -chapter for each component: - -.. toctree:: - :maxdepth: 1 - - /controllers/components/flash - /controllers/components/form-protection - /controllers/components/check-http-cache - -.. _configuring-components: - -Configuring Components -====================== - -Many of the core components require configuration. One example would be -the :doc:`/controllers/components/form-protection`. Configuration for these components, -and for components in general, is usually done via ``loadComponent()`` in your -Controller's ``initialize()`` method or via the ``$components`` array:: - - class PostsController extends AppController - { - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('FormProtection', [ - 'unlockedActions' => ['index'], - ]); - $this->loadComponent('Flash'); - } - } - -You can configure components at runtime using the ``setConfig()`` method. Often, -this is done in your controller's ``beforeFilter()`` method. The above could -also be expressed as:: - - public function beforeFilter(EventInterface $event) - { - $this->FormProtection->setConfig('unlockedActions', ['index']); - } - -Like helpers, components implement ``getConfig()`` and ``setConfig()`` methods -to read and write configuration data:: - - // Read config data. - $this->FormProtection->getConfig('unlockedActions'); - - // Set config - $this->Flash->setConfig('key', 'myFlash'); - -As with helpers, components will automatically merge their ``$_defaultConfig`` -property with constructor configuration to create the ``$_config`` property -which is accessible with ``getConfig()`` and ``setConfig()``. - -Aliasing Components -------------------- - -One common setting to use is the ``className`` option, which allows you to -alias components. This feature is useful when you want to -replace ``$this->Flash`` or another common Component reference with a custom -implementation:: - - // src/Controller/PostsController.php - class PostsController extends AppController - { - public function initialize(): void - { - $this->loadComponent('Flash', [ - 'className' => 'MyFlash', - ]); - } - } - - // src/Controller/Component/MyFlashComponent.php - use Cake\Controller\Component\FlashComponent; - - class MyFlashComponent extends FlashComponent - { - // Add your code to override the core FlashComponent - } - -The above would *alias* ``MyFlashComponent`` to ``$this->Flash`` in your -controllers. - -.. note:: - - Aliasing a component replaces that instance anywhere that component is used, - including inside other Components. - -Loading Components on the Fly ------------------------------ - -You might not need all of your components available on every controller -action. In situations like this you can load a component at runtime using the -``loadComponent()`` method in your controller:: - - // In a controller action - $this->loadComponent('OneTimer'); - $time = $this->OneTimer->getTime(); - -.. note:: - - Keep in mind that components loaded on the fly will not have missed - callbacks called. If you rely on the ``beforeFilter`` or ``startup`` - callbacks being called, you may need to call them manually depending on when - you load your component. - -Using Components -================ - -Once you've included some components in your controller, using them is pretty -simple. Each component you use is exposed as a property on your controller. If -you had loaded up the :php:class:`Cake\\Controller\\Component\\FlashComponent` -in your controller, you could access it like so:: - - class PostsController extends AppController - { - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('Flash'); - } - - public function delete() - { - if ($this->Post->delete($this->request->getData('Post.id')) { - $this->Flash->success('Post deleted.'); - - return $this->redirect(['action' => 'index']); - } - } - -.. note:: - - Since both Models and Components are added to Controllers as - properties they share the same 'namespace'. Be sure to not give a - component and a model the same name. - -.. versionchanged:: 5.1.0 - Components are able to use :doc:`/development/dependency-injection` to receive services. - -.. _creating-a-component: - -Creating a Component -==================== - -Suppose our application needs to perform a complex mathematical operation in -many different parts of the application. We could create a component to house -this shared logic for use in many different controllers. - -The first step is to create a new component file and class. Create the file in -**src/Controller/Component/MathComponent.php**. The basic structure for the -component would look something like this:: - - namespace App\Controller\Component; - - use Cake\Controller\Component; - - class MathComponent extends Component - { - public function doComplexOperation($amount1, $amount2) - { - return $amount1 + $amount2; - } - } - -.. note:: - - All components must extend :php:class:`Cake\\Controller\\Component`. Failing - to do this will trigger an exception. - -Components can use :doc:`/development/dependency-injection` to receive services -as constructor parameters:: - - namespace App\Controller\Component; - - use Cake\Controller\Component; - use App\Service\UserService; - - class SsoComponent extends Component - { - public function __construct( - ComponentRegistry $registry, - array $config = [], - UserService $users - ) { - parent::__construct($registry, $config); - $this->users = $users; - } - } - -.. versionadded: 5.1.0 - DI container support for Components was added. - -Including your Component in your Controllers --------------------------------------------- - -Once our component is finished, we can use it in the application's -controllers by loading it during the controller's ``initialize()`` method. -Once loaded, the controller will be given a new attribute named after the -component, through which we can access an instance of it:: - - // In a controller - // Make the new component available at $this->Math, - // as well as the standard $this->Flash - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('Math'); - $this->loadComponent('Flash'); - } - -When including Components in a Controller you can also declare a -set of parameters that will be passed on to the Component's -constructor. These parameters can then be handled by -the Component:: - - // In your controller. - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('Math', [ - 'precision' => 2, - 'randomGenerator' => 'srand', - ]); - $this->loadComponent('Flash'); - } - -The above would pass the array containing precision and randomGenerator to -``MathComponent::initialize()`` in the ``$config`` parameter. - -Using Other Components in your Component ----------------------------------------- - -Sometimes one of your components may need to use another component. -You can load other components by adding them to the `$components` property:: - - // src/Controller/Component/CustomComponent.php - namespace App\Controller\Component; - - use Cake\Controller\Component; - - class CustomComponent extends Component - { - // The other component your component uses - protected array $components = ['Existing']; - - // Execute any other additional setup for your component. - public function initialize(array $config): void - { - $this->Existing->foo(); - } - - public function bar() - { - // ... - } - } - - // src/Controller/Component/ExistingComponent.php - namespace App\Controller\Component; - - use Cake\Controller\Component; - - class ExistingComponent extends Component - { - public function foo() - { - // ... - } - } - -.. note:: - - In contrast to a component included in a controller - no callbacks will be triggered on a component's component. - -Accessing a Component's Controller ----------------------------------- - -From within a Component you can access the current controller through the -registry:: - - $controller = $this->getController(); - -Component Callbacks -=================== - -Components also offer a few request life-cycle callbacks that allow them to -augment the request cycle. - -.. php:method:: beforeFilter(EventInterface $event) - - Is called before the controller's - beforeFilter() method, but *after* the controller's initialize() method. - -.. php:method:: startup(EventInterface $event) - - Is called after the controller's beforeFilter() - method but before the controller executes the current action - handler. - -.. php:method:: beforeRender(EventInterface $event) - - Is called after the controller executes the requested action's logic, - but before the controller renders views and layout. - -.. php:method:: afterFilter(EventInterface $event) - - Is called during the ``Controller.shutdown`` event, before output is sent to the browser. - -.. php:method:: beforeRedirect(EventInterface $event, $url, Response $response) - - Is invoked when the controller's redirect - method is called but before any further action. If this method - returns ``false`` the controller will not continue on to redirect the - request. The $url, and $response parameters allow you to inspect and modify - the location or any other headers in the response. - -.. _redirect-component-events: - -Using Redirects in Component Events -=================================== - -To redirect from within a component callback method you can use the following:: - - public function beforeFilter(EventInterface $event) - { - $event->stopPropagation(); - - return $this->getController()->redirect('/'); - } - -By stopping the event you let CakePHP know that you don't want any other -component callbacks to run, and that the controller should not handle the action -any further. As of 4.1.0 you can raise a ``RedirectException`` to signal -a redirect:: - - use Cake\Http\Exception\RedirectException; - use Cake\Routing\Router; - - public function beforeFilter(EventInterface $event) - { - throw new RedirectException(Router::url('/')) - } - -Raising an exception will halt all other event listeners and create a new -response that doesn't retain or inherit any of the current response's headers. -When raising a ``RedirectException`` you can include additional headers:: - - throw new RedirectException(Router::url('/'), 302, [ - 'Header-Key' => 'value', - ]); - -.. meta:: - :title lang=en: Components - :keywords lang=en: array controller,core libraries,array name,access control lists,public components,controller code,core components,cookiemonster,login cookie,configuration settings,functionality,logic,sessions,cakephp,doc diff --git a/en/controllers/components/check-http-cache.rst b/en/controllers/components/check-http-cache.rst deleted file mode 100644 index 5813857667..0000000000 --- a/en/controllers/components/check-http-cache.rst +++ /dev/null @@ -1,33 +0,0 @@ -Checking HTTP Cache -=================== - -.. php:class:: CheckHttpCacheComponent(ComponentCollection $collection, array $config = []) - -The HTTP cache validation model is one of the processes used for cache gateways, -also known as reverse proxies, to determine if they can serve a stored copy of -a response to the client. Under this model, you mostly save bandwidth, but when -used correctly you can also save some CPU processing, reducing response -times:: - - // in a Controller - public function initialize(): void - { - parent::initialize(); - - $this->addComponent('CheckHttpCache'); - } - -Enabling the ``CheckHttpCacheComponent`` in your controller automatically -activates a ``beforeRender`` check. This check compares caching headers set in -the response object to the caching headers sent in the request to determine -whether the response was not modified since the last time the client asked for -it. The following request headers are used: - -* ``If-None-Match`` is compared with the response's ``Etag`` header. -* ``If-Modified-Since`` is compared with the response's ``Last-Modified`` - header. - -If response headers match the request header criteria, then view rendering is -skipped. This saves your application generating a view, saving bandwidth and -time. When response headers match, an empty response is returned with a ``304 -Not Modified`` status code. diff --git a/en/controllers/components/flash.rst b/en/controllers/components/flash.rst deleted file mode 100644 index a8d3674439..0000000000 --- a/en/controllers/components/flash.rst +++ /dev/null @@ -1,103 +0,0 @@ -Flash -##### - -.. php:namespace:: Cake\Controller\Component - -.. php:class:: FlashComponent(ComponentCollection $collection, array $config = []) - -FlashComponent provides a way to set one-time notification messages to be -displayed after processing a form or acknowledging data. CakePHP refers to these -messages as "flash messages". FlashComponent writes flash messages to -``$_SESSION``, to be rendered in a View using -:doc:`FlashHelper `. - -Setting Flash Messages -====================== - -FlashComponent provides two ways to set flash messages: its ``__call()`` magic -method and its ``set()`` method. To furnish your application with verbosity, -FlashComponent's ``__call()`` magic method allows you use a method name that -maps to an element located under the **templates/element/flash** directory. -By convention, camelcased methods will map to the lowercased and underscored -element name:: - - // Uses templates/element/flash/success.php - $this->Flash->success('This was successful'); - - // Uses templates/element/flash/great_success.php - $this->Flash->greatSuccess('This was greatly successful'); - -Alternatively, to set a plain-text message without rendering an element, you can -use the ``set()`` method:: - - $this->Flash->set('This is a message'); - -Flash messages are appended to an array internally. Successive calls to -``set()`` or ``__call()`` with the same key will append the messages in the -``$_SESSION``. If you want to overwrite existing messages when setting a flash -message, set the ``clear`` option to ``true`` when configuring the Component. - -FlashComponent's ``__call()`` and ``set()`` methods optionally take a second -parameter, an array of options: - -* ``key`` Defaults to 'flash'. The array key found under the ``Flash`` key in - the session. -* ``element`` Defaults to ``null``, but will automatically be set when using the - ``__call()`` magic method. The element name to use for rendering. -* ``params`` An optional array of keys/values to make available as variables - within an element. -* ``clear`` expects a ``bool`` and allows you to delete all messages in the - current stack and start a new one. - -An example of using these options:: - - // In your Controller - $this->Flash->success('The user has been saved', [ - 'key' => 'positive', - 'clear' => true, - 'params' => [ - 'name' => $user->name, - 'email' => $user->email, - ], - ]); - - // In your View - Flash->render('positive') ?> - - -
      - : , . -
      - -Note that the parameter ``element`` will be always overridden while using -``__call()``. In order to retrieve a specific element from a plugin, you should -set the ``plugin`` parameter. For example:: - - // In your Controller - $this->Flash->warning('My message', ['plugin' => 'PluginName']); - -The code above will use the **warning.php** element under -**plugins/PluginName/templates/element/flash** for rendering the flash -message. - -.. note:: - - By default, CakePHP escapes the content in flash messages to prevent cross - site scripting. User data in your flash messages will be HTML encoded and - safe to be printed. If you want to include HTML in your flash messages, you - need to pass the ``escape`` option and adjust your flash message templates - to allow disabling escaping when the escape option is passed. - -HTML in Flash Messages -====================== - -It is possible to output HTML in flash messages by using the ``'escape'`` option -key:: - - $this->Flash->info(sprintf('%s %s', h($highlight), h($message)), ['escape' => false]); - -Make sure that you escape the input manually, then. In the above example -``$highlight`` and ``$message`` are non-HTML input and therefore escaped. - -For more information about rendering your flash messages, please refer to the -:doc:`FlashHelper ` section. diff --git a/en/controllers/components/form-protection.rst b/en/controllers/components/form-protection.rst deleted file mode 100644 index b8fca5945b..0000000000 --- a/en/controllers/components/form-protection.rst +++ /dev/null @@ -1,167 +0,0 @@ -Form Protection Component -######################### - -.. php:class:: FormProtection(ComponentCollection $collection, array $config = []) - -The FormProtection Component provides protection against form data tampering. - -Like all components it is configured through several configurable parameters. -All of these properties can be set directly or through setter methods of the -same name in your controller's ``initialize()`` or ``beforeFilter()`` methods. - -If you are using other components that process form data in their ``startup()`` -callbacks, be sure to place FormProtection Component before those components -in your ``initialize()`` method. - -.. note:: - - When using the FormProtection Component you **must** use the FormHelper to create - your forms. In addition, you must **not** override any of the fields' "name" - attributes. The FormProtection Component looks for certain indicators that are - created and managed by the FormHelper (especially those created in - :php:meth:`~Cake\\View\\Helper\\FormHelper::create()` and - :php:meth:`~Cake\\View\\Helper\\FormHelper::end()`). Dynamically altering - the fields that are submitted in a POST request, such as disabling, deleting - or creating new fields via JavaScript, is likely to cause the form token - validation to fail. - -Form tampering prevention -========================= - -By default the ``FormProtectionComponent`` prevents users from tampering with -forms in specific ways. It will prevent the following things: - -* Form's action (URL) cannot be modified. -* Unknown fields cannot be added to the form. -* Fields cannot be removed from the form. -* Values in hidden inputs cannot be modified. - -Preventing these types of tampering is accomplished by working with the ``FormHelper`` -and tracking which fields are in a form. The values for hidden fields are -tracked as well. All of this data is combined and turned into a hash and hidden -token fields are automatically be inserted into forms. When a form is submitted, -the ``FormProtectionComponent`` will use the POST data to build the same structure -and compare the hash. - -.. note:: - - The FormProtectionComponent will **not** prevent select options from being - added/changed. Nor will it prevent radio options from being added/changed. - -Usage -===== - -Configuring the form protection component is generally done in the controller's -``initialize()`` or ``beforeFilter()`` callbacks - -Available options are: - -validate - Set to ``false`` to completely skip the validation of POST - requests, essentially turning off form validation. - -unlockedFields - Set to a list of form fields to exclude from POST validation. Fields can be - unlocked either in the Component, or with - :php:meth:`FormHelper::unlockField()`. Fields that have been unlocked are - not required to be part of the POST and hidden unlocked fields do not have - their values checked. - -unlockedActions - Actions to exclude from POST validation checks. - -validationFailureCallback - Callback to call in case of validation failure. Must be a valid Closure. - Unset by default in which case exception is thrown on validation failure. - -Disabling form tampering checks -=============================== - -:: - - namespace App\Controller; - - use App\Controller\AppController; - use Cake\Event\EventInterface; - - class WidgetsController extends AppController - { - public function initialize(): void - { - parent::initialize(); - - $this->loadComponent('FormProtection'); - } - - public function beforeFilter(EventInterface $event) - { - parent::beforeFilter($event); - - if ($this->request->getParam('prefix') === 'Admin') { - $this->FormProtection->setConfig('validate', false); - } - } - } - -The above example would disable form tampering prevention for admin prefixed -routes. - -Disabling form tampering for specific actions -============================================= - -There may be cases where you want to disable form tampering prevention for an -action (ex. AJAX requests). You may "unlock" these actions by listing them in -``$this->FormProtection->setConfig('unlockedActions', ['edit']);`` in your ``beforeFilter()``:: - - namespace App\Controller; - - use App\Controller\AppController; - use Cake\Event\EventInterface; - - class WidgetController extends AppController - { - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('FormProtection'); - } - - public function beforeFilter(EventInterface $event) - { - parent::beforeFilter($event); - - $this->FormProtection->setConfig('unlockedActions', ['edit']); - } - } - -This example would disable all security checks for the edit action. - -Handling validation failure through callbacks -============================================= - -If form protection validation fails it will result in a 400 error by default. -You can configure this behavior by setting the ``validationFailureCallback`` -configuration option to a callback function in the controller. - -By configuring a callback method you can customize how the failure handling process -works:: - - use Cake\Controller\Exception\FormProtectionException; - - public function beforeFilter(EventInterface $event) - { - parent::beforeFilter($event); - - $this->FormProtection->setConfig( - 'validationFailureCallback', - // Prior to 5.2 use Cake\Http\Exception\BadRequestException. - function (FormProtectionException $exception) { - // You can either return a response instance or throw the exception - // received as argument. - } - ); - } - -.. meta:: - :title lang=en: FormProtection - :keywords lang=en: configurable parameters,form protection component,configuration parameters,protection features,tighter security,php class,meth,array,submission,security class,disable security,unlockActions diff --git a/en/controllers/middleware.rst b/en/controllers/middleware.rst deleted file mode 100644 index 9a3a1365f5..0000000000 --- a/en/controllers/middleware.rst +++ /dev/null @@ -1,304 +0,0 @@ -Middleware -########## - -Middleware objects give you the ability to 'wrap' your application in re-usable, -composable layers of Request handling, or response building logic. Visually, -your application ends up at the center, and middleware is wrapped around the app -like an onion. Here we can see an application wrapped with Routes, Assets, -Exception Handling and CORS header middleware. - -.. image:: /_static/img/middleware-setup.png - -When a request is handled by your application it enters from the outermost -middleware. Each middleware can either delegate the request/response to the next -layer, or return a response. Returning a response prevents lower layers from -ever seeing the request. An example of that is the AssetMiddleware handling -a request for a plugin image during development. - -.. image:: /_static/img/middleware-request.png - -If no middleware take action to handle the request, a controller will be located -and have its action invoked, or an exception will be raised generating an error -page. - -Middleware are part of the new HTTP stack in CakePHP that leverages the PSR-7 -request and response interfaces. CakePHP also supports the PSR-15 standard for -server request handlers so you can use any PSR-15 compatible middleware available -on `The Packagist `__. - -Middleware in CakePHP -===================== - -CakePHP provides several middleware to handle common tasks in web applications: - -* ``Cake\Error\Middleware\ErrorHandlerMiddleware`` traps exceptions from the - wrapped middleware and renders an error page using the - :doc:`/development/errors` Exception handler. -* ``Cake\Routing\AssetMiddleware`` checks whether the request is referring to a - theme or plugin asset file, such as a CSS, JavaScript or image file stored in - either a plugin's webroot folder or the corresponding one for a Theme. -* ``Cake\Routing\Middleware\RoutingMiddleware`` uses the ``Router`` to parse the - incoming URL and assign routing parameters to the request. -* ``Cake\I18n\Middleware\LocaleSelectorMiddleware`` enables automatic language - switching from the ``Accept-Language`` header sent by the browser. -* ``Cake\Http\Middleware\EncryptedCookieMiddleware`` gives you the ability to - manipulate encrypted cookies in case you need to manipulate cookie with - obfuscated data. -* ``Cake\Http\Middleware\BodyParserMiddleware`` allows you to decode JSON, XML - and other encoded request bodies based on ``Content-Type`` header. -* :doc:`Cake\Http\Middleware\HttpsEnforcerMiddleware ` - requires HTTPS to be used. -* :doc:`Cake\Http\Middleware\CsrfProtectionMiddleware ` adds - double-submit cookie based CSRF protection to your application. -* :doc:`Cake\Http\Middleware\SessionCsrfProtectionMiddleware ` - adds session based CSRF protection to your application. -* :doc:`Cake\Http\Middleware\CspMiddleware ` - makes it simpler to add Content-Security-Policy headers to your application. -* :doc:`Cake\Http\Middleware\SecurityHeadersMiddleware ` - makes it possible to add security related headers like ``X-Frame-Options`` to - responses. - -.. _using-middleware: - -Using Middleware -================ - -Middleware can be applied to your application globally, to individual -routing scopes, or to specific controllers. - -To apply middleware to all requests, use the ``middleware`` method of your -``App\Application`` class. Your application's ``middleware`` hook method will be -called at the beginning of the request process, you can use the -``MiddlewareQueue`` object to attach middleware:: - - namespace App; - - use Cake\Http\BaseApplication; - use Cake\Http\MiddlewareQueue; - use Cake\Error\Middleware\ErrorHandlerMiddleware; - - class Application extends BaseApplication - { - public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue - { - // Bind the error handler into the middleware queue. - $middlewareQueue->add(new ErrorHandlerMiddleware()); - - // Add middleware by classname. - // As of 4.5.0 classname middleware are optionally resolved - // using the DI container. If the class is not found in the - // container then an instance is created by the middleware queue. - $middlewareQueue->add(UserRateLimiting::class); - - return $middlewareQueue; - } - } - -In addition to adding to the end of the ``MiddlewareQueue`` you can do -a variety of operations:: - - $layer = new \App\Middleware\CustomMiddleware; - - // Added middleware will be last in line. - $middlewareQueue->add($layer); - - // Prepended middleware will be first in line. - $middlewareQueue->prepend($layer); - - // Insert in a specific slot. If the slot is out of - // bounds, it will be added to the end. - $middlewareQueue->insertAt(2, $layer); - - // Insert before another middleware. - // If the named class cannot be found, - // an exception will be raised. - $middlewareQueue->insertBefore( - 'Cake\Error\Middleware\ErrorHandlerMiddleware', - $layer - ); - - // Insert after another middleware. - // If the named class cannot be found, the - // middleware will added to the end. - $middlewareQueue->insertAfter( - 'Cake\Error\Middleware\ErrorHandlerMiddleware', - $layer - ); - - -If your middleware is only applicable to a subset of routes or individual -controllers you can use :ref:`Route scoped middleware `, -or :ref:`Controller middleware `. - -Adding Middleware from Plugins ------------------------------- - -Plugins can use their ``middleware`` hook method to apply any middleware they -have to the application's middleware queue:: - - // in plugins/ContactManager/src/Plugin.php - namespace ContactManager; - - use Cake\Core\BasePlugin; - use Cake\Http\MiddlewareQueue; - use ContactManager\Middleware\ContactManagerContextMiddleware; - - class Plugin extends BasePlugin - { - public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue - { - $middlewareQueue->add(new ContactManagerContextMiddleware()); - - return $middlewareQueue; - } - } - -Creating Middleware -=================== - -Middleware can either be implemented as anonymous functions (Closures), or classes -which extend ``Psr\Http\Server\MiddlewareInterface``. While Closures are suitable -for smaller tasks they make testing harder, and can create a complicated -``Application`` class. Middleware classes in CakePHP have a few conventions: - -* Middleware class files should be put in **src/Middleware**. For example: - **src/Middleware/CorsMiddleware.php** -* Middleware classes should be suffixed with ``Middleware``. For example: - ``LinkMiddleware``. -* Middleware must implement ``Psr\Http\Server\MiddlewareInterface``. - -Middleware can return a response either by calling ``$handler->handle()`` or by -creating their own response. We can see both options in our simple middleware:: - - // In src/Middleware/TrackingCookieMiddleware.php - namespace App\Middleware; - - use Cake\Http\Cookie\Cookie; - use Cake\I18n\Time; - use Psr\Http\Message\ResponseInterface; - use Psr\Http\Message\ServerRequestInterface; - use Psr\Http\Server\RequestHandlerInterface; - use Psr\Http\Server\MiddlewareInterface; - - class TrackingCookieMiddleware implements MiddlewareInterface - { - public function process( - ServerRequestInterface $request, - RequestHandlerInterface $handler - ): ResponseInterface - { - // Calling $handler->handle() delegates control to the *next* middleware - // In your application's queue. - $response = $handler->handle($request); - - if (!$request->getCookie('landing_page')) { - $expiry = new Time('+ 1 year'); - $response = $response->withCookie(new Cookie( - 'landing_page', - $request->getRequestTarget(), - $expiry - )); - } - - return $response; - } - } - -Now that we've made a very simple middleware, let's attach it to our -application:: - - // In src/Application.php - namespace App; - - use App\Middleware\TrackingCookieMiddleware; - use Cake\Http\MiddlewareQueue; - - class Application - { - public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue - { - // Add your simple middleware onto the queue - $middlewareQueue->add(new TrackingCookieMiddleware()); - - // Add some more middleware onto the queue - - return $middlewareQueue; - } - } - - -.. _routing-middleware: - -Routing Middleware -================== - -Routing middleware is responsible for applying your application's routes and -resolving the plugin, controller, and action a request is going to:: - - // In Application.php - public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue - { - // ... - $middlewareQueue->add(new RoutingMiddleware($this)); - } - -.. _encrypted-cookie-middleware: - -Encrypted Cookie Middleware -=========================== - -If your application has cookies that contain data you want to obfuscate and -protect against user tampering, you can use CakePHP's encrypted cookie -middleware to transparently encrypt and decrypt cookie data via middleware. -Cookie data is encrypted with via OpenSSL using AES:: - - use Cake\Http\Middleware\EncryptedCookieMiddleware; - - $cookies = new EncryptedCookieMiddleware( - // Names of cookies to protect - ['secrets', 'protected'], - Configure::read('Security.cookieKey') - ); - - $middlewareQueue->add($cookies); - -.. note:: - It is recommended that the encryption key you use for cookie data, is used - *exclusively* for cookie data. - -The encryption algorithms and padding style used by the cookie middleware are -backwards compatible with ``CookieComponent`` from earlier versions of CakePHP. - -.. _body-parser-middleware: - -Body Parser Middleware -====================== - -If your application accepts JSON, XML or other encoded request bodies, the -``BodyParserMiddleware`` will let you decode those requests into an array that -is available via ``$request->getParsedData()`` and ``$request->getData()``. By -default only ``json`` bodies will be parsed, but XML parsing can be enabled with -an option. You can also define your own parsers:: - - use Cake\Http\Middleware\BodyParserMiddleware; - - // only JSON will be parsed. - $bodies = new BodyParserMiddleware(); - - // Enable XML parsing - $bodies = new BodyParserMiddleware(['xml' => true]); - - // Disable JSON parsing - $bodies = new BodyParserMiddleware(['json' => false]); - - // Add your own parser matching content-type header values - // to the callable that can parse them. - $bodies = new BodyParserMiddleware(); - $bodies->addParser(['text/csv'], function ($body, $request) { - // Use a CSV parsing library. - return Csv::parse($body); - }); - -.. meta:: - :title lang=en: Http Middleware - :keywords lang=en: http, middleware, psr-7, request, response, wsgi, application, baseapplication, https diff --git a/en/controllers/pages-controller.rst b/en/controllers/pages-controller.rst deleted file mode 100644 index 61a07e25d2..0000000000 --- a/en/controllers/pages-controller.rst +++ /dev/null @@ -1,17 +0,0 @@ -The Pages Controller -#################### - -CakePHP's official skeleton app ships with a default controller **PagesController.php**. -This is a simple and optional controller for serving up static content. The home page -you see after installation is generated using this controller and the view -file **templates/Pages/home.php**. If you make the view file -**templates/Pages/about_us.php** you can access it using the URL -**http://example.com/pages/about_us**. You are free to modify the Pages -Controller to meet your needs. - -When you "bake" an app using Composer the Pages Controller is created in your -**src/Controller/** folder. - -.. meta:: - :title lang=en: The Pages Controller - :keywords lang=en: pages controller,default controller,cakephp,ships,php,file folder,home page diff --git a/en/controllers/pagination.rst b/en/controllers/pagination.rst deleted file mode 100644 index 474674a046..0000000000 --- a/en/controllers/pagination.rst +++ /dev/null @@ -1,296 +0,0 @@ -Pagination -########## - -One of the main obstacles of creating flexible and user-friendly web -applications is designing an intuitive user interface. Many applications tend to -grow in size and complexity quickly, and designers and programmers alike find -they are unable to cope with displaying hundreds or thousands of records. -Refactoring takes time, and performance and user satisfaction can suffer. - -Displaying a reasonable number of records per page has always been a critical -part of every application and used to cause many headaches for developers. -CakePHP eases the burden on the developer by providing a terse way to -paginate data. - -Pagination in CakePHP controllers is done through the ``paginate()`` method. You -then use :php:class:`~Cake\\View\\Helper\\PaginatorHelper` in your view templates -to generate pagination controls. - -Basic Usage -=========== - -You can call ``paginate()`` using an ORM table instance or ``Query`` object:: - - public function index() - { - // Paginate the ORM table. - $this->set('articles', $this->paginate($this->Articles)); - - // Paginate a select query - $query = $this->Articles->find('published')->contain('Comments'); - $this->set('articles', $this->paginate($query)); - } - -Advanced Usage -============== - -More complex use cases are supported by configuring the ``$paginate`` -controller property or as the ``$settings`` argument to ``paginate()``. These -conditions serve as the basis for you pagination queries. They are augmented -by the ``sort``, ``direction``, ``limit``, and ``page`` parameters passed in -from the URL:: - - class ArticlesController extends AppController - { - protected array $paginate = [ - 'limit' => 25, - 'order' => [ - 'Articles.title' => 'asc', - ], - ]; - } - -.. tip:: - Default ``order`` options must be defined as an array. - -You can also use :ref:`custom-find-methods` in pagination by using the ``finder`` option:: - - class ArticlesController extends AppController - { - protected array $paginate = [ - 'finder' => 'published', - ]; - } - -Note: This only works with Table as string input in ``$this->paginate('MyTable')``. Once you use ``$this->MyTable->find()`` as input for ``paginate()``, you must directly use that Query object instead. - -If your finder method requires additional options you can pass those -as values for the finder:: - - class ArticlesController extends AppController - { - // find articles by tag - public function tags() - { - $tags = $this->request->getParam('pass'); - - $customFinderOptions = [ - 'tags' => $tags - ]; - // We're using the $settings argument to paginate() here. - // But the same structure could be used in $this->paginate - // - // Our custom finder is called findTagged inside ArticlesTable.php - // which is why we're using `tagged` as the key. - // Our finder should look like: - // public function findTagged(Query $query, array $tagged = []) - $settings = [ - 'finder' => [ - 'tagged' => $customFinderOptions - ] - ]; - $articles = $this->paginate($this->Articles, $settings); - $this->set(compact('articles', 'tags')); - } - } - -In addition to defining general pagination values, you can define more than one -set of pagination defaults in the controller. The name of each model can be used -as a key in the ``$paginate`` property:: - - class ArticlesController extends AppController - { - protected array $paginate = [ - 'Articles' => [], - 'Authors' => [], - ]; - } - -The values of the ``Articles`` and ``Authors`` keys could contain all the keys -that a basic ``$paginate`` array would. - -``Controller::paginate()`` returns an instance of ``Cake\Datasource\Paging\PaginatedResultSet`` -which implements the ``Cake\Datasource\Paging\PaginatedInterface``. - -This object contains the paginated records and the paging params. - -Simple Pagination -================= - -By default ``Controller::paginate()`` uses the ``Cake\Datasource\Paging\NumericPaginator`` -class which does a ``COUNT()`` query to calculate the size of the result set so -that page number links can be rendered. On very large datasets this count query -can be very expensive. In situations where you only want to show 'Next' and 'Previous' -links you can use the 'simple' paginator which does not do a count query:: - - class ArticlesController extends AppController - { - protected array $paginate = [ - 'className' => 'Simple', // Or use Cake\Datasource\Paging\SimplePaginator::class FQCN - ]; - } - -When using the ``SimplePaginator`` you will not be able to generate page -numbers, counter data, links to the last page, or total record count controls. - -.. _paginating-multiple-queries: - -Paginating Multiple Queries -=========================== - -You can paginate multiple models in a single controller action, using the -``scope`` option both in the controller's ``$paginate`` property and in the -call to the ``paginate()`` method:: - - // Paginate property - protected array $paginate = [ - 'Articles' => ['scope' => 'article'], - 'Tags' => ['scope' => 'tag'] - ]; - - // In a controller action - $articles = $this->paginate($this->Articles, ['scope' => 'article']); - $tags = $this->paginate($this->Tags, ['scope' => 'tag']); - $this->set(compact('articles', 'tags')); - -The ``scope`` option will result in the paginator looking in -scoped query string parameters. For example, the following URL could be used to -paginate both tags and articles at the same time:: - - /dashboard?article[page]=1&tag[page]=3 - -See the :ref:`paginator-helper-multiple` section for how to generate scoped HTML -elements and URLs for pagination. - -Paginating the Same Model multiple Times ----------------------------------------- - -To paginate the same model multiple times within a single controller action you -need to define an alias for the model.:: - - // In a controller action - $this->paginate = [ - 'Articles' => [ - 'scope' => 'published_articles', - 'limit' => 10, - 'order' => [ - 'id' => 'desc', - ], - ], - 'UnpublishedArticles' => [ - 'scope' => 'unpublished_articles', - 'limit' => 10, - 'order' => [ - 'id' => 'desc', - ], - ], - ]; - - $publishedArticles = $this->paginate( - $this->Articles->find('all', scope: 'published_articles') - ->where(['published' => true]) - ); - - // Load an additional table object to allow differentiating in the paginator - $unpublishedArticlesTable = $this->fetchTable('UnpublishedArticles', [ - 'className' => 'App\Model\Table\ArticlesTable', - 'table' => 'articles', - 'entityClass' => 'App\Model\Entity\Article', - ]); - - $unpublishedArticles = $this->paginate( - $unpublishedArticlesTable->find('all', scope: 'unpublished_articles') - ->where(['published' => false]) - ); - -.. _control-which-fields-used-for-ordering: - -Control which Fields Used for Ordering -====================================== - -By default sorting can be done on any non-virtual column a table has. This is -sometimes undesirable as it allows users to sort on un-indexed columns that can -be expensive to order by. You can set the allowed list of fields that can be sorted -using the ``sortableFields`` option. This option is required when you want to -sort on any associated data, or computed fields that may be part of your -pagination query:: - - protected array $paginate = [ - 'sortableFields' => [ - 'id', 'title', 'Users.username', 'created', - ], - ]; - -Any requests that attempt to sort on fields not in the allowed list will be -ignored. - -Limit the Maximum Number of Rows per Page -========================================= - -The number of results that are fetched per page is exposed to the user as the -``limit`` parameter. It is generally undesirable to allow users to fetch all -rows in a paginated set. The ``maxLimit`` option asserts that no one can set -this limit too high from the outside. By default CakePHP limits the maximum -number of rows that can be fetched to 100. If this default is not appropriate -for your application, you can adjust it as part of the pagination options, for -example reducing it to ``10``:: - - protected array $paginate = [ - // Other keys here. - 'maxLimit' => 10 - ]; - -If the request's limit param is greater than this value, it will be reduced to -the ``maxLimit`` value. - -Out of Range Page Requests -========================== - -``Controller::paginate()`` will throw a ``NotFoundException`` when trying to -access a non-existent page, i.e. page number requested is greater than total -page count. - -So you could either let the normal error page be rendered or use a try catch -block and take appropriate action when a ``NotFoundException`` is caught:: - - use Cake\Http\Exception\NotFoundException; - - public function index() - { - try { - $this->paginate(); - } catch (NotFoundException $e) { - // Do something here like redirecting to first or last page. - // $e->getPrevious()->getAttributes('pagingParams') will give you required info. - } - } - -Using a paginator class directly -================================ - -You can also use a paginator directly.:: - - // Create a paginator - $paginator = new \Cake\Datasource\Paginator\NumericPaginator(); - - // Paginate the model - $results = $paginator->paginate( - // Query or table instance which you need to paginate - $this->fetchTable('Articles'), - // Request params - $this->request->getQueryParams(), - // Config array having the same structure as options as Controller::$paginate - [ - 'finder' => 'latest', - ] - ); - -Pagination in the View -====================== - -Check the :php:class:`~Cake\\View\\Helper\\PaginatorHelper` documentation for -how to create links for pagination navigation. - -.. meta:: - :title lang=en: Pagination - :keywords lang=en: paginate,pagination,paging diff --git a/en/controllers/request-response.rst b/en/controllers/request-response.rst deleted file mode 100644 index e7d0eeb37d..0000000000 --- a/en/controllers/request-response.rst +++ /dev/null @@ -1,1248 +0,0 @@ -Request & Response Objects -########################## - -.. php:namespace:: Cake\Http - -The request and response objects provide an abstraction around HTTP requests and -responses. The request object in CakePHP allows you to introspect an incoming -request, while the response object allows you to effortlessly create HTTP -responses from your controllers. - -.. index:: $this->request -.. _cake-request: - -Request -======= - -.. php:class:: ServerRequest - -``ServerRequest`` is the default request object used in CakePHP. It centralizes a -number of features for interrogating and interacting with request data. -On each request one Request is created and then passed by reference to the -various layers of an application that use request data. By default the request -is assigned to ``$this->request``, and is available in Controllers, Cells, Views -and Helpers. You can also access it in Components using the controller -reference. - -.. versionchanged:: 4.4.0 - The ``ServerRequest`` is available via DI. - So you can get it from container or use it as a dependency for your service. - -Some of the duties ``ServerRequest`` performs include: - -* Processing the GET, POST, and FILES arrays into the data structures you are - familiar with. -* Providing environment introspection pertaining to the request. Information - like the headers sent, the client's IP address, and the subdomain/domain - names the server your application is running on. -* Providing access to request parameters both as array indexes and object - properties. - -CakePHP's request object implements the `PSR-7 -ServerRequestInterface `_ making it easier to -use libraries from outside of CakePHP. - -.. _request-parameters: - -Request Parameters ------------------- - -The request exposes routing parameters through the ``getParam()`` method:: - - $controllerName = $this->request->getParam('controller'); - -To get all routing parameters as an array use ``getAttribute()``:: - - $parameters = $this->request->getAttribute('params'); - -All :ref:`route-elements` are accessed through this interface. - -In addition to :ref:`route-elements`, you also often need access to -:ref:`passed-arguments`. These are both available on the request object as -well:: - - // Passed arguments - $passedArgs = $this->request->getParam('pass'); - -Will all provide you access to the passed arguments. There -are several important/useful parameters that CakePHP uses internally, these -are also all found in the routing parameters: - -* ``plugin`` The plugin handling the request. Will be null when there is no - plugin. -* ``controller`` The controller handling the current request. -* ``action`` The action handling the current request. -* ``prefix`` The prefix for the current action. See :ref:`prefix-routing` for - more information. - -Query String Parameters ------------------------ - -.. php:method:: getQuery($name, $default = null) - -Query string parameters can be read using the ``getQuery()`` method:: - - // URL is /posts/index?page=1&sort=title - $page = $this->request->getQuery('page'); - -You can either directly access the query property, or you can use -``getQuery()`` method to read the URL query array in an error-free manner. -Any keys that do not exist will return ``null``:: - - $foo = $this->request->getQuery('value_that_does_not_exist'); - // $foo === null - - // You can also provide default values - $foo = $this->request->getQuery('does_not_exist', 'default val'); - -If you want to access all the query parameters you can use -``getQueryParams()``:: - - $query = $this->request->getQueryParams(); - -You can use the casting utility functions to provide typesafe access to request -data and other input:: - - use function Cake\Core\toBool; - use function Cake\Core\toInt; - use function Cake\Core\toString; - use function Cake\I18n\toDate; - use function Cake\I18n\toDateTime; - - // $active is bool|null. - $active = toBool($this->request->getQuery('active')); - - // $page is int|null. - $page = toInt($this->request->getQuery('page')); - - // $query is string|null. - $query = toString($this->request->getQuery('query')); - - // Parse a date based on the format or null - $date = toDate($this->request->getQuery('date'), 'Y-m-d'); - - // Parse a datetime based on a format or null - $date = toDateTime($this->request->getQuery('datetime'), 'Y-m-d H:i:s'); - -.. versionadded:: 5.1.0 - Casting functions were added. - -Request Body Data ------------------ - -.. php:method:: getData($name, $default = null) - -All POST data normally available through PHP's ``$_POST`` global variable can be -accessed using :php:meth:`Cake\\Http\\ServerRequest::getData()`. For example:: - - // An input with a name attribute equal to 'title' is accessible at - $title = $this->request->getData('title'); - -You can use a dot separated names to access nested data. For example:: - - $value = $this->request->getData('address.street_name'); - -For non-existent names the ``$default`` value will be returned:: - - $foo = $this->request->getData('value.that.does.not.exist'); - // $foo == null - -You can also use :ref:`body-parser-middleware` to parse request body of different -content types into an array, so that it's accessible through ``ServerRequest::getData()``. - -If you want to access all the data parameters you can use -``getParsedBody()``:: - - $data = $this->request->getParsedBody(); - -.. _request-file-uploads: - -File Uploads ------------- - -Uploaded files can be accessed through the request body data, using the :php:meth:`Cake\\Http\\ServerRequest::getData()` -method described above. For example, a file from an input element with a name attribute of ``attachment``, can -be accessed like this:: - - $attachment = $this->request->getData('attachment'); - -By default file uploads are represented in the request data as objects that implement -`\\Psr\\Http\\Message\\UploadedFileInterface `__. In the current -implementation, the ``$attachment`` variable in the above example would by default hold an instance of -``\Laminas\Diactoros\UploadedFile``. - -Accessing the uploaded file details is fairly simple, here's how you can obtain the same data as provided by the old -style file upload array:: - - $name = $attachment->getClientFilename(); - $type = $attachment->getClientMediaType(); - $size = $attachment->getSize(); - $tmpName = $attachment->getStream()->getMetadata('uri'); - $error = $attachment->getError(); - -Moving the uploaded file from its temporary location to the desired target -location, doesn't require manually accessing the temporary file, instead it can -be easily done by using the objects ``moveTo()`` method:: - - $attachment->moveTo($targetPath); - -In an HTTP environment, the ``moveTo()`` method will automatically validate -whether the file is an actual uploaded file, and throw an exception in case -necessary. In an CLI environment, where the concept of uploading files doesn't -exist, it will allow to move the file that you've referenced irrespective of its -origins, which makes testing file uploads possible. - -.. php:method:: getUploadedFile($path) - -Returns the uploaded file at a specific path. The path uses the same dot syntax as the -:php:meth:`Cake\\Http\\ServerRequest::getData()` method:: - - $attachment = $this->request->getUploadedFile('attachment'); - -Unlike :php:meth:`Cake\\Http\\ServerRequest::getData()`, :php:meth:`Cake\\Http\\ServerRequest::getUploadedFile()` would -only return data when an actual file upload exists for the given path, if there is regular, non-file request body data -present at the given path, then this method will return ``null``, just like it would for any non-existent path. - -.. php:method:: getUploadedFiles() - -Returns all uploaded files in a normalized array structure. For the above example with the file input name of -``attachment``, the structure would look like:: - - [ - 'attachment' => object(Laminas\Diactoros\UploadedFile) { - // ... - } - ] - -.. php:method:: withUploadedFiles(array $files) - -This method sets the uploaded files of the request object, it accepts an array of objects that implement -`\\Psr\\Http\\Message\\UploadedFileInterface `__. It will -replace all possibly existing uploaded files:: - - $files = [ - 'MyModel' => [ - 'attachment' => new \Laminas\Diactoros\UploadedFile( - $streamOrFile, - $size, - $errorStatus, - $clientFilename, - $clientMediaType - ), - 'anotherAttachment' => new \Laminas\Diactoros\UploadedFile( - '/tmp/hfz6dbn.tmp', - 123, - \UPLOAD_ERR_OK, - 'attachment.txt', - 'text/plain' - ), - ], - ]; - - $this->request = $this->request->withUploadedFiles($files); - -.. note:: - - Uploaded files that have been added to the request via this method, will *not* be available in the request body - data, ie you cannot retrieve them via :php:meth:`Cake\\Http\\ServerRequest::getData()`! If you need them in the - request data (too), then you have to set them via :php:meth:`Cake\\Http\\ServerRequest::withData()` or - :php:meth:`Cake\\Http\\ServerRequest::withParsedBody()`. - -PUT, PATCH or DELETE Data -------------------------- - -.. php:method:: getBody() - -When building REST services, you often accept request data on ``PUT`` and -``DELETE`` requests. Any ``application/x-www-form-urlencoded`` request body data -will automatically be parsed and available via ``$request->getData()`` for ``PUT`` and -``DELETE`` requests. If you are accepting JSON or XML data, you can -access the raw data with ``getBody()``:: - - // Get the stream wrapper on the request body - $body = $request->getBody(); - - // Get the request body as a string - $bodyString = (string)$request->getBody(); - -If your requests contain XML or JSON request content, you should consider using -:ref:`body-parser-middleware` to have CakePHP automatically parse those content -types making the parsed data available in ``$request->getData()`` and -``$request->getParsedBody()``. - -Environment Variables (from $_SERVER and $_ENV) ------------------------------------------------ - -.. php:method:: getEnv($key, $default = null) - -``ServerRequest::getEnv()`` is a wrapper for ``getenv()`` global function and acts as -a getter for environment variables without possible undefined keys:: - - $host = $this->request->getEnv('HTTP_HOST'); - -To access all the environment variables in a request use ``getServerParams()``:: - - $env = $this->request->getServerParams(); - -.. php:method:: withEnv($key, $value) - -``ServerRequest::withEnv()`` is a wrapper for ``putenv()`` global function and acts as -a setter for environment variables without having to modify globals -``$_SERVER`` and ``$_ENV``:: - - // Set a value, generally helpful in testing. - $this->request->withEnv('REQUEST_METHOD', 'POST'); - -XML or JSON Data ----------------- - -Applications employing :doc:`/development/rest` often exchange data in -non-URL-encoded post bodies. You can read input data in any format using -:php:meth:`~Cake\\Http\\ServerRequest::input()`. By providing a decoding function, -you can receive the content in a deserialized format:: - - // Get JSON encoded data submitted to a PUT/POST action - $jsonData = $this->request->input('json_decode'); - -Some deserializing methods require additional parameters when called, such as -the 'as array' parameter on ``json_decode``. If you want XML converted into a -DOMDocument object, :php:meth:`~Cake\\Http\\ServerRequest::input()` supports -passing in additional parameters as well:: - - // Get XML encoded data submitted to a PUT/POST action - $data = $this->request->input('Cake\Utility\Xml::build', ['return' => 'domdocument']); - -Path Information ----------------- - -The request object also provides useful information about the paths in your -application. The ``base`` and ``webroot`` attributes are useful for -generating URLs, and determining whether or not your application is in a -subdirectory. The attributes you can use are:: - - // Assume the current request URL is /subdir/articles/edit/1?page=1 - - // Holds /subdir/articles/edit/1?page=1 - $here = $request->getRequestTarget(); - - // Holds /subdir - $base = $request->getAttribute('base'); - - // Holds /subdir/ - $base = $request->getAttribute('webroot'); - -.. _check-the-request: - -Checking Request Conditions ---------------------------- - -.. php:method:: is($type, $args...) - -The request object provides a way to inspect certain conditions in a given -request. By using the ``is()`` method you can check a number of common -conditions, as well as inspect other application specific request criteria:: - - $isPost = $this->request->is('post'); - -You can also extend the request detectors that are available, by using -:php:meth:`Cake\\Http\\ServerRequest::addDetector()` to create new kinds of -detectors. There are different types of detectors that you can create: - -* Environment value comparison - Compares a value fetched from :php:func:`env()` - for equality with the provided value. -* Header value comparison - If the specified header exists with the specified - value, or if the callable returns true. -* Pattern value comparison - Pattern value comparison allows you to compare a - value fetched from :php:func:`env()` to a regular expression. -* Option based comparison - Option based comparisons use a list of options to - create a regular expression. Subsequent calls to add an already defined - options detector will merge the options. -* Callback detectors - Callback detectors allow you to provide a 'callback' type - to handle the check. The callback will receive the request object as its only - parameter. - -.. php:method:: addDetector($name, $options) - -Some examples would be:: - - // Add an environment detector. - $this->request->addDetector( - 'post', - ['env' => 'REQUEST_METHOD', 'value' => 'POST'] - ); - - // Add a pattern value detector. - $this->request->addDetector( - 'iphone', - ['env' => 'HTTP_USER_AGENT', 'pattern' => '/iPhone/i'] - ); - - // Add an option detector - $this->request->addDetector('internalIp', [ - 'env' => 'CLIENT_IP', - 'options' => ['192.168.0.101', '192.168.0.100'] - ]); - - - // Add a header detector with value comparison - $this->request->addDetector('fancy', [ - 'env' => 'CLIENT_IP', - 'header' => ['X-Fancy' => 1] - ]); - - // Add a header detector with callable comparison - $this->request->addDetector('fancy', [ - 'env' => 'CLIENT_IP', - 'header' => ['X-Fancy' => function ($value, $header) { - return in_array($value, ['1', '0', 'yes', 'no'], true); - }] - ]); - - // Add a callback detector. Must be a valid callable. - $this->request->addDetector( - 'awesome', - function ($request) { - return $request->getParam('awesome'); - } - ); - - // Add a detector that uses additional arguments. - $this->request->addDetector( - 'csv', - [ - 'accept' => ['text/csv'], - 'param' => '_ext', - 'value' => 'csv', - ] - ); - -There are several built-in detectors that you can use: - -* ``is('get')`` Check to see whether the current request is a GET. -* ``is('put')`` Check to see whether the current request is a PUT. -* ``is('patch')`` Check to see whether the current request is a PATCH. -* ``is('post')`` Check to see whether the current request is a POST. -* ``is('delete')`` Check to see whether the current request is a DELETE. -* ``is('head')`` Check to see whether the current request is HEAD. -* ``is('options')`` Check to see whether the current request is OPTIONS. -* ``is('ajax')`` Check to see whether the current request came with - X-Requested-With = XMLHttpRequest. -* ``is('ssl')`` Check to see whether the request is via SSL. -* ``is('flash')`` Check to see whether the request has a User-Agent of Flash. -* ``is('json')`` Check to see whether the request URL has 'json' extension or the - `Accept` header is set to 'application/json'. -* ``is('xml')`` Check to see whether the request URL has 'xml' extension or the `Accept` header is set to - 'application/xml' or 'text/xml'. - -``ServerRequest`` also includes methods like -:php:meth:`Cake\\Http\\ServerRequest::domain()`, -:php:meth:`Cake\\Http\\ServerRequest::subdomains()` and -:php:meth:`Cake\\Http\\ServerRequest::host()` to make applications that use -subdomains simpler. - -Session Data ------------- - -To access the session for a given request use the ``getSession()`` method or use the ``session`` attribute:: - - $session = $this->request->getSession(); - $session = $this->request->getAttribute('session'); - - $data = $session->read('sessionKey'); - -For more information, see the :doc:`/development/sessions` documentation for how -to use the session object. - -Host and Domain Name --------------------- - -.. php:method:: domain($tldLength = 1) - -Returns the domain name your application is running on:: - - // Prints 'example.org' - echo $request->domain(); - -.. php:method:: subdomains($tldLength = 1) - -Returns the subdomains your application is running on as an array:: - - // Returns ['my', 'dev'] for 'my.dev.example.org' - $subdomains = $request->subdomains(); - -.. php:method:: host() - -Returns the host your application is on:: - - // Prints 'my.dev.example.org' - echo $request->host(); - -Reading the HTTP Method ------------------------ - -.. php:method:: getMethod() - -Returns the HTTP method the request was made with:: - - // Output POST - echo $request->getMethod(); - -Restricting Which HTTP method an Action Accepts ------------------------------------------------ - -.. php:method:: allowMethod($methods) - -Set allowed HTTP methods. If not matched, will throw -``MethodNotAllowedException``. The 405 response will include the required -``Allow`` header with the passed methods:: - - public function delete() - { - // Only accept POST and DELETE requests - $this->request->allowMethod(['post', 'delete']); - ... - } - -Reading HTTP Headers --------------------- - -Allows you to access any of the ``HTTP_*`` headers that were used -for the request. For example:: - - // Get the header as a string - $userAgent = $this->request->getHeaderLine('User-Agent'); - - // Get an array of all values. - $acceptHeader = $this->request->getHeader('Accept'); - - // Check if a header exists - $hasAcceptHeader = $this->request->hasHeader('Accept'); - -While some apache installs don't make the ``Authorization`` header accessible, -CakePHP will make it available through apache specific methods as required. - -.. php:method:: referer($local = true) - -Returns the referring address for the request. - -.. php:method:: clientIp() - -Returns the current visitor's IP address. - -Trusting Proxy Headers ----------------------- - -If your application is behind a load balancer or running on a cloud service, you -will often get the load balancer host, port and scheme in your requests. Often -load balancers will also send ``HTTP-X-Forwarded-*`` headers with the original -values. The forwarded headers will not be used by CakePHP out of the box. To -have the request object use these headers set the ``trustProxy`` property to -``true``:: - - $this->request->trustProxy = true; - - // These methods will now use the proxied headers. - $port = $this->request->port(); - $host = $this->request->host(); - $scheme = $this->request->scheme(); - $clientIp = $this->request->clientIp(); - -Once proxies are trusted the ``clientIp()`` method will use the *last* IP -address in the ``X-Forwarded-For`` header. If your application is behind -multiple proxies, you can use ``setTrustedProxies()`` to define the IP addresses -of proxies in your control:: - - $request->setTrustedProxies(['127.1.1.1', '127.8.1.3']); - -After proxies are trusted ``clientIp()`` will use the first IP address in the -``X-Forwarded-For`` header providing it is the only value that isn't from a trusted -proxy. - -Checking Accept Headers ------------------------ - -.. php:method:: accepts($type = null) - -Find out which content types the client accepts, or check whether it accepts a -particular type of content. - -Get all types:: - - $accepts = $this->request->accepts(); - -Check for a single type:: - - $acceptsJson = $this->request->accepts('application/json'); - -.. php:method:: acceptLanguage($language = null) - -Get all the languages accepted by the client, -or check whether a specific language is accepted. - -Get the list of accepted languages:: - - $acceptsLanguages = $this->request->acceptLanguage(); - -Check whether a specific language is accepted:: - - $acceptsSpanish = $this->request->acceptLanguage('es-es'); - -.. _request-cookies: - -Reading Cookies ---------------- - -Request cookies can be read through a number of methods:: - - // Get the cookie value, or null if the cookie is missing. - $rememberMe = $this->request->getCookie('remember_me'); - - // Read the value, or get the default of 0 - $rememberMe = $this->request->getCookie('remember_me', 0); - - // Get all cookies as an hash - $cookies = $this->request->getCookieParams(); - - // Get a CookieCollection instance - $cookies = $this->request->getCookieCollection() - -See the :php:class:`Cake\\Http\\Cookie\\CookieCollection` documentation for how -to work with cookie collection. - - -Uploaded Files --------------- - -Requests expose the uploaded file data in ``getData()`` or -``getUploadedFiles()`` as ``UploadedFileInterface`` objects:: - - // Get a list of UploadedFile objects - $files = $request->getUploadedFiles(); - - // Read the file data. - $files[0]->getStream(); - $files[0]->getSize(); - $files[0]->getClientFileName(); - - // Move the file. - $files[0]->moveTo($targetPath); - -Manipulating URIs ------------------ - -Requests contain a URI object, which contains methods for interacting with the -requested URI:: - - // Get the URI - $uri = $request->getUri(); - - // Read data out of the URI. - $path = $uri->getPath(); - $query = $uri->getQuery(); - $host = $uri->getHost(); - - -.. index:: $this->response - -Response -======== - -.. php:class:: Response - -:php:class:`Cake\\Http\\Response` is the default response class in CakePHP. -It encapsulates a number of features and functionality for generating HTTP -responses in your application. It also assists in testing, as it can be -mocked/stubbed allowing you to inspect headers that will be sent. - -``Response`` provides an interface to wrap the common response-related -tasks such as: - -* Sending headers for redirects. -* Sending content type headers. -* Sending any header. -* Sending the response body. - -Dealing with Content Types --------------------------- - -.. php:method:: withType($contentType = null) - -You can control the Content-Type of your application's responses with -:php:meth:`Cake\\Http\\Response::withType()`. If your application needs to deal -with content types that are not built into Response, you can map them with -``setTypeMap()`` as well:: - - // Add a vCard type - $this->response->setTypeMap('vcf', ['text/v-card']); - - // Set the response Content-Type to vcard. - $this->response = $this->response->withType('vcf'); - -Usually, you'll want to map additional content types in your controller's -:php:meth:`~Controller::beforeFilter()` callback, so you can benefit from -automatic view switching provided by :ref:`controller-viewclasses`. - -.. _cake-response-file: - -Sending Files -------------- - -.. php:method:: withFile(string $path, array $options = []) - -There are times when you want to send files as responses for your requests. -You can accomplish that by using :php:meth:`Cake\\Http\\Response::withFile()`:: - - public function sendFile($id) - { - $file = $this->Attachments->getFile($id); - $response = $this->response->withFile($file['path']); - // Return the response to prevent controller from trying to render - // a view. - return $response; - } - -As shown in the above example, you must pass the file path to the method. -CakePHP will send a proper content type header if it's a known file type listed -in `Cake\\Http\\Response::$_mimeTypes`. You can add new types prior to calling -:php:meth:`Cake\\Http\\Response::withFile()` by using the -:php:meth:`Cake\\Http\\Response::withType()` method. - -If you want, you can also force a file to be downloaded instead of displayed in -the browser by specifying the options:: - - $response = $this->response->withFile( - $file['path'], - ['download' => true, 'name' => 'foo'] - ); - -The supported options are: - -name - The name allows you to specify an alternate file name to be sent to - the user. -download - A boolean value indicating whether headers should be set to force - download. - -Sending a String as File ------------------------- - -You can respond with a file that does not exist on the disk, such as a pdf or an -ics generated on the fly from a string:: - - public function sendIcs() - { - $icsString = $this->Calendars->generateIcs(); - $response = $this->response; - - // Inject string content into response body - $response = $response->withStringBody($icsString); - - $response = $response->withType('ics'); - - // Optionally force file download - $response = $response->withDownload('filename_for_download.ics'); - - // Return response object to prevent controller from trying to render - // a view. - return $response; - } - -Setting Headers ---------------- - -.. php:method:: withHeader($header, $value) - -Setting headers is done with the :php:meth:`Cake\\Http\\Response::withHeader()` -method. Like all of the PSR-7 interface methods, this method returns a *new* -instance with the new header:: - - // Add/replace a header - $response = $response->withHeader('X-Extra', 'My header'); - - // Set multiple headers - $response = $response->withHeader('X-Extra', 'My header') - ->withHeader('Location', 'http://example.com'); - - // Append a value to an existing header - $response = $response->withAddedHeader('Set-Cookie', 'remember_me=1'); - -Headers are not sent when set. Instead, they are held until the response is -emitted by ``Cake\Http\Server``. - -You can now use the convenience method -:php:meth:`Cake\\Http\\Response::withLocation()` to directly set or get the -redirect location header. - -Setting the Body ----------------- - -.. php:method:: withStringBody($string) - -To set a string as the response body, do the following:: - - // Set a string into the body - $response = $response->withStringBody('My Body'); - - // If you want a json response - $response = $response->withType('application/json') - ->withStringBody(json_encode(['Foo' => 'bar'])); - -.. php:method:: withBody($body) - -To set the response body, use the ``withBody()`` method, which is provided by the -:php:class:`Laminas\\Diactoros\\MessageTrait`:: - - $response = $response->withBody($stream); - -Be sure that ``$stream`` is a :php:class:`Psr\\Http\\Message\\StreamInterface` object. -See below on how to create a new stream. - -You can also stream responses from files using :php:class:`Laminas\\Diactoros\\Stream` streams:: - - // To stream from a file - use Laminas\Diactoros\Stream; - - $stream = new Stream('/path/to/file', 'rb'); - $response = $response->withBody($stream); - -You can also stream responses from a callback using the ``CallbackStream``. This -is useful when you have resources like images, CSV files or PDFs you need to -stream to the client:: - - // Streaming from a callback - use Cake\Http\CallbackStream; - - // Create an image. - $img = imagecreate(100, 100); - // ... - - $stream = new CallbackStream(function () use ($img) { - imagepng($img); - }); - $response = $response->withBody($stream); - -Setting the Character Set -------------------------- - -.. php:method:: withCharset($charset) - -Sets the charset that will be used in the response:: - - $this->response = $this->response->withCharset('UTF-8'); - -Interacting with Browser Caching --------------------------------- - -.. php:method:: withDisabledCache() - -You sometimes need to force browsers not to cache the results of a controller -action. :php:meth:`Cake\\Http\\Response::withDisabledCache()` is intended for just -that:: - - public function index() - { - // Disable caching - $this->response = $this->response->withDisabledCache(); - } - -.. warning:: - - Disabling caching from SSL domains while trying to send - files to Internet Explorer can result in errors. - -.. php:method:: withCache($since, $time = '+1 day') - -You can also tell clients that you want them to cache responses. By using -:php:meth:`Cake\\Http\\Response::withCache()`:: - - public function index() - { - // Enable caching - $this->response = $this->response->withCache('-1 minute', '+5 days'); - } - -The above would tell clients to cache the resulting response for 5 days, -hopefully speeding up your visitors' experience. -The ``withCache()`` method sets the ``Last-Modified`` value to the first -argument. ``Expires`` header and the ``max-age`` directive are set based on the -second parameter. Cache-Control's ``public`` directive is set as well. - -.. _cake-response-caching: - -Fine Tuning HTTP Cache ----------------------- - -One of the best and easiest ways of speeding up your application is to use HTTP -cache. Under this caching model, you are only required to help clients decide if -they should use a cached copy of the response by setting a few headers such as -modified time and response entity tag. - -Rather than forcing you to code the logic for caching and for invalidating -(refreshing) it once the data has changed, HTTP uses two models, expiration and -validation, which usually are much simpler to use. - -Apart from using :php:meth:`Cake\\Http\\Response::withCache()`, you can also use -many other methods to fine-tune HTTP cache headers to take advantage of browser -or reverse proxy caching. - -The Cache Control Header -~~~~~~~~~~~~~~~~~~~~~~~~ - -.. php:method:: withSharable($public, $time = null) - -Used under the expiration model, this header contains multiple indicators that -can change the way browsers or proxies use the cached content. A -``Cache-Control`` header can look like this:: - - Cache-Control: private, max-age=3600, must-revalidate - -``Response`` class helps you set this header with some utility methods that will -produce a final valid ``Cache-Control`` header. The first is the -``withSharable()`` method, which indicates whether a response is to be -considered sharable across different users or clients. This method actually -controls the ``public`` or ``private`` part of this header. Setting a response -as private indicates that all or part of it is intended for a single user. To -take advantage of shared caches, the control directive must be set as public. - -The second parameter of this method is used to specify a ``max-age`` for the -cache, which is the number of seconds after which the response is no longer -considered fresh:: - - public function view() - { - // ... - // Set the Cache-Control as public for 3600 seconds - $this->response = $this->response->withSharable(true, 3600); - } - - public function my_data() - { - // ... - // Set the Cache-Control as private for 3600 seconds - $this->response = $this->response->withSharable(false, 3600); - } - -``Response`` exposes separate methods for setting each of the directives in -the ``Cache-Control`` header. - -The Expiration Header -~~~~~~~~~~~~~~~~~~~~~ - -.. php:method:: withExpires($time) - -You can set the ``Expires`` header to a date and time after which the response -is no longer considered fresh. This header can be set using the -``withExpires()`` method:: - - public function view() - { - $this->response = $this->response->withExpires('+5 days'); - } - -This method also accepts a :php:class:`DateTime` instance or any string that can -be parsed by the :php:class:`DateTime` class. - -The Etag Header -~~~~~~~~~~~~~~~ - -.. php:method:: withEtag($tag, $weak = false) - -Cache validation in HTTP is often used when content is constantly changing, and -asks the application to only generate the response contents if the cache is no -longer fresh. Under this model, the client continues to store pages in the -cache, but it asks the application every time -whether the resource has changed, instead of using it directly. -This is commonly used with static resources such as images and other assets. - -The ``withEtag()`` method (called entity tag) is a string -that uniquely identifies the requested resource, as a checksum does for a file, -in order to determine whether it matches a cached resource. - -To take advantage of this header, you must either call the -``isNotModified()`` method manually or include the -:doc:`/controllers/components/check-http-cache` in your controller:: - - public function index() - { - $articles = $this->Articles->find('all')->all(); - - // Simple checksum of the article contents. - // You should use a more efficient implementation - // in a real world application. - $checksum = md5(json_encode($articles)); - - $response = $this->response->withEtag($checksum); - if ($response->isNotModified($this->request)) { - return $response; - } - - $this->response = $response; - // ... - } - -.. note:: - - Most proxy users should probably consider using the Last Modified Header - instead of Etags for performance and compatibility reasons. - -The Last Modified Header -~~~~~~~~~~~~~~~~~~~~~~~~ - -.. php:method:: withModified($time) - -Also, under the HTTP cache validation model, you can set the ``Last-Modified`` -header to indicate the date and time at which the resource was modified for the -last time. Setting this header helps CakePHP tell caching clients whether the -response was modified or not based on their cache. - -To take advantage of this header, you must either call the -``isNotModified()`` method manually or include the -:doc:`/controllers/components/check-http-cache` in your controller:: - - public function view() - { - $article = $this->Articles->find()->first(); - $response = $this->response->withModified($article->modified); - if ($response->isNotModified($this->request)) { - return $response; - } - $this->response; - // ... - } - -The Vary Header -~~~~~~~~~~~~~~~ - -.. php:method:: withVary($header) - -In some cases, you might want to serve different content using the same URL. -This is often the case if you have a multilingual page or respond with different -HTML depending on the browser. Under such circumstances you can use the ``Vary`` -header:: - - $response = $this->response->withVary('User-Agent'); - $response = $this->response->withVary('Accept-Encoding', 'User-Agent'); - $response = $this->response->withVary('Accept-Language'); - -Sending Not-Modified Responses -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. php:method:: isNotModified(Request $request) - -Compares the cache headers for the request object with the cache header from the -response and determines whether it can still be considered fresh. If so, deletes -the response content, and sends the `304 Not Modified` header:: - - // In a controller action. - if ($this->response->isNotModified($this->request)) { - return $this->response; - } - -.. _response-cookies: - -Setting Cookies ---------------- - -Cookies can be added to response using either an array or a :php:class:`Cake\\Http\\Cookie\\Cookie` -object:: - - use Cake\Http\Cookie\Cookie; - use DateTime; - - // Add a cookie - $this->response = $this->response->withCookie(Cookie::create( - 'remember_me', - 'yes', - // All keys are optional - [ - 'expires' => new DateTime('+1 year'), - 'path' => '', - 'domain' => '', - 'secure' => false, - 'httponly' => false, - 'samesite' => null // Or one of CookieInterface::SAMESITE_* constants - ] - )); - -See the :ref:`creating-cookies` section for how to use the cookie object. You -can use ``withExpiredCookie()`` to send an expired cookie in the response. This -will make the browser remove its local cookie:: - - $this->response = $this->response->withExpiredCookie(new Cookie('remember_me')); - -.. _cors-headers: - -Setting Cross Origin Request Headers (CORS) -------------------------------------------- - -The ``cors()`` method is used to define `HTTP Access Control -`__ -related headers with a fluent interface:: - - $this->response = $this->response->cors($this->request) - ->allowOrigin(['*.cakephp.org']) - ->allowMethods(['GET', 'POST']) - ->allowHeaders(['X-CSRF-Token']) - ->allowCredentials() - ->exposeHeaders(['Link']) - ->maxAge(300) - ->build(); - -CORS related headers will only be applied to the response if the following -criteria are met: - -#. The request has an ``Origin`` header. -#. The request's ``Origin`` value matches one of the allowed Origin values. - -.. tip:: - - CakePHP has no built-in CORS middleware because dealing with CORS requests - is very application specific. We recommend you build your own ``CORSMiddleware`` - if you need one and adjust the response object as desired. - -Running logic after the Response has been sent ----------------------------------------------- - -In fastcgi based environments you can listen to the ``Server.terminate`` event -to run logic **after** the response has been sent to the client. The -``terminate`` event will be passed a ``request`` and ``response``. The -``request`` is fetched from the applications' DI container, or from -``Router::getRequest()`` if the DI container does not have a request registered. - -.. warning:: - In non fastcgi environments the ``Server.terminate`` event is fired before - the response is sent. - -.. versionadded:: 5.1.0 - -Common Mistakes with Immutable Responses -======================================== - -Response objects offer a number of methods that treat -responses as immutable objects. Immutable objects help prevent difficult to -track accidental side-effects, and reduce mistakes caused by method calls caused -by refactoring that change ordering. While they offer a number of benefits, -immutable objects can take some getting used to. Any method that starts with -``with`` operates on the response in an immutable fashion, and will **always** -return a **new** instance. Forgetting to retain the modified instance is the most -frequent mistake people make when working with immutable objects:: - - $this->response->withHeader('X-CakePHP', 'yes!'); - -In the above code, the response will be lacking the ``X-CakePHP`` header, as the -return value of the ``withHeader()`` method was not retained. To correct the -above code you would write:: - - $this->response = $this->response->withHeader('X-CakePHP', 'yes!'); - -.. php:namespace:: Cake\Http\Cookie - -Cookie Collections -================== - -.. php:class:: CookieCollection - -``CookieCollection`` objects are accessible from the request and response objects. -They let you interact with groups of cookies using immutable patterns, which -allow the immutability of the request and response to be preserved. - -.. _creating-cookies: - -Creating Cookies ----------------- - -.. php:class:: Cookie - -``Cookie`` objects can be defined through constructor objects, or by using the -fluent interface that follows immutable patterns:: - - use Cake\Http\Cookie\Cookie; - - // All arguments in the constructor - $cookie = new Cookie( - 'remember_me', // name - 1, // value - new DateTime('+1 year'), // expiration time, if applicable - '/', // path, if applicable - 'example.com', // domain, if applicable - false, // secure only? - true // http only ? - ); - - // Using the builder methods - $cookie = (new Cookie('remember_me')) - ->withValue('1') - ->withExpiry(new DateTime('+1 year')) - ->withPath('/') - ->withDomain('example.com') - ->withSecure(false) - ->withHttpOnly(true); - -Once you have created a cookie, you can add it to a new or existing -``CookieCollection``:: - - use Cake\Http\Cookie\CookieCollection; - - // Create a new collection - $cookies = new CookieCollection([$cookie]); - - // Add to an existing collection - $cookies = $cookies->add($cookie); - - // Remove a cookie by name - $cookies = $cookies->remove('remember_me'); - -.. note:: - Remember that collections are immutable and adding cookies into, or removing - cookies from a collection, creates a *new* collection object. - -Cookie objects can be added to responses:: - - // Add one cookie - $response = $this->response->withCookie($cookie); - - // Replace the entire cookie collection - $response = $this->response->withCookieCollection($cookies); - -Cookies set to responses can be encrypted using the -:ref:`encrypted-cookie-middleware`. - -Reading Cookies ---------------- - -Once you have a ``CookieCollection`` instance, you can access the cookies it -contains:: - - // Check if a cookie exists - $cookies->has('remember_me'); - - // Get the number of cookies in the collection - count($cookies); - - // Get a cookie instance. Will throw an error if the cookie is not found - $cookie = $cookies->get('remember_me'); - - // Get a cookie or null - $cookie = $cookies->remember_me; - - // Check if a cookie exists - $exists = isset($cookies->remember_me) - -Once you have a ``Cookie`` object you can interact with it's state and modify -it. Keep in mind that cookies are immutable, so you'll need to update the -collection if you modify a cookie:: - - // Get the value - $value = $cookie->getValue() - - // Access data inside a JSON value - $id = $cookie->read('User.id'); - - // Check state - $cookie->isHttpOnly(); - $cookie->isSecure(); - -.. meta:: - :title lang=en: Request and Response objects - :keywords lang=en: request controller,request parameters,array indexes,purpose index,response objects,domain information,request object,request data,interrogating,params,parameters,previous versions,introspection,dispatcher,rout,data structures,arrays,ip address,migration,indexes,cakephp,PSR-7,immutable diff --git a/en/core-libraries/app.rst b/en/core-libraries/app.rst deleted file mode 100644 index 1b1bb6f926..0000000000 --- a/en/core-libraries/app.rst +++ /dev/null @@ -1,123 +0,0 @@ -App Class -######### - -.. php:namespace:: Cake\Core - -.. php:class:: App - -The App class is responsible for resource location and path management. - -Finding Classes -=============== - -.. php:staticmethod:: className($name, $type = '', $suffix = '') - -This method is used to resolve class names throughout CakePHP. It resolves -the short form names CakePHP uses and returns the fully resolved class name:: - - // Resolve a short class name with the namespace + suffix. - App::className('Flash', 'Controller/Component', 'Component'); - // Returns Cake\Controller\Component\FlashComponent - - // Resolve a plugin name. - App::className('DebugKit.Toolbar', 'Controller/Component', 'Component'); - // Returns DebugKit\Controller\Component\ToolbarComponent - - // Names with \ in them will be returned unaltered. - App::className('App\Cache\ComboCache'); - // Returns App\Cache\ComboCache - -When resolving classes, the ``App`` namespace will be tried, and if the -class does not exist the ``Cake`` namespace will be attempted. If both -class names do not exist, ``false`` will be returned. - -Finding Paths to Resources -========================== - -.. php:staticmethod:: path(string $package, ?string $plugin = null) - -The method returns paths set using ``App.paths`` app config:: - - // Get the templates path set using ``App.paths.templates`` app config. - App::path('templates'); - -The same way you can retrieve paths for ``locales``, ``plugins``. - -Finding Paths to Namespaces -=========================== - -.. php:staticmethod:: classPath(string $package, ?string $plugin = null) - -Used to get locations for paths based on conventions:: - - // Get the path to Controller/ in your application - App::classPath('Controller'); - -This can be done for all namespaces that are part of your application. - -``App::classPath()`` will only return the default path, and will not be able to -provide any information about additional paths the autoloader is configured -for. - -.. php:staticmethod:: core(string $package) - -Used for finding the path to a package inside CakePHP:: - - // Get the path to Cache engines. - App::core('Cache/Engine'); - -Locating Themes -=============== - -Since themes are plugins, you can use the methods above to get the path to -a theme. - -Loading Vendor Files -==================== - -Ideally vendor files should be autoloaded with ``Composer``, if you have vendor -files that cannot be autoloaded or installed with Composer you will need to use -``require`` to load them. - -If you cannot install a library with Composer, it is best to install each library in -a directory following Composer's convention of ``vendor/$author/$package``. -If you had a library called AcmeLib, you could install it into -``vendor/Acme/AcmeLib``. Assuming it did not use PSR-0 compatible classnames -you could autoload the classes within it using ``classmap`` in your -application's ``composer.json``:: - - "autoload": { - "psr-4": { - "App\\": "src/", - "App\\Test\\": "tests/" - }, - "classmap": [ - "vendor/Acme/AcmeLib" - ] - } - -If your vendor library does not use classes, and instead provides functions, you -can configure Composer to load these files at the beginning of each request -using the ``files`` autoloading strategy:: - - "autoload": { - "psr-4": { - "App\\": "src/", - "App\\Test\\": "tests/" - }, - "files": [ - "vendor/Acme/AcmeLib/functions.php" - ] - } - -After configuring the vendor libraries you will need to regenerate your -application's autoloader using:: - - $ php composer.phar dump-autoload - -If you happen to not be using Composer in your application, you will need to -manually load all vendor libraries yourself. - -.. meta:: - :title lang=en: App Class - :keywords lang=en: compatible implementation,model behaviors,path management,loading files,php class,class loading,model behavior,class location,component model,management class,autoloader,classname,directory location,override,conventions,lib,textile,cakephp,php classes,loaded diff --git a/en/core-libraries/caching.rst b/en/core-libraries/caching.rst deleted file mode 100644 index 27f787acb9..0000000000 --- a/en/core-libraries/caching.rst +++ /dev/null @@ -1,655 +0,0 @@ -Caching -####### - -.. php:namespace:: Cake\Cache - -.. php:class:: Cache - -Caching can be used to make reading from expensive or slow resources faster, by -maintaining a second copy of the required data in a faster or closer storage -system. For example, you can store the results of expensive queries, or remote -webservice access that doesn't frequently change in a cache. Once in the cache, -reading data from the cache is much cheaper than accessing the remote resource. - -Caching in CakePHP is facilitated by the ``Cache`` class. -This class provides a static interface and uniform API to -interact with various Caching implementations. CakePHP -provides several cache engines, and provides a simple interface if you need to -build your own backend. The built-in caching engines are: - -* ``File`` File cache is a simple cache that uses local files. It - is the slowest cache engine, and doesn't provide as many features for - atomic operations. However, since disk storage is often quite cheap, - storing large objects, or elements that are infrequently written - work well in files. -* ``Memcached`` Uses the `Memcached `_ - extension. -* ``Redis`` Uses the `phpredis `_ - extension. Redis provides a fast and persistent cache system similar to - Memcached, also provides atomic operations. -* ``Apcu`` APCu cache uses the PHP `APCu `_ extension. - This extension uses shared memory on the webserver to store objects. - This makes it very fast, and able to provide atomic read/write features. -* ``Array`` Stores all data in an array. This engine does not provide - persistent storage and is intended for use in application test suites. -* ``Null`` The null engine doesn't actually store anything and fails all read - operations. - -Regardless of the CacheEngine you choose to use, your application interacts with -:php:class:`Cake\\Cache\\Cache`. - -.. _cache-configuration: - -Configuring Cache Engines -========================= - -.. php:staticmethod:: setConfig($key, $config = null) - -Your application can configure any number of 'engines' during its bootstrap -process. Cache engine configurations are defined in **config/app.php**. - -For optimal performance CakePHP requires two cache engines to be defined. - -* ``_cake_core_`` is used for storing file maps, and parsed results of - :doc:`/core-libraries/internationalization-and-localization` files. -* ``_cake_model_``, is used to store schema descriptions for your applications - models. - -Using multiple engine configurations also lets you incrementally change the -storage as needed. For example in your **config/app.php** you could put the -following:: - - // ... - 'Cache' => [ - 'short' => [ - 'className' => 'File', - 'duration' => '+1 hours', - 'path' => CACHE, - 'prefix' => 'cake_short_', - ], - // Using a fully namespaced name. - 'long' => [ - 'className' => 'Cake\Cache\Engine\FileEngine', - 'duration' => '+1 week', - 'probability' => 100, - 'path' => CACHE . 'long' . DS, - ], - ] - // ... - -Configuration options can also be provided as a :term:`DSN` string. This is -useful when working with environment variables or :term:`PaaS` providers:: - - Cache::setConfig('short', [ - 'url' => 'memcached://user:password@cache-host/?timeout=3600&prefix=myapp_', - ]); - -When using a DSN string you can define any additional parameters/options as -query string arguments. - -You can also configure Cache engines at runtime:: - - // Using a short name - Cache::setConfig('short', [ - 'className' => 'File', - 'duration' => '+1 hours', - 'path' => CACHE, - 'prefix' => 'cake_short_' - ]); - - // Using a fully namespaced name. - Cache::setConfig('long', [ - 'className' => 'Cake\Cache\Engine\FileEngine', - 'duration' => '+1 week', - 'probability' => 100, - 'path' => CACHE . 'long' . DS, - ]); - - // Using a constructed object. - $object = new FileEngine($config); - Cache::setConfig('other', $object); - -The name of these engine configurations ('short' and 'long') are used as the ``$config`` -parameter for :php:meth:`Cake\\Cache\\Cache::write()` and -:php:meth:`Cake\\Cache\\Cache::read()`. When configuring cache engines you can -refer to the class name using the following syntaxes:: - - // Short name (in App\ or Cake namespaces) - Cache::setConfig('long', ['className' => 'File']); - - // Plugin short name - Cache::setConfig('long', ['className' => 'MyPlugin.SuperCache']); - - // Full namespace - Cache::setConfig('long', ['className' => 'Cake\Cache\Engine\FileEngine']); - - // An object implementing CacheEngineInterface - Cache::setConfig('long', ['className' => $myCache]); - -.. note:: - - When using the FileEngine you might need to use the ``mask`` option to - ensure cache files are made with the correct permissions. - -Engine Options --------------- - -Each engine accepts the following options: - -* ``duration`` Specify how long items in this cache configuration last. - Specified as a ``strtotime()`` compatible expression. -* ``groups`` List of groups or 'tags' associated to every key stored in this - config. Useful when you need to delete a subset of data from a cache. -* ``prefix`` Prepended to all entries. Good for when you need to share - a keyspace with either another cache config or another application. -* ``probability`` Probability of hitting a cache gc cleanup. Setting to 0 will disable - ``Cache::gc()`` from ever being called automatically. - -FileEngine Options -------------------- - -FileEngine uses the following engine specific options: - -* ``isWindows`` Automatically populated with whether the host is windows or not -* ``lock`` Should files be locked before writing to them? -* ``mask`` The mask used for created files -* ``path`` Path to where cachefiles should be saved. Defaults to system's temp dir. - -.. _caching-redisengine: - -RedisEngine Options -------------------- - -RedisEngine uses the following engine specific options: - -* ``port`` The port your Redis server is running on. -* ``host`` The host your Redis server is running on. -* ``database`` The database number to use for connection. -* ``password`` Redis server password. -* ``persistent`` Should a persistent connection be made to Redis. -* ``timeout`` Connection timeout for Redis. -* ``unix_socket`` Path to a unix socket for Redis. -* ``tls`` Connect to redis over TLS. -* ``ssl_key`` The ssl private key used for TLS connections. -* ``ssl_ca`` The ssl certificate authority file for TLS connections. -* ``ssl_cert`` The ssl certificate used for TLS connections. - -.. versionadded:: 5.1.0 - TLS connections were added in 5.1 - -MemcacheEngine Options ----------------------- - -- ``compress`` Whether to compress data. -- ``username`` Login to access the Memcache server. -- ``password`` Password to access the Memcache server. -- ``persistent`` The name of the persistent connection. All configurations using - the same persistent value will share a single underlying connection. -- ``serialize`` The serializer engine used to serialize data. Available engines are php, - igbinary and json. Beside php, the memcached extension must be compiled with the - appropriate serializer support. -- ``servers`` String or array of memcached servers. If an array MemcacheEngine will use - them as a pool. -- ``duration`` Be aware that any duration greater than 30 days will be treated as real - Unix time value rather than an offset from current time. -- ``options`` Additional options for the memcached client. Should be an array of option => value. - Use the ``\Memcached::OPT_*`` constants as keys. - -.. _cache-configuration-fallback: - -Configuring Cache Fallbacks ---------------------------- - -In the event that an engine is not available, such as the ``FileEngine`` trying -to write to an unwritable folder or the ``RedisEngine`` failing to connect to -Redis, the engine will fall back to the noop ``NullEngine`` and trigger a loggable -error. This prevents the application from throwing an uncaught exception due to -cache failure. - -You can configure Cache configurations to fall back to a specified config using -the ``fallback`` configuration key:: - - Cache::setConfig('redis', [ - 'className' => 'Redis', - 'duration' => '+1 hours', - 'prefix' => 'cake_redis_', - 'host' => '127.0.0.1', - 'port' => 6379, - 'fallback' => 'default', - ]); - -If initializing the ``RedisEngine`` instance fails, the ``redis`` cache configuration -would fall back to using the ``default`` cache configuration. If initializing the -engine for the ``default`` cache configuration *also* fails, in this scenario the -engine would fall back once again to the ``NullEngine`` and prevent the application -from throwing an uncaught exception. - -You can turn off cache fallbacks with ``false``:: - - Cache::setConfig('redis', [ - 'className' => 'Redis', - 'duration' => '+1 hours', - 'prefix' => 'cake_redis_', - 'host' => '127.0.0.1', - 'port' => 6379, - 'fallback' => false - ]); - -When there is no fallback cache failures will be raised as exceptions. - -Removing Configured Cache Engines ---------------------------------- - -.. php:staticmethod:: drop($key) - -Once a configuration is created you cannot change it. Instead you should drop -the configuration and re-create it using :php:meth:`Cake\\Cache\\Cache::drop()` and -:php:meth:`Cake\\Cache\\Cache::setConfig()`. Dropping a cache engine will remove -the config and destroy the adapter if it was constructed. - -Writing to a Cache -================== - -.. php:staticmethod:: write($key, $value, $config = 'default') - -``Cache::write()`` will write a $value to the Cache. You can read or -delete this value later by referring to it by ``$key``. You may -specify an optional configuration to store the cache in as well. If -no ``$config`` is specified, default will be used. ``Cache::write()`` -can store any type of object and is ideal for storing results of -model finds:: - - $posts = Cache::read('posts'); - if ($posts === null) { - $posts = $someService->getAllPosts(); - Cache::write('posts', $posts); - } - -Using ``Cache::write()`` and ``Cache::read()`` to reduce the number -of trips made to the database to fetch posts. - -.. note:: - - If you plan to cache the result of queries made with the CakePHP ORM, - it is better to use the built-in cache capabilities of the Query object - as described in the :ref:`caching-query-results` section - -Writing Multiple Keys at Once ------------------------------ - -.. php:staticmethod:: writeMany($data, $config = 'default') - -You may find yourself needing to write multiple cache keys at once. While you -can use multiple calls to ``write()``, ``writeMany()`` allows CakePHP to use -more efficient storage APIs where available. For example using ``writeMany()`` -save multiple network connections when using Memcached:: - - $result = Cache::writeMany([ - 'article-' . $slug => $article, - 'article-' . $slug . '-comments' => $comments - ]); - - // $result will contain - ['article-first-post' => true, 'article-first-post-comments' => true] - -Atomic writes -------------- - -.. php:staticmethod:: add($key, $value $config = 'default') - -Using ``Cache::add()`` will let you atomically set a key to a value if the key -does not already exist in the cache. If the key already exists in the cache -backend or the write fails, ``add()`` will return ``false``:: - - // Set a key to act as a lock - $result = Cache::add($lockKey, true); - if (!$result) { - return; - } - // Do an action where there can only be one process active at a time. - - // Remove the lock key. - Cache::delete($lockKey); - -.. warning:: - - File based caching does not support atomic writes. - -Read Through Caching --------------------- - -.. php:staticmethod:: remember($key, $callable, $config = 'default') - -Cache helps with read-through caching. If the named cache key exists, -it will be returned. If the key does not exist, the callable will be invoked -and the results stored in the cache at the provided key. - -For example, you often want to cache remote service call results. You could use -``remember()`` to make this simple:: - - class IssueService - { - public function allIssues($repo) - { - return Cache::remember($repo . '-issues', function () use ($repo) { - return $this->fetchAll($repo); - }); - } - } - -Reading From a Cache -==================== - -.. php:staticmethod:: read($key, $config = 'default') - -``Cache::read()`` is used to read the cached value stored under -``$key`` from the ``$config``. If ``$config`` is null the default -config will be used. ``Cache::read()`` will return the cached value -if it is a valid cache or ``null`` if the cache has expired or -doesn't exist. Use strict comparison operators ``===`` or ``!==`` -to check the success of the ``Cache::read()`` operation. - -For example:: - - $cloud = Cache::read('cloud'); - if ($cloud !== null) { - return $cloud; - } - - // Generate cloud data - // ... - - // Store data in cache - Cache::write('cloud', $cloud); - - return $cloud; - -Or if you are using another cache configuration called ``short``, you can -specify it in ``Cache::read()`` and ``Cache::write()`` calls as below:: - - // Read key "cloud", but from short configuration instead of default - $cloud = Cache::read('cloud', 'short'); - if ($cloud === null) { - // Generate cloud data - // ... - - // Store data in cache, using short cache configuration instead of default - Cache::write('cloud', $cloud, 'short'); - } - - return $cloud; - -Reading Multiple Keys at Once ------------------------------ - -.. php:staticmethod:: readMany($keys, $config = 'default') - -After you've written multiple keys at once, you'll probably want to read them as -well. While you could use multiple calls to ``read()``, ``readMany()`` allows -CakePHP to use more efficient storage APIs where available. For example using -``readMany()`` save multiple network connections when using Memcached:: - - $result = Cache::readMany([ - 'article-' . $slug, - 'article-' . $slug . '-comments' - ]); - // $result will contain - ['article-first-post' => '...', 'article-first-post-comments' => '...'] - -Deleting From a Cache -===================== - -.. php:staticmethod:: delete($key, $config = 'default') - -``Cache::delete()`` will allow you to completely remove a cached -object from the store:: - - // Remove a key - Cache::delete('my_key'); - -As of 4.4.0, the ``RedisEngine`` also provides a ``deleteAsync()`` method -which uses the ``UNLINK`` operation to remove cache keys:: - - Cache::pool('redis')->deleteAsync('my_key'); - -Deleting Multiple Keys at Once ------------------------------- - -.. php:staticmethod:: deleteMany($keys, $config = 'default') - -After you've written multiple keys at once, you may want to delete them. While -you could use multiple calls to ``delete()``, ``deleteMany()`` allows CakePHP to use -more efficient storage APIs where available. For example using ``deleteMany()`` -save multiple network connections when using Memcached:: - - $result = Cache::deleteMany([ - 'article-' . $slug, - 'article-' . $slug . '-comments' - ]); - // $result will contain - ['article-first-post' => true, 'article-first-post-comments' => true] - -Clearing Cached Data -==================== - -.. php:staticmethod:: clear($config = 'default') - -Destroy all cached values for a cache configuration. In engines like: Apcu, -Memcached, the cache configuration's prefix is used to remove -cache entries. Make sure that different cache configurations have different -prefixes:: - - // Will clear all keys. - Cache::clear(); - -As of 4.4.0, the ``RedisEngine`` also provides a ``clearBlocking()`` method -which uses the ``UNLINK`` operation to remove cache keys:: - - Cache::pool('redis')->clearBlocking(); - -.. note:: - - Because APCu uses isolated caches for webserver and CLI they - have to be cleared separately (CLI cannot clear webserver and vice versa). - -Using Cache to Store Counters -============================= - -.. php:staticmethod:: increment($key, $offset = 1, $config = 'default') - -.. php:staticmethod:: decrement($key, $offset = 1, $config = 'default') - -Counters in your application are good candidates for storage in a cache. As an -example, a simple countdown for remaining 'slots' in a contest could be stored -in Cache. The Cache class exposes atomic ways to increment/decrement counter -values. Atomic operations are important for these values as it -reduces the risk of contention, and ability for two users to simultaneously -lower the value by one, resulting in an incorrect value. - -After setting an integer value you can manipulate it using ``increment()`` and -``decrement()``:: - - Cache::write('initial_count', 10); - - // Later on - Cache::decrement('initial_count'); - - // Or - Cache::increment('initial_count'); - -.. note:: - - Incrementing and decrementing do not work with FileEngine. You should use - APCu, Redis or Memcached instead. - -Using Cache to Store Common Query Results -========================================= - -You can greatly improve the performance of your application by putting results -that infrequently change, or that are subject to heavy reads into the cache. -A perfect example of this are the results from -:php:meth:`Cake\\ORM\\Table::find()`. The Query object allows you to cache -results using the ``cache()`` method. See the :ref:`caching-query-results` section -for more information. - - -.. _cache-groups: - -Using Groups -============ - -Sometimes you will want to mark multiple cache entries to belong to certain -group or namespace. This is a common requirement for mass-invalidating keys -whenever some information changes that is shared among all entries in the same -group. This is possible by declaring the groups in cache configuration:: - - Cache::setConfig('site_home', [ - 'className' => 'Redis', - 'duration' => '+999 days', - 'groups' => ['comment', 'article'], - ]); - -.. php:method:: clearGroup($group, $config = 'default') - -Let's say you want to store the HTML generated for your homepage in cache, but -would also want to automatically invalidate this cache every time a comment or -post is added to your database. By adding the groups ``comment`` and ``article``, -we have effectively tagged any key stored into this cache configuration with -both group names. - -For instance, whenever a new post is added, we could tell the Cache engine to -remove all entries associated to the ``article`` group:: - - // src/Model/Table/ArticlesTable.php - public function afterSave($event, $entity, $options = []) - { - if ($entity->isNew()) { - Cache::clearGroup('article', 'site_home'); - } - } - -.. php:staticmethod:: groupConfigs($group = null) - -``groupConfigs()`` can be used to retrieve mapping between group and -configurations, i.e.: having the same group:: - - // src/Model/Table/ArticlesTable.php - - /** - * A variation of previous example that clears all Cache configurations - * having the same group - */ - public function afterSave($event, $entity, $options = []) - { - if ($entity->isNew()) { - $configs = Cache::groupConfigs('article'); - foreach ($configs['article'] as $config) { - Cache::clearGroup('article', $config); - } - } - } - -Groups are shared across all cache configs using the same engine and same -prefix. If you are using groups and want to take advantage of group deletion, -choose a common prefix for all your configs. - -Globally Enable or Disable Cache -================================ - -.. php:staticmethod:: disable() - -You may need to disable all Cache read & writes when trying to figure out cache -expiration related issues. You can do this using ``enable()`` and -``disable()``:: - - // Disable all cache reads, and cache writes. - Cache::disable(); - -Once disabled, all reads and writes will return ``null``. - -.. php:staticmethod:: enable() - -Once disabled, you can use ``enable()`` to re-enable caching:: - - // Re-enable all cache reads, and cache writes. - Cache::enable(); - -.. php:staticmethod:: enabled() - -If you need to check on the state of Cache, you can use ``enabled()``. - -Creating a Cache Engine -======================= - -You can provide custom ``Cache`` engines in ``App\Cache\Engine`` as well -as in plugins using ``$plugin\Cache\Engine``. Cache engines must be in a cache -directory. If you had a cache engine named ``MyCustomCacheEngine`` -it would be placed in either **src/Cache/Engine/MyCustomCacheEngine.php**. -Or in **plugins/MyPlugin/src/Cache/Engine/MyCustomCacheEngine.php** as -part of a plugin. Cache configs from plugins need to use the plugin -dot syntax:: - - Cache::setConfig('custom', [ - 'className' => 'MyPlugin.MyCustomCache', - // ... - ]); - -Custom Cache engines must extend :php:class:`Cake\\Cache\\CacheEngine` which -defines a number of abstract methods as well as provides a few initialization -methods. - -The required API for a CacheEngine is - -.. php:class:: CacheEngine - - The base class for all cache engines used with Cache. - -.. php:method:: write($key, $value) - - :return: boolean for success. - - Write value for a key into cache, Return ``true`` - if the data was successfully cached, ``false`` on failure. - -.. php:method:: read($key) - - :return: The cached value or ``null`` for failure. - - Read a key from the cache. Return ``null`` to indicate - the entry has expired or does not exist. - -.. php:method:: delete($key) - - :return: Boolean ``true`` on success. - - Delete a key from the cache. Return ``false`` to indicate that - the entry did not exist or could not be deleted. - -.. php:method:: clear($check) - - :return: Boolean ``true`` on success. - - Delete all keys from the cache. If $check is ``true``, you should - validate that each value is actually expired. - -.. php:method:: clearGroup($group) - - :return: Boolean ``true`` on success. - - Delete all keys from the cache belonging to the same group. - -.. php:method:: decrement($key, $offset = 1) - - :return: Boolean ``true`` on success. - - Decrement a number under the key and return decremented value - -.. php:method:: increment($key, $offset = 1) - - :return: Boolean ``true`` on success. - - Increment a number under the key and return incremented value - -.. meta:: - :title lang=en: Caching - :keywords lang=en: uniform api,cache engine,cache system,atomic operations,php class,disk storage,static methods,php extension,consistent manner,similar features,apcu,apc,memcache,queries,cakephp,elements,servers,memory diff --git a/en/core-libraries/collections.rst b/en/core-libraries/collections.rst deleted file mode 100644 index f7b57deb70..0000000000 --- a/en/core-libraries/collections.rst +++ /dev/null @@ -1,1215 +0,0 @@ -Collections -########### - -.. php:namespace:: Cake\Collection - -.. php:class:: Collection - -The collection classes provide a set of tools to manipulate arrays or -``Traversable`` objects. If you have ever used underscore.js, -you have an idea of what you can expect from the collection classes. - -Collection instances are immutable; modifying a collection will instead generate -a new collection. This makes working with collection objects more predictable as -operations are side-effect free. - -Quick Example -============= - -Collections can be created using an array or ``Traversable`` object. You'll also -interact with collections every time you interact with the ORM in CakePHP. -A simple use of a Collection would be:: - - use Cake\Collection\Collection; - - $items = ['a' => 1, 'b' => 2, 'c' => 3]; - $collection = new Collection($items); - - // Create a new collection containing elements - // with a value greater than one. - $overOne = $collection->filter(function ($value, $key, $iterator) { - return $value > 1; - }); - -You can also use the ``collection()`` helper function instead of ``new -Collection()``:: - - $items = ['a' => 1, 'b' => 2, 'c' => 3]; - - // These both make a Collection instance. - $collectionA = new Collection($items); - $collectionB = collection($items); - -The benefit of the helper method is that it is easier to chain off of than -``(new Collection($items))``. - -The :php:trait:`~Cake\\Collection\\CollectionTrait` allows you to integrate -collection-like features into any ``Traversable`` object you have in your -application as well. - -List of Methods -=============== - -.. csv-table:: - :class: docutils internal-toc - - :php:meth:`append`, :php:meth:`appendItem`, :php:meth:`avg`, - :php:meth:`buffered`, :php:meth:`chunk`, :php:meth:`chunkWithKeys` - :php:meth:`combine`, :php:meth:`compile`, :php:meth:`contains` - :php:meth:`countBy`, :php:meth:`each`, :php:meth:`every` - :php:meth:`extract`, :php:meth:`filter`, :php:meth:`first` - :php:meth:`firstMatch`, :php:meth:`groupBy`, :php:meth:`indexBy` - :php:meth:`insert`, :php:meth:`isEmpty`, :php:meth:`last` - :php:meth:`listNested`, :php:meth:`map`, :php:meth:`match` - :php:meth:`max`, :php:meth:`median`, :php:meth:`min` - :php:meth:`nest`, :php:meth:`prepend`, :php:meth:`prependItem` - :php:meth:`reduce`, :php:meth:`reject`, :php:meth:`sample` - :php:meth:`shuffle`, :php:meth:`skip`, :php:meth:`some` - :php:meth:`sortBy`, :php:meth:`stopWhen`, :php:meth:`sumOf` - :php:meth:`take`, :php:meth:`through`, :php:meth:`transpose` - :php:meth:`unfold`, :php:meth:`zip` - -Iterating -========= - -.. php:method:: each($callback) - -Collections can be iterated and/or transformed into new collections with the -``each()`` and ``map()`` methods. The ``each()`` method will not create a new -collection, but will allow you to modify any objects within the collection:: - - $collection = new Collection($items); - $collection = $collection->each(function ($value, $key) { - echo "Element $key: $value"; - }); - -The return of ``each()`` will be the collection object. Each will iterate the -collection immediately applying the callback to each value in the collection. - -.. php:method:: map($callback) - -The ``map()`` method will create a new collection based on the output of the -callback being applied to each object in the original collection:: - - $items = ['a' => 1, 'b' => 2, 'c' => 3]; - $collection = new Collection($items); - - $new = $collection->map(function ($value, $key) { - return $value * 2; - }); - - // $result contains [2, 4, 6]; - $result = $new->toList(); - - // $result contains ['a' => 2, 'b' => 4, 'c' => 6]; - $result = $new->toArray(); - -The ``map()`` method will create a new iterator which lazily creates -the resulting items when iterated. - -.. php:method:: extract($path) - -One of the most common uses for a ``map()`` function is to extract a single -column from a collection. If you are looking to build a list of elements -containing the values for a particular property, you can use the ``extract()`` -method:: - - $collection = new Collection($people); - $names = $collection->extract('name'); - - // $result contains ['mark', 'jose', 'barbara']; - $result = $names->toList(); - -As with many other functions in the collection class, you are allowed to specify -a dot-separated path for extracting columns. This example will return -a collection containing the author names from a list of articles:: - - $collection = new Collection($articles); - $names = $collection->extract('author.name'); - - // $result contains ['Maria', 'Stacy', 'Larry']; - $result = $names->toList(); - -Finally, if the property you are looking after cannot be expressed as a path, -you can use a callback function to return it:: - - $collection = new Collection($articles); - $names = $collection->extract(function ($article) { - return $article->author->name . ', ' . $article->author->last_name; - }); - -Often, the properties you need to extract a common key present in multiple -arrays or objects that are deeply nested inside other structures. For those -cases you can use the ``{*}`` matcher in the path key. This matcher is often -helpful when matching HasMany and BelongsToMany association data:: - - $data = [ - [ - 'name' => 'James', - 'phone_numbers' => [ - ['number' => 'number-1'], - ['number' => 'number-2'], - ['number' => 'number-3'], - ], - ], - [ - 'name' => 'James', - 'phone_numbers' => [ - ['number' => 'number-4'], - ['number' => 'number-5'], - ], - ], - ]; - - $numbers = (new Collection($data))->extract('phone_numbers.{*}.number'); - $result = $numbers->toList(); - // $result contains ['number-1', 'number-2', 'number-3', 'number-4', 'number-5'] - -This last example uses ``toList()`` unlike other examples, which is important -when we're getting results with possibly duplicate keys. By using ``toList()`` -we'll be guaranteed to get all values even if there are duplicate keys. - -Unlike :php:meth:`Cake\\Utility\\Hash::extract()` this method only supports the -``{*}`` wildcard. All other wildcard and attributes matchers are not supported. - -.. php:method:: combine($keyPath, $valuePath, $groupPath = null) - -Collections allow you to create a new collection made from keys and values in -an existing collection. Both the key and value paths can be specified with -dot notation paths:: - - $items = [ - ['id' => 1, 'name' => 'foo', 'parent' => 'a'], - ['id' => 2, 'name' => 'bar', 'parent' => 'b'], - ['id' => 3, 'name' => 'baz', 'parent' => 'a'], - ]; - $combined = (new Collection($items))->combine('id', 'name'); - $result = $combined->toArray(); - - // $result contains - [ - 1 => 'foo', - 2 => 'bar', - 3 => 'baz', - ]; - -You can also optionally use a ``groupPath`` to group results based on a path:: - - $combined = (new Collection($items))->combine('id', 'name', 'parent'); - $result = $combined->toArray(); - - // $result contains - [ - 'a' => [1 => 'foo', 3 => 'baz'], - 'b' => [2 => 'bar'] - ]; - -Finally you can use *closures* to build keys/values/groups paths dynamically, -for example when working with entities and dates (converted to ``I18n\DateTime`` -instances by the ORM) you may want to group results by date:: - - $combined = (new Collection($entities))->combine( - 'id', - function ($entity) { return $entity; }, - function ($entity) { return $entity->date->toDateString(); } - ); - $result = $combined->toArray(); - - // $result contains - [ - 'date string like 2015-05-01' => ['entity1->id' => entity1, 'entity2->id' => entity2, ..., 'entityN->id' => entityN] - 'date string like 2015-06-01' => ['entity1->id' => entity1, 'entity2->id' => entity2, ..., 'entityN->id' => entityN] - ] - -.. php:method:: stopWhen(callable $c) - -You can stop the iteration at any point using the ``stopWhen()`` method. Calling -it in a collection will create a new one that will stop yielding results if the -passed callable returns true for one of the elements:: - - $items = [10, 20, 50, 1, 2]; - $collection = new Collection($items); - - $new = $collection->stopWhen(function ($value, $key) { - // Stop on the first value bigger than 30 - return $value > 30; - }); - - // $result contains [10, 20]; - $result = $new->toList(); - -.. php:method:: unfold(callable $callback) - -Sometimes the internal items of a collection will contain arrays or iterators -with more items. If you wish to flatten the internal structure to iterate once -over all elements you can use the ``unfold()`` method. It will create a new -collection that will yield every single element nested in the collection:: - - $items = [[1, 2, 3], [4, 5]]; - $collection = new Collection($items); - $new = $collection->unfold(); - - // $result contains [1, 2, 3, 4, 5]; - $result = $new->toList(); - -When passing a callable to ``unfold()`` you can control what elements will be -unfolded from each item in the original collection. This is useful for returning -data from paginated services:: - - $pages = [1, 2, 3, 4]; - $collection = new Collection($pages); - $items = $collection->unfold(function ($page, $key) { - // An imaginary web service that returns a page of results - return MyService::fetchPage($page)->toList(); - }); - - $allPagesItems = $items->toList(); - -If you are using PHP 5.5+, you can use the ``yield`` keyword inside ``unfold()`` -to return as many elements for each item in the collection as you may need:: - - $oddNumbers = [1, 3, 5, 7]; - $collection = new Collection($oddNumbers); - $new = $collection->unfold(function ($oddNumber) { - yield $oddNumber; - yield $oddNumber + 1; - }); - - // $result contains [1, 2, 3, 4, 5, 6, 7, 8]; - $result = $new->toList(); - -.. php:method:: chunk($chunkSize) - -When dealing with large amounts of items in a collection, it may make sense to -process the elements in batches instead of one by one. For splitting -a collection into multiple arrays of a certain size, you can use the ``chunk()`` -function:: - - $items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; - $collection = new Collection($items); - $chunked = $collection->chunk(2); - $chunked->toList(); // [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]] - -The ``chunk`` function is particularly useful when doing batch processing, for -example with a database result:: - - $collection = new Collection($articles); - $collection->map(function ($article) { - // Change a property in the article - $article->property = 'changed'; - }) - ->chunk(20) - ->each(function ($batch) { - myBulkSave($batch); // This function will be called for each batch - }); - -.. php:method:: chunkWithKeys($chunkSize) - -Much like :php:meth:`chunk()`, ``chunkWithKeys()`` allows you to slice up -a collection into smaller batches but with keys preserved. This is useful when -chunking associative arrays:: - - $collection = new Collection([ - 'a' => 1, - 'b' => 2, - 'c' => 3, - 'd' => [4, 5] - ]); - $chunked = $collection->chunkWithKeys(2); - $result = $chunked->toList(); - - // $result contains - [ - ['a' => 1, 'b' => 2], - ['c' => 3, 'd' => [4, 5]] - ] - -Filtering -========= - -.. php:method:: filter($callback) - -Collections allow you to filter and create new collections based on -the result of callback functions. You can use ``filter()`` to create a new -collection of elements matching a criteria callback:: - - $collection = new Collection($people); - $ladies = $collection->filter(function ($person, $key) { - return $person->gender === 'female'; - }); - $guys = $collection->filter(function ($person, $key) { - return $person->gender === 'male'; - }); - -.. php:method:: reject(callable $c) - -The inverse of ``filter()`` is ``reject()``. This method does a negative filter, -removing elements that match the filter function:: - - $collection = new Collection($people); - $ladies = $collection->reject(function ($person, $key) { - return $person->gender === 'male'; - }); - -.. php:method:: every($callback) - -You can do truth tests with filter functions. To see if every element in -a collection matches a test you can use ``every()``:: - - $collection = new Collection($people); - $allYoungPeople = $collection->every(function ($person) { - return $person->age < 21; - }); - -.. php:method:: some($callback) - -You can see if the collection contains at least one element matching a filter -function using the ``some()`` method:: - - $collection = new Collection($people); - $hasYoungPeople = $collection->some(function ($person) { - return $person->age < 21; - }); - -.. php:method:: match($conditions) - -If you need to extract a new collection containing only the elements that -contain a given set of properties, you should use the ``match()`` method:: - - $collection = new Collection($comments); - $commentsFromMark = $collection->match(['user.name' => 'Mark']); - -.. php:method:: firstMatch($conditions) - -The property name can be a dot-separated path. You can traverse into nested -entities and match the values they contain. When you only need the first -matching element from a collection, you can use ``firstMatch()``:: - - $collection = new Collection($comments); - $comment = $collection->firstMatch([ - 'user.name' => 'Mark', - 'active' => true - ]); - -As you can see from the above, both ``match()`` and ``firstMatch()`` allow you -to provide multiple conditions to match on. In addition, the conditions can be -for different paths, allowing you to express complex conditions to match -against. - -Aggregation -=========== - -.. php:method:: reduce($callback, $initial) - -The counterpart of a ``map()`` operation is usually a ``reduce``. This -function will help you build a single result out of all the elements in a -collection:: - - $totalPrice = $collection->reduce(function ($accumulated, $orderLine) { - return $accumulated + $orderLine->price; - }, 0); - -In the above example, ``$totalPrice`` will be the sum of all single prices -contained in the collection. Note the second argument for the ``reduce()`` -function takes the initial value for the reduce operation you are -performing:: - - $allTags = $collection->reduce(function ($accumulated, $article) { - return array_merge($accumulated, $article->tags); - }, []); - -.. php:method:: min(string|callable $callback, $type = SORT_NUMERIC) - -To extract the minimum value for a collection based on a property, just use the -``min()`` function. This will return the full element from the collection and -not just the smallest value found:: - - $collection = new Collection($people); - $youngest = $collection->min('age'); - - echo $youngest->name; - -You are also able to express the property to compare by providing a path or a -callback function:: - - $collection = new Collection($people); - $personYoungestChild = $collection->min(function ($person) { - return $person->child->age; - }); - - $personWithYoungestDad = $collection->min('dad.age'); - -.. php:method:: max(string|callable $callback, $type = SORT_NUMERIC) - -The same can be applied to the ``max()`` function, which will return a single -element from the collection having the highest property value:: - - $collection = new Collection($people); - $oldest = $collection->max('age'); - - $personOldestChild = $collection->max(function ($person) { - return $person->child->age; - }); - - $personWithOldestDad = $collection->max('dad.age'); - -.. php:method:: sumOf($path = null) - -Finally, the ``sumOf()`` method will return the sum of a property of all -elements:: - - $collection = new Collection($people); - $sumOfAges = $collection->sumOf('age'); - - $sumOfChildrenAges = $collection->sumOf(function ($person) { - return $person->child->age; - }); - - $sumOfDadAges = $collection->sumOf('dad.age'); - -.. php:method:: avg($path = null) - -Calculate the average value of the elements in the collection. Optionally -provide a matcher path, or function to extract values to generate the average -for:: - - $items = [ - ['invoice' => ['total' => 100]], - ['invoice' => ['total' => 200]], - ]; - - // $average contains 150 - $average = (new Collection($items))->avg('invoice.total'); - -.. php:method:: median($path = null) - -Calculate the median value of a set of elements. Optionally provide a matcher -path, or function to extract values to generate the median for:: - - $items = [ - ['invoice' => ['total' => 400]], - ['invoice' => ['total' => 500]], - ['invoice' => ['total' => 100]], - ['invoice' => ['total' => 333]], - ['invoice' => ['total' => 200]], - ]; - - // $median contains 333 - $median = (new Collection($items))->median('invoice.total'); - - -Grouping and Counting ---------------------- - -.. php:method:: groupBy($callback) - -Collection values can be grouped by different keys in a new collection when they -share the same value for a property:: - - $students = [ - ['name' => 'Mark', 'grade' => 9], - ['name' => 'Andrew', 'grade' => 10], - ['name' => 'Stacy', 'grade' => 10], - ['name' => 'Barbara', 'grade' => 9] - ]; - $collection = new Collection($students); - $studentsByGrade = $collection->groupBy('grade'); - $result = $studentsByGrade->toArray(); - - // $result contains - [ - 10 => [ - ['name' => 'Andrew', 'grade' => 10], - ['name' => 'Stacy', 'grade' => 10] - ], - 9 => [ - ['name' => 'Mark', 'grade' => 9], - ['name' => 'Barbara', 'grade' => 9] - ] - ] - -As usual, it is possible to provide either a dot-separated path for nested -properties or your own callback function to generate the groups dynamically:: - - $commentsByUserId = $comments->groupBy('user.id'); - - $classResults = $students->groupBy(function ($student) { - return $student->grade > 6 ? 'approved' : 'denied'; - }); - -.. php:method:: countBy($callback) - -If you only wish to know the number of occurrences per group, you can do so by -using the ``countBy()`` method. It takes the same arguments as ``groupBy`` so it -should be already familiar to you:: - - $classResults = $students->countBy(function ($student) { - return $student->grade > 6 ? 'approved' : 'denied'; - }); - - // Result could look like this when converted to array: - ['approved' => 70, 'denied' => 20] - -.. php:method:: indexBy($callback) - -There will be certain cases where you know an element is unique for the property -you want to group by. If you wish a single result per group, you can use the -function ``indexBy()``:: - - $usersById = $users->indexBy('id'); - - // When converted to array result could look like - [ - 1 => 'markstory', - 3 => 'jose_zap', - 4 => 'jrbasso' - ] - -As with the ``groupBy()`` function you can also use a property path or -a callback:: - - $articlesByAuthorId = $articles->indexBy('author.id'); - - $filesByHash = $files->indexBy(function ($file) { - return md5($file); - }); - -.. php:method:: zip($items) - -The elements of different collections can be grouped together using the -``zip()`` method. It will return a new collection containing an array grouping -the elements from each collection that are placed at the same position:: - - $odds = new Collection([1, 3, 5]); - $pairs = new Collection([2, 4, 6]); - $combined = $odds->zip($pairs)->toList(); // [[1, 2], [3, 4], [5, 6]] - -You can also zip multiple collections at once:: - - $years = new Collection([2013, 2014, 2015, 2016]); - $salaries = [1000, 1500, 2000, 2300]; - $increments = [0, 500, 500, 300]; - - $rows = $years->zip($salaries, $increments); - $result = $rows->toList(); - - // $result contains - [ - [2013, 1000, 0], - [2014, 1500, 500], - [2015, 2000, 500], - [2016, 2300, 300] - ] - -As you can already see, the ``zip()`` method is very useful for transposing -multidimensional arrays:: - - $data = [ - 2014 => ['jan' => 100, 'feb' => 200], - 2015 => ['jan' => 300, 'feb' => 500], - 2016 => ['jan' => 400, 'feb' => 600], - ]; - - // Getting jan and feb data together - - $firstYear = new Collection(array_shift($data)); - $result = $firstYear->zip($data[0], $data[1])->toList(); - - // Or $firstYear->zip(...$data) in PHP >= 5.6 - - // $result contains - [ - [100, 300, 400], - [200, 500, 600] - ] - -Sorting -======= - -.. php:method:: sortBy($callback, $order = SORT_DESC, $sort = SORT_NUMERIC) - -Collection values can be sorted in ascending or descending order based on -a column or custom function. To create a new sorted collection out of the values -of another one, you can use ``sortBy``:: - - $collection = new Collection($people); - $sorted = $collection->sortBy('age'); - -As seen above, you can sort by passing the name of a column or property that -is present in the collection values. You are also able to specify a property -path instead using the dot notation. The next example will sort articles by -their author's name:: - - $collection = new Collection($articles); - $sorted = $collection->sortBy('author.name'); - -The ``sortBy()`` method is flexible enough to let you specify an extractor -function that will let you dynamically select the value to use for comparing two -different values in the collection:: - - $collection = new Collection($articles); - $sorted = $collection->sortBy(function ($article) { - return $article->author->name . '-' . $article->title; - }); - -In order to specify in which direction the collection should be sorted, you need -to provide either ``SORT_ASC`` or ``SORT_DESC`` as the second parameter for -sorting in ascending or descending direction respectively. By default, -collections are sorted in descending direction:: - - $collection = new Collection($people); - $sorted = $collection->sortBy('age', SORT_ASC); - -Sometimes you will need to specify which type of data you are trying to compare -so that you get consistent results. For this purpose, you should supply a third -argument in the ``sortBy()`` function with one of the following constants: - -- **SORT_NUMERIC**: For comparing numbers -- **SORT_STRING**: For comparing string values -- **SORT_NATURAL**: For sorting string containing numbers and you'd like those - numbers to be order in a natural way. For example: showing "10" after "2". -- **SORT_LOCALE_STRING**: For comparing strings based on the current locale. - -By default, ``SORT_NUMERIC`` is used:: - - $collection = new Collection($articles); - $sorted = $collection->sortBy('title', SORT_ASC, SORT_NATURAL); - -.. warning:: - - It is often expensive to iterate sorted collections more than once. If you - plan to do so, consider converting the collection to an array or simply use - the ``compile()`` method on it. - -Working with Tree Data -====================== - -.. php:method:: nest($idPath, $parentPath, $nestingKey = 'children') - -Not all data is meant to be represented in a linear way. Collections make it -easier to construct and flatten hierarchical or nested structures. Creating -a nested structure where children are grouped by a parent identifier property -can be done with the ``nest()`` method. - -Two parameters are required for this function. The first one is the property -representing the item identifier. The second parameter is the name of the -property representing the identifier for the parent item:: - - $collection = new Collection([ - ['id' => 1, 'parent_id' => null, 'name' => 'Birds'], - ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds'], - ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'], - ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'], - ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish'], - ['id' => 6, 'parent_id' => null, 'name' => 'Fish'], - ]); - $nested = $collection->nest('id', 'parent_id'); - $result = $nested->toList(); - - // $result contains - [ - [ - 'id' => 1, - 'parent_id' => null, - 'name' => 'Birds', - 'children' => [ - ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds', 'children' => []], - ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle', 'children' => []], - ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull', 'children' => []], - ], - ], - [ - 'id' => 6, - 'parent_id' => null, - 'name' => 'Fish', - 'children' => [ - ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish', 'children' => []], - ], - ], - ]; - -Children elements are nested inside the ``children`` property inside each of the -items in the collection. This type of data representation is helpful for -rendering menus or traversing elements up to certain level in the tree. - -.. php:method:: listNested($order = 'desc', $nestingKey = 'children') - -The inverse of ``nest()`` is ``listNested()``. This method allows you to flatten -a tree structure back into a linear structure. It takes two parameters; the -first one is the traversing mode (asc, desc or leaves), and the second one is -the name of the property containing the children for each element in the -collection. - -Taking the input the nested collection built in the previous example, we can -flatten it:: - - $result = $nested->listNested()->toList(); - - // $result contains - [ - ['id' => 1, 'parent_id' => null, 'name' => 'Birds', 'children' => [...]], - ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds'], - ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'], - ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'], - ['id' => 6, 'parent_id' => null, 'name' => 'Fish', 'children' => [...]], - ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish'] - ] - -By default, the tree is traversed from the root to the leaves. You can also -instruct it to only return the leaf elements in the tree:: - - $result = $nested->listNested('leaves')->toList(); - - // $result contains - [ - ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds', 'children' => [], ], - ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle', 'children' => [], ], - ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull', 'children' => [], ], - ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish', 'children' => [], ], - ] - - -Once you have converted a tree into a nested list, you can use the ``printer()`` -method to configure how the list output should be formatted:: - - $result = $nested->listNested()->printer('name', 'id', '--')->toArray(); - - // $result contains - [ - 1 => 'Birds', - 2 => '--Land Birds', - 3 => '--Eagle', - 4 => '--Seagull', - 6 => 'Fish', - 5 => '--Clown Fish', - ] - -The ``printer()`` method also lets you use a callback to generate the keys and -or values:: - - $nested->listNested()->printer( - function ($el) { - return $el->name; - }, - function ($el) { - return $el->id; - } - ); - -Other Methods -============= - -.. php:method:: isEmpty() - -Allows you to see if a collection contains any elements:: - - $collection = new Collection([]); - // Returns true - $collection->isEmpty(); - - $collection = new Collection([1]); - // Returns false - $collection->isEmpty(); - -.. php:method:: contains($value) - -Collections allow you to quickly check if they contain one particular -value: by using the ``contains()`` method:: - - $items = ['a' => 1, 'b' => 2, 'c' => 3]; - $collection = new Collection($items); - $hasThree = $collection->contains(3); - -Comparisons are performed using the ``===`` operator. If you wish to do looser -comparison types you can use the ``some()`` method. - -.. php:method:: shuffle() - -Sometimes you may wish to show a collection of values in a random order. In -order to create a new collection that will return each value in a randomized -position, use the ``shuffle``:: - - $collection = new Collection(['a' => 1, 'b' => 2, 'c' => 3]); - - // This could return [2, 3, 1] - $collection->shuffle()->toList(); - -.. php:method:: transpose() - -When you transpose a collection, you get a new collection containing a row made -of the each of the original columns:: - - $items = [ - ['Products', '2012', '2013', '2014'], - ['Product A', '200', '100', '50'], - ['Product B', '300', '200', '100'], - ['Product C', '400', '300', '200'], - ]; - $transpose = (new Collection($items))->transpose(); - $result = $transpose->toList(); - - // $result contains - [ - ['Products', 'Product A', 'Product B', 'Product C'], - ['2012', '200', '300', '400'], - ['2013', '100', '200', '300'], - ['2014', '50', '100', '200'], - ] - -Withdrawing Elements --------------------- - -.. php:method:: sample($length = 10) - -Shuffling a collection is often useful when doing quick statistical analysis. -Another common operation when doing this sort of task is withdrawing a few -random values out of a collection so that more tests can be performed on those. -For example, if you wanted to select 5 random users to which you'd like to apply -some A/B tests to, you can use the ``sample()`` function:: - - $collection = new Collection($people); - - // Withdraw maximum 20 random users from this collection - $testSubjects = $collection->sample(20); - -``sample()`` will take at most the number of values you specify in the first -argument. If there are not enough elements in the collection to satisfy the -sample, the full collection in a random order is returned. - -.. php:method:: take($length, $offset) - -Whenever you want to take a slice of a collection use the ``take()`` function, -it will create a new collection with at most the number of values you specify in -the first argument, starting from the position passed in the second argument:: - - $topFive = $collection->sortBy('age')->take(5); - - // Take 5 people from the collection starting from position 4 - $nextTopFive = $collection->sortBy('age')->take(5, 4); - -Positions are zero-based, therefore the first position number is ``0``. - -.. php:method:: skip($length) - -While the second argument of ``take()`` can help you skip some elements before -getting them from the collection, you can also use ``skip()`` for the same -purpose as a way to take the rest of the elements after a certain position:: - - $collection = new Collection([1, 2, 3, 4]); - $allExceptFirstTwo = $collection->skip(2)->toList(); // [3, 4] - -.. php:method:: first() - -One of the most common uses of ``take()`` is getting the first element in the -collection. A shortcut method for achieving the same goal is using the -``first()`` method:: - - $collection = new Collection([5, 4, 3, 2]); - $collection->first(); // Returns 5 - -.. php:method:: last() - -Similarly, you can get the last element of a collection using the ``last()`` -method:: - - $collection = new Collection([5, 4, 3, 2]); - $collection->last(); // Returns 2 - -Expanding Collections ---------------------- - -.. php:method:: append(array|Traversable $items) - -You can compose multiple collections into a single one. This enables you to -gather data from various sources, concatenate it, and apply other collection -functions to it very smoothly. The ``append()`` method will return a new -collection containing the values from both sources:: - - $cakephpTweets = new Collection($tweets); - $myTimeline = $cakephpTweets->append($phpTweets); - - // Tweets containing `cakefest` from both sources - $myTimeline->filter(function ($tweet) { - return strpos($tweet, 'cakefest'); - }); - -.. php:method:: appendItem($value, $key) - -Allows you to append an item with an optional key to the collection. If you -specify a key that already exists in the collection, the value will not be -overwritten:: - - $cakephpTweets = new Collection($tweets); - $myTimeline = $cakephpTweets->appendItem($newTweet, 99); - -.. php:method:: prepend($items) - -The ``prepend()`` method will return a new collection containing the values from -both sources:: - - $cakephpTweets = new Collection($tweets); - $myTimeline = $cakephpTweets->prepend($phpTweets); - -.. php:method:: prependItem($value, $key) - -Allows you to prepend an item with an optional key to the collection. If you -specify a key that already exists in the collection, the value will not be -overwritten:: - - $cakephpTweets = new Collection($tweets); - $myTimeline = $cakephpTweets->prependItem($newTweet, 99); - -.. warning:: - - When appending from different sources, you can expect some keys from both - collections to be the same. For example, when appending two simple arrays. - This can present a problem when converting a collection to an array using - ``toArray()``. If you do not want values from one collection to override - others in the previous one based on their key, make sure that you call - ``toList()`` in order to drop the keys and preserve all values. - -Modifiying Elements -------------------- - -.. php:method:: insert($path, $items) - -At times, you may have two separate sets of data that you would like to insert -the elements of one set into each of the elements of the other set. This is -a very common case when you fetch data from a data source that does not support -data-merging or joins natively. - -Collections offer an ``insert()`` method that will allow you to insert each of -the elements in one collection into a property inside each of the elements of -another collection:: - - $users = [ - ['username' => 'mark'], - ['username' => 'juan'], - ['username' => 'jose'] - ]; - - $languages = [ - ['PHP', 'Python', 'Ruby'], - ['Bash', 'PHP', 'Javascript'], - ['Javascript', 'Prolog'] - ]; - - $merged = (new Collection($users))->insert('skills', $languages); - $result = $merged->toArray(); - - // $result contains - [ - ['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']], - ['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']], - ['username' => 'jose', 'skills' => ['Javascript', 'Prolog']] - ]; - -The first parameter for the ``insert()`` method is a dot-separated path of -properties to follow so that the elements can be inserted at that position. The -second argument is anything that can be converted to a collection object. - -Please observe that elements are inserted by the position they are found, thus, -the first element of the second collection is merged into the first -element of the first collection. - -If there are not enough elements in the second collection to insert into the -first one, then the target property will not be present:: - - $languages = [ - ['PHP', 'Python', 'Ruby'], - ['Bash', 'PHP', 'Javascript'] - ]; - - $merged = (new Collection($users))->insert('skills', $languages); - $result = $merged->toArray(); - - // $result contains - [ - ['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']], - ['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']], - ['username' => 'jose'] - ]; - -The ``insert()`` method can operate array elements or objects implementing the -``ArrayAccess`` interface. - -Making Collection Methods Reusable ----------------------------------- - -Using closures for collection methods is great when the work to be done is small -and focused, but it can get messy very quickly. This becomes more obvious when -a lot of different methods need to be called or when the length of the closure -methods is more than just a few lines. - -There are also cases when the logic used for the collection methods can be -reused in multiple parts of your application. It is recommended that you -consider extracting complex collection logic to separate classes. For example, -imagine a lengthy closure like this one:: - - $collection - ->map(function ($row, $key) { - if (!empty($row['items'])) { - $row['total'] = collection($row['items'])->sumOf('price'); - } - - if (!empty($row['total'])) { - $row['tax_amount'] = $row['total'] * 0.25; - } - - // More code here... - - return $modifiedRow; - }); - -This can be refactored by creating another class:: - - class TotalOrderCalculator - { - public function __invoke($row, $key) - { - if (!empty($row['items'])) { - $row['total'] = collection($row['items'])->sumOf('price'); - } - - if (!empty($row['total'])) { - $row['tax_amount'] = $row['total'] * 0.25; - } - - // More code here... - - return $modifiedRow; - } - } - - // Use the logic in your map() call - $collection->map(new TotalOrderCalculator) - -.. php:method:: through($callback) - -Sometimes a chain of collection method calls can become reusable in other parts -of your application, but only if they are called in that specific order. In -those cases you can use ``through()`` in combination with a class implementing -``__invoke`` to distribute your handy data processing calls:: - - $collection - ->map(new ShippingCostCalculator) - ->map(new TotalOrderCalculator) - ->map(new GiftCardPriceReducer) - ->buffered() - ... - -The above method calls can be extracted into a new class so they don't need to -be repeated every time:: - - class FinalCheckOutRowProcessor - { - public function __invoke($collection) - { - return $collection - ->map(new ShippingCostCalculator) - ->map(new TotalOrderCalculator) - ->map(new GiftCardPriceReducer) - ->buffered() - ... - } - } - - // Now you can use the through() method to call all methods at once - $collection->through(new FinalCheckOutRowProcessor); - -Optimizing Collections ----------------------- - -.. php:method:: buffered() - -Collections often perform most operations that you create using its functions in -a lazy way. This means that even though you can call a function, it does not -mean it is executed right away. This is true for a great deal of functions in -this class. Lazy evaluation allows you to save resources in situations -where you don't use all the values in a collection. You might not use all the -values when iteration stops early, or when an exception/failure case is reached -early. - -Additionally, lazy evaluation helps speed up some operations. Consider the -following example:: - - $collection = new Collection($oneMillionItems); - $collection = $collection->map(function ($item) { - return $item * 2; - }); - $itemsToShow = $collection->take(30); - -Had the collections not been lazy, we would have executed one million operations, -even though we only wanted to show 30 elements out of it. Instead, our map -operation was only applied to the 30 elements we used. We can also -derive benefits from this lazy evaluation for smaller collections when we -do more than one operation on them. For example: calling ``map()`` twice and -then ``filter()``. - -Lazy evaluation comes with its downside too. You could be doing the same -operations more than once if you optimize a collection prematurely. Consider -this example:: - - $ages = $collection->extract('age'); - - $youngerThan30 = $ages->filter(function ($item) { - return $item < 30; - }); - - $olderThan30 = $ages->filter(function ($item) { - return $item > 30; - }); - -If we iterate both ``youngerThan30`` and ``olderThan30``, the collection would -unfortunately execute the ``extract()`` operation twice. This is because -collections are immutable and the lazy-extracting operation would be done for -both filters. - -Luckily we can overcome this issue with a single function. If you plan to reuse -the values from certain operations more than once, you can compile the results -into another collection using the ``buffered()`` function:: - - $ages = $collection->extract('age')->buffered(); - $youngerThan30 = ... - $olderThan30 = ... - -Now, when both collections are iterated, they will only call the -extracting operation once. - -Making Collections Rewindable ------------------------------ - -The ``buffered()`` method is also useful for converting non-rewindable iterators -into collections that can be iterated more than once:: - - // In PHP 5.5+ - public function results() - { - ... - foreach ($transientElements as $e) { - yield $e; - } - } - $rewindable = (new Collection(results()))->buffered(); - -Cloning Collections -------------------- - -.. php:method:: compile($preserveKeys = true) - -Sometimes you need to get a clone of the elements from another -collection. This is useful when you need to iterate the same set from different -places at the same time. In order to clone a collection out of another use the -``compile()`` method:: - - $ages = $collection->extract('age')->compile(); - - foreach ($ages as $age) { - foreach ($collection as $element) { - echo h($element->name) . ' - ' . $age; - } - } - -.. meta:: - :title lang=en: Collections - :keywords lang=en: collections, cakephp, append, sort, compile, contains, countBy, each, every, extract, filter, first, firstMatch, groupBy, indexBy, jsonSerialize, map, match, max, min, reduce, reject, sample, shuffle, some, random, sortBy, take, toArray, insert, sumOf, stopWhen, unfold, through diff --git a/en/core-libraries/email.rst b/en/core-libraries/email.rst deleted file mode 100644 index 2256268e8d..0000000000 --- a/en/core-libraries/email.rst +++ /dev/null @@ -1,665 +0,0 @@ -Mailer -###### - -.. php:namespace:: Cake\Mailer - -.. php:class:: Mailer(string|array|null $profile = null) - -``Mailer`` is a convenience class for sending email. With this class you can send -email from any place inside of your application. - -Basic Usage -=========== - -First of all, you should ensure the class is loaded:: - - use Cake\Mailer\Mailer; - -After you've loaded ``Mailer``, you can send an email with the following:: - - $mailer = new Mailer('default'); - $mailer->setFrom(['me@example.com' => 'My Site']) - ->setTo('you@example.com') - ->setSubject('About') - ->deliver('My message'); - -Since ``Mailer``'s setter methods return the instance of the class, you are able -to set its properties with method chaining. - -``Mailer`` has several methods for defining recipients - ``setTo()``, ``setCc()``, -``setBcc()``, ``addTo()``, ``addCc()`` and ``addBcc()``. The main difference being -that the first three will overwrite what was already set and the latter will just -add more recipients to their respective field:: - - $mailer = new Mailer(); - $mailer->setTo('to@example.com', 'To Example'); - $mailer->addTo('to2@example.com', 'To2 Example'); - // The email's To recipients are: to@example.com and to2@example.com - $mailer->setTo('test@example.com', 'ToTest Example'); - // The email's To recipient is: test@example.com - -Choosing the Sender -------------------- - -When sending email on behalf of other people, it's often a good idea to define the -original sender using the Sender header. You can do so using ``setSender()``:: - - $mailer = new Mailer(); - $mailer->setSender('app@example.com', 'MyApp emailer'); - -.. note:: - - It's also a good idea to set the envelope sender when sending mail on another - person's behalf. This prevents them from getting any messages about - deliverability. - -.. _email-configuration: - -Configuration -============= - -Mailer profiles and email transport settings are defined in your application's -configuration files. The ``Email`` and ``EmailTransport`` keys define mailer -profiles and email transport configurations respectively. During application -bootstrap configuration settings are passed from ``Configure`` into the -``Mailer`` and ``TransportFactory`` classes using ``setConfig()``. By defining -profiles and transports, you can keep your application code free of -configuration data, and avoid duplication that makes maintenance and deployment -more difficult. - -To load a predefined configuration, you can use the ``setProfile()`` method or -pass it to the constructor of ``Mailer``:: - - $mailer = new Mailer(); - $mailer->setProfile('default'); - - // Or in constructor - $mailer = new Mailer('default'); - -Instead of passing a string which matches a preset configuration name, you can -also just load an array of options:: - - $mailer = new Mailer(); - $mailer->setProfile(['from' => 'me@example.org', 'transport' => 'my_custom']); - - // Or in constructor - $mailer = new Mailer(['from' => 'me@example.org', 'transport' => 'my_custom']); - -.. _email-configurations: - -Configuration Profiles ----------------------- - -Defining delivery profiles allows you to consolidate common email settings into -re-usable profiles. Your application can have as many profiles as necessary. The -following configuration keys are used: - -- ``'from'``: Mailer or array of sender. See ``Mailer::setFrom()``. -- ``'sender'``: Mailer or array of real sender. See ``Mailer::setSender()``. -- ``'to'``: Mailer or array of destination. See ``Mailer::setTo()``. -- ``'cc'``: Mailer or array of carbon copy. See ``Mailer::setCc()``. -- ``'bcc'``: Mailer or array of blind carbon copy. See ``Mailer::setBcc()``. -- ``'replyTo'``: Mailer or array to reply the e-mail. See ``Mailer::setReplyTo()``. -- ``'readReceipt'``: Mailer address or an array of addresses to receive the - receipt of read. See ``Mailer::setReadReceipt()``. -- ``'returnPath'``: Mailer address or an array of addresses to return if have - some error. See ``Mailer::setReturnPath()``. -- ``'messageId'``: Message ID of e-mail. See ``Mailer::setMessageId()``. -- ``'subject'``: Subject of the message. See ``Mailer::setSubject()``. -- ``'message'``: Content of message. Do not set this field if you are using rendered content. -- ``'priority'``: Priority of the email as numeric value (usually from 1 to 5 with 1 being the highest). -- ``'headers'``: Headers to be included. See ``Mailer::setHeaders()``. -- ``'viewRenderer'``: If you are using rendered content, set the view classname. - See ``ViewBuilder::setClassName()``. -- ``'template'``: If you are using rendered content, set the template name. See - ``ViewBuilder::setTemplate()``. -- ``'theme'``: Theme used when rendering template. See ``ViewBuilder::setTheme()``. -- ``'layout'``: If you are using rendered content, set the layout to render. See - ``ViewBuilder::setTemplate()``. -- ``'autoLayout'``: If you want to render a template without layout, set this field to - ``false``. See ``ViewBuilder::disableAutoLayout()``. -- ``'viewVars'``: If you are using rendered content, set the array with - variables to be used in the view. See ``Mailer::setViewVars()``. -- ``'attachments'``: List of files to attach. See ``Mailer::setAttachments()``. -- ``'emailFormat'``: Format of email (html, text or both). See ``Mailer::setEmailFormat()``. -- ``'transport'``: Transport configuration name. See :ref:`email-transport`. -- ``'log'``: Log level to log the email headers and message. ``true`` will use - LOG_DEBUG. See :ref:`logging-levels`. Note that logs will be emitted under the scope named ``email``. - See also :ref:`logging-scopes`. -- ``'helpers'``: Array of helpers used in the email template. - ``ViewBuilder::setHelpers()``/``ViewBuilder::addHelpers()``. - -.. note:: - - The values of above keys using Mailer or array, like from, to, cc, etc will be passed - as first parameter of corresponding methods. The equivalent for: - ``$mailer->setFrom('my@example.com', 'My Site')`` - would be defined as ``'from' => ['my@example.com' => 'My Site']`` in your config - -Setting Headers -=============== - -In ``Mailer`` you are free to set whatever headers you want. Do not forget to -put the ``X-`` prefix for your custom headers. - -See ``Mailer::setHeaders()`` and ``Mailer::addHeaders()`` - -Sending Templated Emails -========================== - -Emails are often much more than just a simple text message. In order -to facilitate that, CakePHP provides a way to send emails using CakePHP's -:doc:`view layer `. - -The templates for emails reside in a special folder ``templates/email`` of your -application. Mailer views can also use layouts and elements just like normal views:: - - $mailer = new Mailer(); - $mailer - ->setEmailFormat('html') - ->setTo('bob@example.com') - ->setFrom('app@domain.com') - ->viewBuilder() - ->setTemplate('welcome') - ->setLayout('fancy'); - - $mailer->deliver(); - -The above would use **templates/email/html/welcome.php** for the view -and **templates/layout/email/html/fancy.php** for the layout. You can -send multipart templated email messages as well:: - - $mailer = new Mailer(); - $mailer - ->setEmailFormat('both') - ->setTo('bob@example.com') - ->setFrom('app@domain.com') - ->viewBuilder() - ->setTemplate('welcome') - ->setLayout('fancy'); - - $mailer->deliver(); - -This would use the following template files: - -* **templates/email/text/welcome.php** -* **templates/layout/email/text/fancy.php** -* **templates/email/html/welcome.php** -* **templates/layout/email/html/fancy.php** - -When sending templated emails you have the option of sending either -``text``, ``html`` or ``both``. - -You can set all view related config using the view builder instance got by -``Mailer::viewBuilder()`` similar to how you do the same in controller. - -You can set view variables with ``Mailer::setViewVars()``:: - - $mailer = new Mailer('templated'); - $mailer->setViewVars(['value' => 12345]); - -Or you can use the view builder methods ``ViewBuilder::setVar()`` and -``ViewBuilder::setVars()``. - -In your email templates you can use these with:: - -

      Here is your value:

      - -You can use helpers in emails as well, much like you can in normal template files. -By default only the ``HtmlHelper`` is loaded. You can load additional -helpers using the ``ViewBuilder::addHelpers()`` method:: - - $mailer->viewBuilder()->addHelpers(['Html', 'Custom', 'Text']); - -When adding helpers be sure to include 'Html' or it will be removed from the -helpers loaded in your email template. - -.. note:: - In versions prior to 4.3.0, you will need to use ``setHelpers()`` instead. - -If you want to send email using templates in a plugin you can use the familiar -:term:`plugin syntax` to do so:: - - $mailer = new Mailer(); - $mailer->viewBuilder()->setTemplate('Blog.new_comment'); - -The above would use template and layout from the Blog plugin as an example. - -In some cases, you might need to override the default template provided by plugins. -You can do this using themes:: - - $mailer->viewBuilder() - ->setTemplate('Blog.new_comment') - ->setLayout('Blog.auto_message') - ->setTheme('TestTheme'); - -This allows you to override the ``new_comment`` template in your theme without -modifying the Blog plugin. The template file needs to be created in the -following path: -**templates/plugin/TestTheme/plugin/Blog/email/text/new_comment.php**. - -Sending Attachments -=================== - -.. php:method:: setAttachments($attachments) - -You can attach files to email messages as well. There are a few -different formats depending on what kind of files you have, and how -you want the filenames to appear in the recipient's mail client: - -1. Array: ``$mailer->setAttachments(['/full/file/path/file.png'])`` will - attach this file with the name file.png.. -2. Array with key: - ``$mailer->setAttachments(['photo.png' => '/full/some_hash.png'])`` will - attach some_hash.png with the name photo.png. The recipient will see - photo.png, not some_hash.png. -3. Nested arrays:: - - $mailer->setAttachments([ - 'photo.png' => [ - 'file' => '/full/some_hash.png', - 'mimetype' => 'image/png', - 'contentId' => 'my-unique-id', - ], - ]); - - The above will attach the file with different mimetype and with custom - Content ID (when set the content ID the attachment is transformed to inline). - The mimetype and contentId are optional in this form. - - 3.1. When you are using the ``contentId``, you can use the file in the HTML - body like ````. - - 3.2. You can use the ``contentDisposition`` option to disable the - ``Content-Disposition`` header for an attachment. This is useful when - sending ical invites to clients using outlook. - - 3.3 Instead of the ``file`` option you can provide the file contents as - a string using the ``data`` option. This allows you to attach files without - needing file paths to them. - -Relaxing Address Validation Rules ---------------------------------- - -.. php:method:: setEmailPattern($pattern) - -If you are having validation issues when sending to non-compliant addresses, you -can relax the pattern used to validate email addresses. This is sometimes -necessary when dealing with some ISPs:: - - $mailer = new Mailer('default'); - - // Relax the email pattern, so you can send - // to non-conformant addresses. - $mailer->setEmailPattern($newPattern); - -Sending Emails from CLI -======================== - -When sending emails within a CLI script (Shells, Tasks, ...) you should manually -set the domain name for Mailer to use. It will serve as the host name for the -message id (since there is no host name in a CLI environment):: - - $mailer->setDomain('www.example.org'); - // Results in message ids like ```` (valid) - // Instead of ``` (invalid) - -A valid message id can help to prevent emails ending up in spam folders. - -Creating Reusable Emails -========================= - -Until now we have seen how to directly use the the ``Mailer`` class to create and -send one emails. But main feature of mailer is to allow creating reusable emails -throughout your application. They can also be used to contain multiple email -configurations in one location. This helps keep your code DRYer and keeps email -configuration noise out of other areas in your application. - -In this example we will be creating a ``Mailer`` that contains user-related -emails. To create our ``UserMailer``, create the file -**src/Mailer/UserMailer.php**. The contents of the file should look like the -following:: - - namespace App\Mailer; - - use Cake\Mailer\Mailer; - - class UserMailer extends Mailer - { - public function welcome($user) - { - $this - ->setTo($user->email) - ->setSubject(sprintf('Welcome %s', $user->name)) - ->viewBuilder() - ->setTemplate('welcome_mail'); // By default template with same name as method name is used. - } - - public function resetPassword($user) - { - $this - ->setTo($user->email) - ->setSubject('Reset password') - ->setViewVars(['token' => $user->token]); - } - } - -In our example we have created two methods, one for sending a welcome email, and -another for sending a password reset email. Each of these methods expect a user -``Entity`` and utilizes its properties for configuring each email. - -We are now able to use our ``UserMailer`` to send out our user-related emails -from anywhere in our application. For example, if we wanted to send our welcome -email we could do the following:: - - namespace App\Controller; - - use Cake\Mailer\MailerAwareTrait; - - class UsersController extends AppController - { - use MailerAwareTrait; - - public function register() - { - $user = $this->Users->newEmptyEntity(); - if ($this->request->is('post')) { - $user = $this->Users->patchEntity($user, $this->request->getData()) - if ($this->Users->save($user)) { - $this->getMailer('User')->send('welcome', [$user]); - } - } - $this->set('user', $user); - } - } - -If we wanted to completely separate sending a user their welcome email from our -application's code, we can have our ``UserMailer`` subscribe to the -``Model.afterSave`` event. By subscribing to an event, we can keep our -application's user-related classes completely free of email-related logic and -instructions. For example, we could add the following to our ``UserMailer``:: - - public function implementedEvents() - { - return [ - 'Model.afterSave' => 'onRegistration', - ]; - } - - public function onRegistration(EventInterface $event, EntityInterface $entity, ArrayObject $options) - { - if ($entity->isNew()) { - $this->send('welcome', [$entity]); - } - } - -You can now register the mailer as an event listener and the -``onRegistration()`` method will be invoked every time the ``Model.afterSave`` -event is fired:: - - // attach to Users event manager - $this->Users->getEventManager()->on($this->getMailer('User')); - -.. note:: - - For information on how to register event listener objects, - please refer to the :ref:`registering-event-listeners` documentation. - -.. _email-transport: - -Configuring Transports -====================== - -Email messages are delivered by transports. Different transports allow you to -send messages via PHP's ``mail()`` function, SMTP servers, or not at all which -is useful for debugging. Configuring transports allows you to keep configuration -data out of your application code and makes deployment simpler as you can simply -change the configuration data. An example transport configuration looks like:: - - // In config/app.php - 'EmailTransport' => [ - // Sample Mail configuration - 'default' => [ - 'className' => 'Mail', - ], - // Sample SMTP configuration - 'gmail' => [ - 'host' => 'smtp.gmail.com', - 'port' => 587, - 'username' => 'my@gmail.com', - 'password' => 'secret', - 'className' => 'Smtp', - 'tls' => true, - ], - ], - -Transports can also be configured at runtime using -``TransportFactory::setConfig()``:: - - use Cake\Mailer\TransportFactory; - - // Define an SMTP transport - TransportFactory::setConfig('gmail', [ - 'host' => 'ssl://smtp.gmail.com', - 'port' => 465, - 'username' => 'my@gmail.com', - 'password' => 'secret', - 'className' => 'Smtp' - ]); - -You can configure SSL SMTP servers, like Gmail. To do so, put the ``ssl://`` -prefix in the host and configure the port value accordingly. You can also -enable TLS SMTP using the ``tls`` option:: - - use Cake\Mailer\TransportFactory; - - TransportFactory::setConfig('gmail', [ - 'host' => 'smtp.gmail.com', - 'port' => 587, - 'username' => 'my@gmail.com', - 'password' => 'secret', - 'className' => 'Smtp', - 'tls' => true - ]); - -The above configuration would enable TLS communication for email messages. - -To configure your mailer to use a specific transport you can use -:php:meth:`Cake\\Mailer\\Mailer::setTransport()` method or have the transport -in your configuration:: - - // Use a named transport already configured using TransportFactory::setConfig() - $mailer->setTransport('gmail'); - - // Use a constructed object. - $mailer->setTransport(new \Cake\Mailer\Transport\DebugTransport()); - -.. warning:: - You will need to have access for less secure apps enabled in your Google - account for this to work: - `Allowing less secure apps to access your - account `__. - -.. note:: -   `Gmail SMTP settings `__. - -.. note:: - To use SSL + SMTP, you will need to have the SSL configured in your PHP - install. - -Configuration options can also be provided as a :term:`DSN` string. This is -useful when working with environment variables or :term:`PaaS` providers:: - - TransportFactory::setConfig('default', [ - 'url' => 'smtp://my@gmail.com:secret@smtp.gmail.com:587?tls=true', - ]); - -When using a DSN string you can define any additional parameters/options as -query string arguments. - -.. php:staticmethod:: drop($key) - -Once configured, transports cannot be modified. In order to modify a transport -you must first drop it and then reconfigure it. - -Creating Custom Transports --------------------------- - -You are able to create your custom transports for situations such as send email using services -like SendGrid, MailGun or Postmark. To create your transport, first create the file -**src/Mailer/Transport/ExampleTransport.php** (where Example is the name of your -transport). To start, your file should look like:: - - namespace App\Mailer\Transport; - - use Cake\Mailer\AbstractTransport; - use Cake\Mailer\Message; - - class ExampleTransport extends AbstractTransport - { - public function send(Message $message): array - { - // Do something. - } - } - -You must implement the method ``send(Message $message)`` with your custom logic. - -Sending emails without using Mailer -=================================== - -The ``Mailer`` is a higher level abstraction class which acts as a bridge between -the ``Cake\Mailer\Message``, ``Cake\Mailer\Renderer`` and ``Cake\Mailer\AbstractTransport`` -classes to configure emails with a fluent interface. - -If you want you can use these classes directly with the ``Mailer`` too. - -For example:: - - $render = new \Cake\Mailer\Renderer(); - $render->viewBuilder() - ->setTemplate('custom') - ->setLayout('sparkly'); - - $message = new \Cake\Mailer\Message(); - $message - ->setFrom('admin@cakephp.org') - ->setTo('user@foo.com') - ->setBody($render->render()); - - $transport = new \Cake\Mailer\Transport\MailTransport(); - $result = $transport->send($message); - -You can even skip using the ``Renderer`` and set the message body directly -using ``Message::setBodyText()`` and ``Message::setBodyHtml()`` methods. - -.. _email-testing: - -Testing Mailers -=============== - -To test mailers, add ``Cake\TestSuite\EmailTrait`` to your test case. -The ``MailerTrait`` uses PHPUnit hooks to replace your application's email transports -with a proxy that intercepts email messages and allows you to do assertions -on the mail that would be delivered. - -Add the trait to your test case to start testing emails, and load routes if your -emails need to generate URLs:: - - namespace App\Test\TestCase\Mailer; - - use App\Mailer\WelcomeMailer; - use App\Model\Entity\User; - - use Cake\TestSuite\EmailTrait; - use Cake\TestSuite\TestCase; - - class WelcomeMailerTestCase extends TestCase - { - use EmailTrait; - - public function setUp(): void - { - parent::setUp(); - $this->loadRoutes(); - } - } - -Let's assume we have a mailer that delivers welcome emails when a new user -registers. We want to check that the subject and body contain the user's name:: - - // in our WelcomeMailerTestCase class. - public function testName() - { - $user = new User([ - 'name' => 'Alice Alittea', - 'email' => 'alice@example.org', - ]); - $mailer = new WelcomeMailer(); - $mailer->send('welcome', [$user]); - - $this->assertMailSentTo($user->email); - $this->assertMailContainsText('Hi ' . $user->name); - $this->assertMailContainsText('Welcome to CakePHP!'); - } - -Assertion methods ------------------ - -The ``Cake\TestSuite\EmailTrait`` trait provides the following assertions:: - - // Asserts an expected number of emails were sent - $this->assertMailCount($count); - - // Asserts that no emails were sent - $this->assertNoMailSent(); - - // Asserts an email was sent to an address - $this->assertMailSentTo($address); - - // Asserts an email was sent from an address - $this->assertMailSentFrom($emailAddress); - $this->assertMailSentFrom([$emailAddress => $displayName]); - - // Asserts an email contains expected contents - $this->assertMailContains($contents); - - // Asserts an email contains expected html contents - $this->assertMailContainsHtml($contents); - - // Asserts an email contains expected text contents - $this->assertMailContainsText($contents); - - // Asserts an email contains the expected value within an Message getter (for example, "subject") - $this->assertMailSentWith($expected, $parameter); - - // Asserts an email at a specific index was sent to an address - $this->assertMailSentToAt($at, $address); - - // Asserts an email at a specific index was sent from an address - $this->assertMailSentFromAt($at, $address); - - // Asserts an email at a specific index contains expected contents - $this->assertMailContainsAt($at, $contents); - - // Asserts an email at a specific index contains expected html contents - $this->assertMailContainsHtmlAt($at, $contents); - - // Asserts an email at a specific index contains expected text contents - $this->assertMailContainsTextAt($at, $contents); - - // Asserts an email contains an attachment - $this->assertMailContainsAttachment('test.png'); - - // Asserts an email at a specific index contains the expected value within an Message getter (for example, "cc") - $this->assertMailSentWithAt($at, $expected, $parameter); - - // Asserts an email contains a substring in the subject. - $this->assertMailSubjectContains('Free Offer'); - - // Asserts an email at the specific index contains a substring in the subject. - $this->assertMailSubjectContainsAt(1, 'Free Offer'); - -.. meta:: - :title lang=en: Email - :keywords lang=en: sending mail,email sender,envelope sender,php class,database configuration,sending emails,commands,smtp,transports,attributes,array,config,flexibility,php email,new email,sending email,models diff --git a/en/core-libraries/events.rst b/en/core-libraries/events.rst deleted file mode 100644 index 889f040074..0000000000 --- a/en/core-libraries/events.rst +++ /dev/null @@ -1,600 +0,0 @@ -Events System -############# - -Creating maintainable applications is both a science and an art. It is -well-known that a key for having good quality code is making your objects -loosely coupled and strongly cohesive at the same time. Cohesion means that -all methods and properties for a class are strongly related to the class -itself and it is not trying to do the job other objects should be doing, -while loosely coupling is the measure of how little a class is "wired" -to external objects, and how much that class is depending on them. - -There are certain cases where you need to cleanly communicate with other parts -of an application, without having to hard code dependencies, thus losing -cohesion and increasing class coupling. Using the Observer pattern, which allows -objects to notify other objects and anonymous listeners about changes is -a useful pattern to achieve this goal. - -Listeners in the observer pattern can subscribe to events and choose to act upon -them if they are relevant. If you have used JavaScript, there is a good chance -that you are already familiar with event driven programming. - -CakePHP emulates several aspects of how events are triggered and managed in -popular JavaScript libraries such as jQuery. In the CakePHP implementation, an -event object is dispatched to all listeners. The event object holds information -about the event, and provides the ability to stop event propagation at any -point. Listeners can register themselves or can delegate this task to other -objects and have the chance to alter the state and the event itself for the rest -of the callbacks. - -The event subsystem is at the heart of Model, Behavior, Controller, View and -Helper callbacks. If you've ever used any of them, you are already somewhat -familiar with events in CakePHP. - -Example Event Usage -=================== - -Let's suppose you are building a Cart plugin, and you'd like to focus on just -handling order logic. You don't really want to include shipping logic, emailing -the user or decrementing the item from the stock, but these are important tasks -to the people using your plugin. If you were not using events, you may try to -implement this by attaching behaviors to models, or adding components to your -controllers. Doing so represents a challenge most of the time, since you -would have to come up with the code for externally loading those behaviors or -attaching hooks to your plugin controllers. - -Instead, you can use events to allow you to cleanly separate the concerns of -your code and allow additional concerns to hook into your plugin using events. -For example, in your Cart plugin you have an Orders model that deals with -creating orders. You'd like to notify the rest of the application that an order -has been created. To keep your Orders model clean you could use events:: - - // Cart/Model/Table/OrdersTable.php - namespace Cart\Model\Table; - - use Cake\Event\Event; - use Cake\ORM\Table; - - class OrdersTable extends Table - { - public function place($order) - { - if ($this->save($order)) { - $this->Cart->remove($order); - $event = new Event('Order.afterPlace', $this, [ - 'order' => $order - ]); - $this->getEventManager()->dispatch($event); - - return true; - } - - return false; - } - } - -The above code allows you to notify the other parts of the application -that an order has been created. You can then do tasks like send email -notifications, update stock, log relevant statistics and other tasks in separate -objects that focus on those concerns. - -Accessing Event Managers -======================== - -In CakePHP events are triggered against event managers. Event managers are -available in every Table, View and Controller using ``getEventManager()``:: - - $events = $this->getEventManager(); - -Each model has a separate event manager, while the View and Controller -share one. This allows model events to be self contained, and allow components -or controllers to act upon events created in the view if necessary. - -Global Event Manager --------------------- - -In addition to instance level event managers, CakePHP provides a global event -manager that allows you to listen to any event fired in an application. This is -useful when attaching listeners to a specific instance might be cumbersome or -difficult. The global manager is a singleton instance of -:php:class:`Cake\\Event\\EventManager`. Listeners attached to the global -dispatcher will be fired before instance listeners at the same priority. You can -access the global manager using a static method:: - - // In any configuration file or piece of code that executes before the event - use Cake\Event\EventManager; - - EventManager::instance()->on( - 'Order.afterPlace', - $aCallback - ); - -One important thing you should consider is that there are events that will be -triggered having the same name but different subjects, so checking it in the -event object is usually required in any function that gets attached globally in -order to prevent some bugs. Remember that with the flexibility of using the -global manager, some additional complexity is incurred. - -:php:meth:`Cake\\Event\\EventManager::dispatch()` method accepts the event -object as an argument and notifies all listener and callbacks passing this -object along. The listeners will handle all the extra logic around the -``afterPlace`` event, you can log the time, send emails, update user statistics -possibly in separate objects and even delegating it to offline tasks if you have -the need. - -.. _tracking-events: - -Tracking Events ---------------- - -To keep a list of events that are fired on a particular ``EventManager``, you -can enable event tracking. To do so, simply attach an -:php:class:`Cake\\Event\\EventList` to the manager:: - - EventManager::instance()->setEventList(new EventList()); - -After firing an event on the manager, you can retrieve it from the event list:: - - $eventsFired = EventManager::instance()->getEventList(); - $firstEvent = $eventsFired[0]; - -Tracking can be disabled by removing the event list or calling -:php:meth:`Cake\\Event\\EventList::trackEvents(false)`. - -Core Events -=========== - -There are a number of core events within the framework which your application -can listen to. Each layer of CakePHP emits events that you can use in your -application. - -* :ref:`ORM/Model events ` -* :ref:`Controller events ` -* :ref:`View events ` - -``Server.terminate`` --------------------- - -The ``Server.terminate`` event is triggered after the response has been sent to the -client. This event is useful for performing tasks that should be done after the -response has been sent, such as logging or sending emails. - -You can listen to this event using an event manager instance:: - - use Cake\Event\EventManager; - - EventManager::instance()->on('Server.terminate', function ($event) { - // Perform tasks that should be done after the response has been - // sent to the client. - }); - -Or using the ``events`` hook in your Application/Plugin class:: - - use Cake\Event\EventManagerInterface; - - public function events(EventManagerInterface $eventManager): EventManagerInterface - { - $eventManager->on('Server.terminate', function ($event) { - // Perform tasks that should be done after the response has been - // sent to the client. - }); - - return $eventManager; - } - -.. tip:: - This is called even if an exception is thrown during the request, e.g. on 404 pages. - -.. note:: - The ``Server.terminate`` event only works for PHP-FPM implementations which - support the ``fastcgi_finish_request`` function. - -.. _registering-event-listeners: - -Registering Listeners -===================== - -Listeners are the preferred way to register callbacks for an event. This is done -by implementing the :php:class:`Cake\\Event\\EventListenerInterface` interface -in any class you wish to register some callbacks. Classes implementing it need -to provide the ``implementedEvents()`` method. This method must return an -associative array with all event names that the class will handle. - -To continue our previous example, let's imagine we have a UserStatistic class -responsible for calculating a user's purchasing history, and compiling into -global site statistics. This is a great place to use a listener class. Doing so -allows you to concentrate the statistics logic in one place and react to events -as necessary. Our ``UserStatistics`` listener might start out like:: - - namespace App\Event; - - use Cake\Event\EventListenerInterface; - - class UserStatistic implements EventListenerInterface - { - public function implementedEvents(): array - { - return [ - // Custom event names let you design your application events - // as required. - 'Order.afterPlace' => 'updateBuyStatistic', - ]; - } - - public function updateBuyStatistic($event) - { - // Code to update statistics - } - } - - // From your controller, attach the UserStatistic object to the Order's event manager - $statistics = new UserStatistic(); - $this->Orders->getEventManager()->on($statistics); - -As you can see in the above code, the ``on()`` function will accept instances -of the ``EventListener`` interface. Internally, the event manager will use -``implementedEvents()`` to attach the correct callbacks. - -.. versionadded:: 5.1.0 - The ``events`` hook was added to the ``BaseApplication`` as well as the ``BasePlugin`` class - -As of CakePHP 5.1 it is recommended to register event listeners by adding them via the ``events`` hook in your application or plugin class:: - - namespace App; - - use App\Event\UserStatistic; - use Cake\Event\EventManagerInterface; - use Cake\Http\BaseApplication; - - class Application extends BaseApplication - { - // The rest of your Application class - - public function events(EventManagerInterface $eventManager): EventManagerInterface - { - $statistics = new UserStatistic(); - $eventManager->on($statistics); - - return $eventManager; - } - } - -Registering Anonymous Listeners -------------------------------- - -While event listener objects are generally a better way to implement listeners, -you can also bind any ``callable`` as an event listener. For example if we -wanted to put any orders into the log files, we could use a simple anonymous -function to do so:: - - use Cake\Log\Log; - - // From within a controller, or during application bootstrap. - $this->Orders->getEventManager()->on('Order.afterPlace', function ($event) { - Log::write( - 'info', - 'A new order was placed with id: ' . $event->getSubject()->id - ); - }); - -In addition to anonymous functions you can use any other callable type that PHP -supports:: - - $events = [ - 'email-sending' => 'EmailSender::sendBuyEmail', - 'inventory' => [$this->InventoryManager, 'decrement'], - ]; - foreach ($events as $callable) { - $eventManager->on('Order.afterPlace', $callable); - } - -When working with plugins that don't trigger specific events, you can leverage -event listeners on the default events. Lets take an example 'UserFeedback' -plugin which handles feedback forms from users. From your application you would -like to know when a Feedback record has been saved and ultimately act on it. You -can listen to the global ``Model.afterSave`` event. However, you can take -a more direct approach and only listen to the event you really need:: - - // You can create the following before the - // save operation, ie. config/bootstrap.php - use Cake\Datasource\FactoryLocator; - // If sending emails - use Cake\Mailer\Email; - - FactoryLocator::get('Table')->get('ThirdPartyPlugin.Feedbacks') - ->getEventManager() - ->on('Model.afterSave', function($event, $entity) - { - // For example we can send an email to the admin - $email = new Email('default'); - $email->setFrom(['info@yoursite.com' => 'Your Site']) - ->setTo('admin@yoursite.com') - ->setSubject('New Feedback - Your Site') - ->send('Body of message'); - }); - -You can use this same approach to bind listener objects. - -Interacting with Existing Listeners ------------------------------------ - -Assuming several event listeners have been registered the presence or absence -of a particular event pattern can be used as the basis of some action.:: - - // Attach listeners to EventManager. - $this->getEventManager()->on('User.Registration', [$this, 'userRegistration']); - $this->getEventManager()->on('User.Verification', [$this, 'userVerification']); - $this->getEventManager()->on('User.Authorization', [$this, 'userAuthorization']); - - // Somewhere else in your application. - $events = $this->getEventManager()->matchingListeners('Verification'); - if (!empty($events)) { - // Perform logic related to presence of 'Verification' event listener. - // For example removing the listener if present. - $this->getEventManager()->off('User.Verification'); - } else { - // Perform logic related to absence of 'Verification' event listener - } - -.. note:: - - The pattern passed to the ``matchingListeners`` method is case sensitive. - -.. _event-priorities: - -Establishing Priorities ------------------------ - -In some cases you might want to control the order that listeners are invoked. -For instance, if we go back to our user statistics example. It would be ideal if -this listener was called at the end of the stack. By calling it at the end of -the listener stack, we can ensure that the event was not cancelled, and that no -other listeners raised exceptions. We can also get the final state of the -objects in the case that other listeners have modified the subject or event -object. - -Priorities are defined as an integer when adding a listener. The higher the -number, the later the method will be fired. The default priority for all -listeners is ``10``. If you need your method to be run earlier, using any value -below this default will work. On the other hand if you desire to run the -callback after the others, using a number above ``10`` will do. - -If two callbacks happen to have the same priority value, they will be executed -with a the order they were attached. You set priorities using the ``on()`` -method for callbacks, and declaring it in the ``implementedEvents()`` function -for event listeners:: - - // Setting priority for a callback - $callback = [$this, 'doSomething']; - $this->getEventManager()->on( - 'Order.afterPlace', - ['priority' => 2], - $callback - ); - - // Setting priority for a listener - class UserStatistic implements EventListenerInterface - { - public function implementedEvents() - { - return [ - 'Order.afterPlace' => [ - 'callable' => 'updateBuyStatistic', - 'priority' => 100 - ], - ]; - } - } - -As you see, the main difference for ``EventListener`` objects is that you need -to use an array for specifying the callable method and the priority preference. -The ``callable`` key is a special array entry that the manager will read to know -what function in the class it should be calling. - -Getting Event Data as Function Parameters ------------------------------------------ - -When events have data provided in their constructor, the provided data is -converted into arguments for the listeners. An example from the View layer is -the afterRender callback:: - - $this->getEventManager() - ->dispatch(new Event('View.afterRender', $this, ['view' => $viewFileName])); - -The listeners of the ``View.afterRender`` callback should have the following -signature:: - - function (EventInterface $event, $viewFileName) - -Each value provided to the Event constructor will be converted into function -parameters in the order they appear in the data array. If you use an associative -array, the result of ``array_values`` will determine the function argument -order. - -.. note:: - - Unlike in 2.x, converting event data to listener arguments is the default - behavior and cannot be disabled. - -Dispatching Events -================== - -Once you have obtained an instance of an event manager you can dispatch events -using :php:meth:`~Cake\\Event\\EventManager::dispatch()`. This method takes an -instance of the :php:class:`Cake\\Event\\Event` class. Let's look at dispatching -an event:: - - // An event listener has to be instantiated before dispatching an event. - // Create a new event and dispatch it. - $event = new Event('Order.afterPlace', $this, [ - 'order' => $order - ]); - $this->getEventManager()->dispatch($event); - -:php:class:`Cake\\Event\\Event` accepts 3 arguments in its constructor. The -first one is the event name, you should try to keep this name as unique as -possible, while making it readable. We suggest a convention as follows: -``Layer.eventName`` for general events happening at a layer level (for example, -``Controller.startup``, ``View.beforeRender``) and ``Layer.Class.eventName`` for -events happening in specific classes on a layer, for example -``Model.User.afterRegister`` or ``Controller.Courses.invalidAccess``. - -The second argument is the ``subject``, meaning the object associated to the -event, usually when it is the same class triggering events about itself, using -``$this`` will be the most common case. Although a Component could trigger -controller events too. The subject class is important because listeners will get -immediate access to the object properties and have the chance to inspect or -change them on the fly. - -Finally, the third argument is any additional event data. This can be any data -you consider useful to pass around so listeners can act upon it. While this can -be an argument of any type, we recommend passing an associative array. - -The :php:meth:`~Cake\\Event\\EventManager::dispatch()` method accepts an event -object as an argument and notifies all subscribed listeners. - -.. _stopping-events: - -Stopping Events ---------------- - -Much like DOM events, you may want to stop an event to prevent additional -listeners from being notified. You can see this in action during model callbacks -(for example, beforeSave) in which it is possible to stop the saving operation if -the code detects it cannot proceed any further. - -In order to stop events you can either return ``false`` in your callbacks or -call the ``stopPropagation()`` method on the event object:: - - public function doSomething($event) - { - // ... - return false; // Stops the event - } - - public function updateBuyStatistic($event) - { - // ... - $event->stopPropagation(); - } - -Stopping an event will prevent any additional callbacks from being called. -Additionally the code triggering the event may behave differently based on the -event being stopped or not. Generally it does not make sense to stop 'after' -events, but stopping 'before' events is often used to prevent the entire -operation from occurring. - -To check if an event was stopped, you call the ``isStopped()`` method in the -event object:: - - public function place($order) - { - $event = new Event('Order.beforePlace', $this, ['order' => $order]); - $this->getEventManager()->dispatch($event); - if ($event->isStopped()) { - return false; - } - if ($this->Orders->save($order)) { - // ... - } - // ... - } - -In the previous example the order would not get saved if the event is stopped -during the ``beforePlace`` process. - -Getting Event Results ---------------------- - -Every time a callback returns a non-null non-false value, it gets stored in the -``$result`` property of the event object. This is useful when you want to allow -callbacks to modify the event execution. Let's take again our ``beforePlace`` -example and let callbacks modify the ``$order`` data. - -Event results can be altered either using the event object result property -directly or returning the value in the callback itself:: - - // A listener callback - public function doSomething($event) - { - // ... - $alteredData = $event->getData('order') + $moreData; - - return $alteredData; - } - - // Another listener callback - public function doSomethingElse($event) - { - // ... - $event->setResult(['order' => $alteredData] + $this->result()); - } - - // Using the event result - public function place($order) - { - $event = new Event('Order.beforePlace', $this, ['order' => $order]); - $this->getEventManager()->dispatch($event); - if (!empty($event->getResult()['order'])) { - $order = $event->getResult()['order']; - } - if ($this->Orders->save($order)) { - // ... - } - // ... - } - -It is possible to alter any event object property and have the new data passed -to the next callback. In most of the cases, providing objects as event data or -result and directly altering the object is the best solution as the reference is -kept the same and modifications are shared across all callback calls. - -Removing Callbacks and Listeners --------------------------------- - -If for any reason you want to remove any callback from the event manager just -call the :php:meth:`Cake\\Event\\EventManager::off()` method using as -arguments the first two parameters you used for attaching it:: - - // Attaching a function - $this->getEventManager()->on('My.event', [$this, 'doSomething']); - - // Detaching the function - $this->getEventManager()->off('My.event', [$this, 'doSomething']); - - // Attaching an anonymous function. - $myFunction = function ($event) { ... }; - $this->getEventManager()->on('My.event', $myFunction); - - // Detaching the anonymous function - $this->getEventManager()->off('My.event', $myFunction); - - // Adding a EventListener - $listener = new MyEventLister(); - $this->getEventManager()->on($listener); - - // Detaching a single event key from a listener - $this->getEventManager()->off('My.event', $listener); - - // Detaching all callbacks implemented by a listener - $this->getEventManager()->off($listener); - -Events are a great way of separating concerns in your application and make -classes both cohesive and decoupled from each other. Events can be utilized to -de-couple application code and make extensible plugins. - -Keep in mind that with great power comes great responsibility. Using too many -events can make debugging harder and require additional integration testing. - -Additional Reading -================== - -* :doc:`/orm/behaviors` -* :doc:`/console-commands/commands` -* :doc:`/controllers/components` -* :doc:`/views/helpers` -* :ref:`testing-events` - -.. meta:: - :title lang=en: Events system - :keywords lang=en: events, dispatch, decoupling, cakephp, callbacks, triggers, hooks, php diff --git a/en/core-libraries/form.rst b/en/core-libraries/form.rst deleted file mode 100644 index d6c8d4c48f..0000000000 --- a/en/core-libraries/form.rst +++ /dev/null @@ -1,224 +0,0 @@ -Modelless Forms -############### - -.. php:namespace:: Cake\Form - -.. php:class:: Form - -Most of the time you will have forms backed by :doc:`ORM entities ` -and :doc:`ORM tables ` or other persistent stores, -but there are times when you'll need to validate user input and then perform an -action if the data is valid. The most common example of this is a contact form. - -Creating a Form -=============== - -Generally when using the Form class you'll want to use a subclass to define your -form. This makes testing easier, and lets you re-use your form. Forms are put -into **src/Form** and usually have ``Form`` as a class suffix. For example, -a simple contact form would look like:: - - // in src/Form/ContactForm.php - namespace App\Form; - - use Cake\Form\Form; - use Cake\Form\Schema; - use Cake\Validation\Validator; - - class ContactForm extends Form - { - protected function _buildSchema(Schema $schema): Schema - { - return $schema->addField('name', 'string') - ->addField('email', ['type' => 'string']) - ->addField('body', ['type' => 'text']); - } - - public function validationDefault(Validator $validator): Validator - { - $validator->minLength('name', 10) - ->email('email'); - - return $validator; - } - - protected function _execute(array $data): bool - { - // Send an email. - return true; - } - } - -In the above example we see the 3 hook methods that forms provide: - -* ``_buildSchema`` is used to define the schema data that is used by FormHelper - to create an HTML form. You can define field type, length, and precision. -* ``validationDefault`` Gets a :php:class:`Cake\\Validation\\Validator` instance - that you can attach validators to. -* ``_execute`` lets you define the behavior you want to happen when - ``execute()`` is called and the data is valid. - -You can always define additional public methods as you need as well. - -Processing Request Data -======================= - -Once you've defined your form, you can use it in your controller to process -and validate request data:: - - // In a controller - namespace App\Controller; - - use App\Controller\AppController; - use App\Form\ContactForm; - - class ContactController extends AppController - { - public function index() - { - $contact = new ContactForm(); - if ($this->request->is('post')) { - if ($contact->execute($this->request->getData())) { - $this->Flash->success('We will get back to you soon.'); - } else { - $this->Flash->error('There was a problem submitting your form.'); - } - } - $this->set('contact', $contact); - } - } - -In the above example, we use the ``execute()`` method to run our form's -``_execute()`` method only when the data is valid, and set flash messages -accordingly. If we want to use a non-default validation set we can use the -``validate`` option:: - - if ($contact->execute($this->request->getData(), 'update')) { - // Handle form success. - } - -This option can also be set to ``false`` to disable validation. - -We could have also used the ``validate()`` method to only validate -the request data:: - - $isValid = $form->validate($this->request->getData()); - - // You can also use other validation sets. The following - // would use the rules defined by `validationUpdate()` - $isValid = $form->validate($this->request->getData(), 'update'); - -Setting Form Values -=================== - -You can set default values for modelless forms using the ``setData()`` method. -Values set with this method will overwrite existing data in the form object:: - - // In a controller - namespace App\Controller; - - use App\Controller\AppController; - use App\Form\ContactForm; - - class ContactController extends AppController - { - public function index() - { - $contact = new ContactForm(); - if ($this->request->is('post')) { - if ($contact->execute($this->request->getData())) { - $this->Flash->success('We will get back to you soon.'); - } else { - $this->Flash->error('There was a problem submitting your form.'); - } - } - - if ($this->request->is('get')) { - $contact->setData([ - 'name' => 'John Doe', - 'email' => 'john.doe@example.com' - ]); - } - - $this->set('contact', $contact); - } - } - -Values should only be defined if the request method is GET, otherwise -you will overwrite your previous POST Data which might have validation errors -that need corrections. You can use ``set()`` to add or replace individual fields -or a subset of fields:: - - // Set one field. - $contact->set('name', 'John Doe'); - - // Set multiple fields; - $contact->set([ - 'name' => 'John Doe', - 'email' => 'john.doe@example.com', - ]); - -Getting Form Errors -=================== - -Once a form has been validated you can retrieve the errors from it:: - - $errors = $form->getErrors(); - /* $errors contains - [ - 'name' => ['length' => 'Name must be at least two characters long'], - 'email' => ['format' => 'A valid email address is required'], - ] - */ - - $error = $form->getError('email'); - /* $error contains - [ - 'format' => 'A valid email address is required', - ] - */ - -Invalidating Individual Form Fields from Controller -=================================================== - -It is possible to invalidate individual fields from the controller without the -use of the Validator class. The most common use case for this is when the -validation is done on a remote server. In such case, you must manually -invalidate the fields accordingly to the feedback from the remote server:: - - // in src/Form/ContactForm.php - public function setErrors($errors) - { - $this->_errors = $errors; - } - -According to how the validator class would have returned the errors, ``$errors`` -must be in this format:: - - ['fieldName' => ['validatorName' => 'The error message to display']] - -Now you will be able to invalidate form fields by setting the fieldName, then -set the error messages:: - - // In a controller - $contact = new ContactForm(); - $contact->setErrors(['email' => ['_required' => 'Your email is required']]); - -Proceed to Creating HTML with FormHelper to see the results. - -Creating HTML with FormHelper -============================= - -Once you've created a Form class, you'll likely want to create an HTML form for -it. FormHelper understands Form objects just like ORM entities:: - - echo $this->Form->create($contact); - echo $this->Form->control('name'); - echo $this->Form->control('email'); - echo $this->Form->control('body'); - echo $this->Form->button('Submit'); - echo $this->Form->end(); - -The above would create an HTML form for the ``ContactForm`` we defined earlier. -HTML forms created with FormHelper will use the defined schema and validator to -determine field types, maxlengths, and validation errors. diff --git a/en/core-libraries/global-constants-and-functions.rst b/en/core-libraries/global-constants-and-functions.rst deleted file mode 100644 index 2ff14eb213..0000000000 --- a/en/core-libraries/global-constants-and-functions.rst +++ /dev/null @@ -1,240 +0,0 @@ -Constants & Functions -##################### - -While most of your day-to-day work in CakePHP will be utilizing core classes and -methods, CakePHP features a number of global convenience functions that may come -in handy. Many of these functions are for use with CakePHP classes (loading -model or component classes), but many others make working with arrays or -strings a little easier. - -We'll also cover some of the constants available in CakePHP applications. Using -these constants will help make upgrades more smooth, but are also convenient -ways to point to certain files or directories in your CakePHP application. - -Global Functions -================ - -Here are CakePHP's globally available functions. Most of them are just -convenience wrappers for other CakePHP functionality, such as debugging and -translating content. By default only namespaced functions are autoloaded, -however you can optionally load global aliases by adding:: - - require CAKE . 'functions.php'; - -To your application's ``config/bootstrap.php``. Doing this will load global -aliases for *all* functions listed below. - -.. php:namespace:: Cake\I18n - -.. php:function:: \_\_(string $string_id, [$formatArgs]) - - This function handles localization in CakePHP applications. The - ``$string_id`` identifies the ID for a translation. You can supply - additional arguments to replace placeholders in your string:: - - __('You have {0} unread messages', $number); - - You can also provide a name-indexed array of replacements:: - - __('You have {unread} unread messages', ['unread' => $number]); - - .. note:: - - Check out the - :doc:`/core-libraries/internationalization-and-localization` section for - more information. - -.. php:function:: __d(string $domain, string $msg, mixed $args = null) - - Allows you to override the current domain for a single message lookup. - - Useful when internationalizing a plugin: - ``echo __d('plugin_name', 'This is my plugin');`` - - .. note:: - - Make sure to use the underscored version of the plugin name here as domain. - -.. php:function:: __dn(string $domain, string $singular, string $plural, integer $count, mixed $args = null) - - Allows you to override the current domain for a single plural message - lookup. Returns correct plural form of message identified by ``$singular`` - and ``$plural`` for count ``$count`` from domain ``$domain``. - -.. php:function:: __dx(string $domain, string $context, string $msg, mixed $args = null) - - Allows you to override the current domain for a single message lookup. It - also allows you to specify a context. - - The context is a unique identifier for the translations string that makes it - unique within the same domain. - -.. php:function:: __dxn(string $domain, string $context, string $singular, string $plural, integer $count, mixed $args = null) - - Allows you to override the current domain for a single plural message - lookup. It also allows you to specify a context. Returns correct plural - form of message identified by ``$singular`` and ``$plural`` for count - ``$count`` from domain ``$domain``. Some languages have more than one form - for plural messages dependent on the count. - - The context is a unique identifier for the translations string that makes it - unique within the same domain. - -.. php:function:: __n(string $singular, string $plural, integer $count, mixed $args = null) - - Returns correct plural form of message identified by ``$singular`` and - ``$plural`` for count ``$count``. Some languages have more than one form for - plural messages dependent on the count. - -.. php:function:: __x(string $context, string $msg, mixed $args = null) - - The context is a unique identifier for the translations string that makes it - unique within the same domain. - -.. php:function:: __xn(string $context, string $singular, string $plural, integer $count, mixed $args = null) - - Returns correct plural form of message identified by ``$singular`` and - ``$plural`` for count ``$count`` from domain ``$domain``. It also allows you - to specify a context. Some languages have more than one form for plural - messages dependent on the count. - - The context is a unique identifier for the translations string that makes it - unique within the same domain. - -.. php:namespace:: Cake\Collection - -.. php:function:: collection(mixed $items) - - Convenience wrapper for instantiating a new :php:class:`Cake\\Collection\\Collection` - object, wrapping the passed argument. The ``$items`` parameter takes either - a ``Traversable`` object or an array. - -.. php:namespace:: Cake\Core - -.. php:function:: debug(mixed $var, boolean $showHtml = null, $showFrom = true) - - If the core ``$debug`` variable is ``true``, ``$var`` is printed out. - If ``$showHTML`` is ``true`` or left as ``null``, the data is rendered to be - browser-friendly. If ``$showFrom`` is not set to ``false``, the debug output - will start with the line from which it was called. Also see - :doc:`/development/debugging` - -.. php:function:: dd(mixed $var, boolean $showHtml = null) - - It behaves like ``debug()``, but execution is also halted. - If the core ``$debug`` variable is ``true``, ``$var`` is printed. - If ``$showHTML`` is ``true`` or left as ``null``, the data is rendered to be - browser-friendly. Also see :doc:`/development/debugging` - -.. php:function:: pr(mixed $var) - - Convenience wrapper for ``print_r()``, with the addition of - wrapping ``
      `` tags around the output.
      -
      -.. php:function:: pj(mixed $var)
      -
      -    JSON pretty print convenience function, with the addition of
      -    wrapping ``
      `` tags around the output.
      -
      -    It is meant for debugging the JSON representation of objects and arrays.
      -
      -.. php:function:: env(string $key, string $default = null)
      -
      -    Gets an environment variable from available sources. Used as a backup if
      -    ``$_SERVER`` or ``$_ENV`` are disabled.
      -
      -    This function also emulates ``PHP_SELF`` and ``DOCUMENT_ROOT`` on
      -    unsupporting servers. In fact, it's a good idea to always use ``env()``
      -    instead of ``$_SERVER`` or ``getenv()`` (especially if you plan to
      -    distribute the code), since it's a full emulation wrapper.
      -
      -.. php:function:: h(string $text, boolean $double = true, string $charset = null)
      -
      -    Convenience wrapper for ``htmlspecialchars()``.
      -
      -.. php:function:: pluginSplit(string $name, boolean $dotAppend = false, string $plugin = null)
      -
      -    Splits a dot syntax plugin name into its plugin and class name. If ``$name``
      -    does not have a dot, then index 0 will be ``null``.
      -
      -    Commonly used like ``list($plugin, $name) = pluginSplit('Users.User');``
      -
      -.. php:function:: namespaceSplit(string $class)
      -
      -    Split the namespace from the classname.
      -
      -    Commonly used like ``list($namespace, $className) = namespaceSplit('Cake\Core\App');``
      -
      -Core Definition Constants
      -=========================
      -
      -Most of the following constants refer to paths in your application.
      -
      -.. php:const:: APP
      -
      -   Absolute path to your application directory, including a trailing slash.
      -
      -.. php:const:: APP_DIR
      -
      -    Equals ``app`` or the name of your application directory.
      -
      -.. php:const:: CACHE
      -
      -    Path to the cache files directory. It can be shared between hosts in a
      -    multi-server setup.
      -
      -.. php:const:: CAKE
      -
      -    Path to the cake directory.
      -
      -.. php:const:: CAKE_CORE_INCLUDE_PATH
      -
      -    Path to the root lib directory.
      -
      -.. php:const:: CONFIG
      -
      -   Path to the config directory.
      -
      -.. php:const:: CORE_PATH
      -
      -   Path to the CakePHP directory with ending directory slash.
      -
      -.. php:const:: DS
      -
      -    Short for PHP's ``DIRECTORY_SEPARATOR``, which is ``/`` on Linux and ``\``
      -    on Windows.
      -
      -.. php:const:: LOGS
      -
      -    Path to the logs directory.
      -
      -.. php:const:: RESOURCES
      -
      -   Path to the resources directory.
      -
      -.. php:const:: ROOT
      -
      -    Path to the root directory.
      -
      -.. php:const:: TESTS
      -
      -    Path to the tests directory.
      -
      -.. php:const:: TMP
      -
      -    Path to the temporary files directory.
      -
      -.. php:const:: WWW_ROOT
      -
      -    Full path to the webroot.
      -
      -Timing Definition Constants
      -===========================
      -
      -.. php:const:: TIME_START
      -
      -    Unix timestamp in microseconds as a float from when the application started.
      -
      -.. meta::
      -    :title lang=en: Global Constants and Functions
      -    :keywords lang=en: internationalization and localization,global constants,example config,array php,convenience functions,core libraries,component classes,optional number,global functions,string string,core classes,format strings,unread messages,placeholders,useful functions,arrays,parameters,existence,translations
      diff --git a/en/core-libraries/hash.rst b/en/core-libraries/hash.rst
      deleted file mode 100644
      index 2a0c2a74ff..0000000000
      --- a/en/core-libraries/hash.rst
      +++ /dev/null
      @@ -1,887 +0,0 @@
      -Hash
      -####
      -
      -.. php:namespace:: Cake\Utility
      -
      -.. php:class:: Hash
      -
      -Array management, if done right, can be a very powerful and useful
      -tool for building smarter, more optimized code. CakePHP offers a
      -very useful set of static utilities in the Hash class that allow you
      -to do just that.
      -
      -CakePHP's Hash class can be called from any model or controller in
      -the same way Inflector is called. Example: :php:meth:`Hash::combine()`.
      -
      -.. _hash-path-syntax:
      -
      -Hash Path Syntax
      -================
      -
      -The path syntax described below is used by all the methods in ``Hash``. Not all
      -parts of the path syntax are available in all methods. A path expression is
      -made of any number of tokens. Tokens are composed of two groups. Expressions,
      -are used to traverse the array data, while matchers are used to qualify
      -elements. You apply matchers to expression elements.
      -
      -Expression Types
      -----------------
      -
      -+--------------------------------+--------------------------------------------+
      -| Expression                     | Definition                                 |
      -+================================+============================================+
      -| ``{n}``                        | Represents a numeric key. Will match       |
      -|                                | any string or numeric key.                 |
      -+--------------------------------+--------------------------------------------+
      -| ``{s}``                        | Represents a string. Will match any        |
      -|                                | string value including numeric string      |
      -|                                | values.                                    |
      -+--------------------------------+--------------------------------------------+
      -| ``{*}``                        | Matches any value.                         |
      -+--------------------------------+--------------------------------------------+
      -| ``Foo``                        | Matches keys with the exact same value.    |
      -+--------------------------------+--------------------------------------------+
      -
      -All expression elements are supported by all methods. In addition to expression
      -elements, you can use attribute matching with certain methods. They are ``extract()``,
      -``combine()``, ``format()``, ``check()``, ``map()``, ``reduce()``,
      -``apply()``, ``sort()``, ``insert()``, ``remove()`` and ``nest()``.
      -
      -Attribute Matching Types
      -------------------------
      -
      -+--------------------------------+--------------------------------------------+
      -| Matcher                        | Definition                                 |
      -+================================+============================================+
      -| ``[id]``                       | Match elements with a given array key.     |
      -+--------------------------------+--------------------------------------------+
      -| ``[id=2]``                     | Match elements with id equal to 2.         |
      -+--------------------------------+--------------------------------------------+
      -| ``[id!=2]``                    | Match elements with id not equal to 2.     |
      -+--------------------------------+--------------------------------------------+
      -| ``[id>2]``                     | Match elements with id greater than 2.     |
      -+--------------------------------+--------------------------------------------+
      -| ``[id>=2]``                    | Match elements with id greater than        |
      -|                                | or equal to 2.                             |
      -+--------------------------------+--------------------------------------------+
      -| ``[id<2]``                     | Match elements with id less than 2         |
      -+--------------------------------+--------------------------------------------+
      -| ``[id<=2]``                    | Match elements with id less than           |
      -|                                | or equal to 2.                             |
      -+--------------------------------+--------------------------------------------+
      -| ``[text=/.../]``               | Match elements that have values matching   |
      -|                                | the regular expression inside ``...``.     |
      -+--------------------------------+--------------------------------------------+
      -
      -.. php:staticmethod:: get(array|\ArrayAccess $data, $path, $default = null)
      -
      -    ``get()`` is a simplified version of ``extract()``, it only supports direct
      -    path expressions. Paths with ``{n}``, ``{s}``, ``{*}`` or matchers are not
      -    supported. Use ``get()`` when you want exactly one value out of an array. If
      -    a matching path is not found the default value will be returned.
      -
      -.. php:staticmethod:: extract(array|\ArrayAccess $data, $path)
      -
      -    ``Hash::extract()`` supports all expression, and matcher components of
      -    :ref:`hash-path-syntax`. You can use extract to retrieve data from arrays
      -    or object implementing ``ArrayAccess`` interface, along arbitrary paths
      -    quickly without having to loop through the data structures. Instead you
      -    use path expressions to qualify which elements you want returned ::
      -
      -        // Common Usage:
      -        $users = [
      -            ['id' => 1, 'name' => 'mark'],
      -            ['id' => 2, 'name' => 'jane'],
      -            ['id' => 3, 'name' => 'sally'],
      -            ['id' => 4, 'name' => 'jose'],
      -        ];
      -        $results = Hash::extract($users, '{n}.id');
      -        // $results equals:
      -        // [1,2,3,4];
      -
      -.. php:staticmethod:: Hash::insert(array $data, $path, $values = null)
      -
      -    Inserts ``$values`` into an array as defined by ``$path``::
      -
      -        $a = [
      -            'pages' => ['name' => 'page']
      -        ];
      -        $result = Hash::insert($a, 'files', ['name' => 'files']);
      -        // $result now looks like:
      -        [
      -            [pages] => [
      -                [name] => page
      -            ]
      -            [files] => [
      -                [name] => files
      -            ]
      -        ]
      -
      -    You can use paths using ``{n}``, ``{s}`` and ``{*}`` to insert data into multiple
      -    points::
      -
      -        $users = Hash::insert($users, '{n}.new', 'value');
      -
      -    Attribute matchers work with ``insert()`` as well::
      -
      -        $data = [
      -            0 => ['up' => true, 'Item' => ['id' => 1, 'title' => 'first']],
      -            1 => ['Item' => ['id' => 2, 'title' => 'second']],
      -            2 => ['Item' => ['id' => 3, 'title' => 'third']],
      -            3 => ['up' => true, 'Item' => ['id' => 4, 'title' => 'fourth']],
      -            4 => ['Item' => ['id' => 5, 'title' => 'fifth']],
      -        ];
      -        $result = Hash::insert($data, '{n}[up].Item[id=4].new', 9);
      -        /* $result now looks like:
      -            [
      -                ['up' => true, 'Item' => ['id' => 1, 'title' => 'first']],
      -                ['Item' => ['id' => 2, 'title' => 'second']],
      -                ['Item' => ['id' => 3, 'title' => 'third']],
      -                ['up' => true, 'Item' => ['id' => 4, 'title' => 'fourth', 'new' => 9]],
      -                ['Item' => ['id' => 5, 'title' => 'fifth']],
      -            ]
      -        */
      -
      -.. php:staticmethod:: remove(array $data, $path)
      -
      -    Removes all elements from an array that match ``$path``. ::
      -
      -        $a = [
      -            'pages' => ['name' => 'page'],
      -            'files' => ['name' => 'files']
      -        ];
      -        $result = Hash::remove($a, 'files');
      -        /* $result now looks like:
      -            [
      -                [pages] => [
      -                    [name] => page
      -                ]
      -
      -            ]
      -        */
      -
      -    Using ``{n}``, ``{s}`` and ``{*}`` will allow you to remove multiple values at once.
      -    You can also use attribute matchers with ``remove()``::
      -
      -        $data = [
      -            0 => ['clear' => true, 'Item' => ['id' => 1, 'title' => 'first']],
      -            1 => ['Item' => ['id' => 2, 'title' => 'second']],
      -            2 => ['Item' => ['id' => 3, 'title' => 'third']],
      -            3 => ['clear' => true, 'Item' => ['id' => 4, 'title' => 'fourth']],
      -            4 => ['Item' => ['id' => 5, 'title' => 'fifth']],
      -        ];
      -        $result = Hash::remove($data, '{n}[clear].Item[id=4]');
      -        /* $result now looks like:
      -            [
      -                ['clear' => true, 'Item' => ['id' => 1, 'title' => 'first']],
      -                ['Item' => ['id' => 2, 'title' => 'second']],
      -                ['Item' => ['id' => 3, 'title' => 'third']],
      -                ['clear' => true],
      -                ['Item' => ['id' => 5, 'title' => 'fifth']],
      -            ]
      -        */
      -
      -.. php:staticmethod:: combine(array $data, $keyPath, $valuePath = null, $groupPath = null)
      -
      -    Creates an associative array using a ``$keyPath`` as the path to build its keys,
      -    and optionally ``$valuePath`` as path to get the values. If ``$valuePath`` is not
      -    specified, or doesn't match anything, values will be initialized to null.
      -    You can optionally group the values by what is obtained when following the
      -    path specified in ``$groupPath``. ::
      -
      -        $a = [
      -            [
      -                'User' => [
      -                    'id' => 2,
      -                    'group_id' => 1,
      -                    'Data' => [
      -                        'user' => 'mariano.iglesias',
      -                        'name' => 'Mariano Iglesias'
      -                    ]
      -                ]
      -            ],
      -            [
      -                'User' => [
      -                    'id' => 14,
      -                    'group_id' => 2,
      -                    'Data' => [
      -                        'user' => 'phpnut',
      -                        'name' => 'Larry E. Masters'
      -                    ]
      -                ]
      -            ],
      -        ];
      -
      -        $result = Hash::combine($a, '{n}.User.id');
      -        /* $result now looks like:
      -            [
      -                [2] =>
      -                [14] =>
      -            ]
      -        */
      -
      -        $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.user');
      -        /* $result now looks like:
      -            [
      -                [2] => 'mariano.iglesias'
      -                [14] => 'phpnut'
      -            ]
      -        */
      -
      -        $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data');
      -        /* $result now looks like:
      -            [
      -                [2] => [
      -                        [user] => mariano.iglesias
      -                        [name] => Mariano Iglesias
      -                ]
      -                [14] => [
      -                        [user] => phpnut
      -                        [name] => Larry E. Masters
      -                ]
      -            ]
      -        */
      -
      -        $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.name');
      -        /* $result now looks like:
      -            [
      -                [2] => Mariano Iglesias
      -                [14] => Larry E. Masters
      -            ]
      -        */
      -
      -        $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data', '{n}.User.group_id');
      -        /* $result now looks like:
      -            [
      -                [1] => [
      -                        [2] => [
      -                                [user] => mariano.iglesias
      -                                [name] => Mariano Iglesias
      -                        ]
      -                ]
      -                [2] => [
      -                        [14] => [
      -                                [user] => phpnut
      -                                [name] => Larry E. Masters
      -                        ]
      -                ]
      -            ]
      -        */
      -
      -        $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.name', '{n}.User.group_id');
      -        /* $result now looks like:
      -            [
      -                [1] => [
      -                        [2] => Mariano Iglesias
      -                ]
      -                [2] => [
      -                        [14] => Larry E. Masters
      -                ]
      -            ]
      -        */
      -
      -        // As of 3.9.0 $keyPath can be null
      -        $result = Hash::combine($a, null, '{n}.User.Data.name');
      -        /* $result now looks like:
      -            [
      -                [0] => Mariano Iglesias
      -                [1] => Larry E. Masters
      -            ]
      -        */
      -
      -    You can provide arrays for both ``$keyPath`` and ``$valuePath``. If you do this,
      -    the first value will be used as a format string, for values extracted by the
      -    other paths::
      -
      -        $result = Hash::combine(
      -            $a,
      -            '{n}.User.id',
      -            ['%s: %s', '{n}.User.Data.user', '{n}.User.Data.name'],
      -            '{n}.User.group_id'
      -        );
      -        /* $result now looks like:
      -            [
      -                [1] => [
      -                        [2] => mariano.iglesias: Mariano Iglesias
      -                ]
      -                [2] => [
      -                        [14] => phpnut: Larry E. Masters
      -                ]
      -            ]
      -        */
      -
      -        $result = Hash::combine(
      -            $a,
      -            ['%s: %s', '{n}.User.Data.user', '{n}.User.Data.name'],
      -            '{n}.User.id'
      -        );
      -        /* $result now looks like:
      -            [
      -                [mariano.iglesias: Mariano Iglesias] => 2
      -                [phpnut: Larry E. Masters] => 14
      -            ]
      -        */
      -
      -.. php:staticmethod:: format(array $data, array $paths, $format)
      -
      -    Returns a series of values extracted from an array, formatted with a
      -    format string::
      -
      -        $data = [
      -            [
      -                'Person' => [
      -                    'first_name' => 'Nate',
      -                    'last_name' => 'Abele',
      -                    'city' => 'Boston',
      -                    'state' => 'MA',
      -                    'something' => '42'
      -                ]
      -            ],
      -            [
      -                'Person' => [
      -                    'first_name' => 'Larry',
      -                    'last_name' => 'Masters',
      -                    'city' => 'Boondock',
      -                    'state' => 'TN',
      -                    'something' => '{0}'
      -                ]
      -            ],
      -            [
      -                'Person' => [
      -                    'first_name' => 'Garrett',
      -                    'last_name' => 'Woodworth',
      -                    'city' => 'Venice Beach',
      -                    'state' => 'CA',
      -                    'something' => '{1}'
      -                ]
      -            ]
      -        ];
      -
      -        $res = Hash::format($data, ['{n}.Person.first_name', '{n}.Person.something'], '%2$d, %1$s');
      -        /*
      -        [
      -            [0] => 42, Nate
      -            [1] => 0, Larry
      -            [2] => 0, Garrett
      -        ]
      -        */
      -
      -        $res = Hash::format($data, ['{n}.Person.first_name', '{n}.Person.something'], '%1$s, %2$d');
      -        /*
      -        [
      -            [0] => Nate, 42
      -            [1] => Larry, 0
      -            [2] => Garrett, 0
      -        ]
      -        */
      -
      -.. php:staticmethod:: contains(array $data, array $needle)
      -
      -    Determines if one Hash or array contains the exact keys and values
      -    of another::
      -
      -        $a = [
      -            0 => ['name' => 'main'],
      -            1 => ['name' => 'about']
      -        ];
      -        $b = [
      -            0 => ['name' => 'main'],
      -            1 => ['name' => 'about'],
      -            2 => ['name' => 'contact'],
      -            'a' => 'b',
      -        ];
      -
      -        $result = Hash::contains($a, $a);
      -        // true
      -        $result = Hash::contains($a, $b);
      -        // false
      -        $result = Hash::contains($b, $a);
      -        // true
      -
      -.. php:staticmethod:: check(array $data, string $path = null)
      -
      -    Checks if a particular path is set in an array::
      -
      -        $set = [
      -            'My Index 1' => ['First' => 'The first item']
      -        ];
      -        $result = Hash::check($set, 'My Index 1.First');
      -        // $result == true
      -
      -        $result = Hash::check($set, 'My Index 1');
      -        // $result == true
      -
      -        $set = [
      -            'My Index 1' => [
      -                'First' => [
      -                    'Second' => [
      -                        'Third' => [
      -                            'Fourth' => 'Heavy. Nesting.'
      -                        ]
      -                    ]
      -                ]
      -            ]
      -        ];
      -        $result = Hash::check($set, 'My Index 1.First.Second');
      -        // $result == true
      -
      -        $result = Hash::check($set, 'My Index 1.First.Second.Third');
      -        // $result == true
      -
      -        $result = Hash::check($set, 'My Index 1.First.Second.Third.Fourth');
      -        // $result == true
      -
      -        $result = Hash::check($set, 'My Index 1.First.Seconds.Third.Fourth');
      -        // $result == false
      -
      -.. php:staticmethod:: filter(array $data, $callback = ['Hash', 'filter'])
      -
      -    Filters empty elements out of array, excluding '0'. You can also supply a
      -    custom ``$callback`` to filter the array elements. The callback should
      -    return ``false`` to remove elements from the resulting array::
      -
      -        $data = [
      -            '0',
      -            false,
      -            true,
      -            0,
      -            ['one thing', 'I can tell you', 'is you got to be', false]
      -        ];
      -        $res = Hash::filter($data);
      -
      -        /* $res now looks like:
      -            [
      -                [0] => 0
      -                [2] => true
      -                [3] => 0
      -                [4] => [
      -                        [0] => one thing
      -                        [1] => I can tell you
      -                        [2] => is you got to be
      -                ]
      -            ]
      -        */
      -
      -.. php:staticmethod:: flatten(array $data, string $separator = '.')
      -
      -    Collapses a multi-dimensional array into a single dimension::
      -
      -        $arr = [
      -            [
      -                'Post' => ['id' => '1', 'title' => 'First Post'],
      -                'Author' => ['id' => '1', 'user' => 'Kyle'],
      -            ],
      -            [
      -                'Post' => ['id' => '2', 'title' => 'Second Post'],
      -                'Author' => ['id' => '3', 'user' => 'Crystal'],
      -            ],
      -        ];
      -        $res = Hash::flatten($arr);
      -        /* $res now looks like:
      -            [
      -                [0.Post.id] => 1
      -                [0.Post.title] => First Post
      -                [0.Author.id] => 1
      -                [0.Author.user] => Kyle
      -                [1.Post.id] => 2
      -                [1.Post.title] => Second Post
      -                [1.Author.id] => 3
      -                [1.Author.user] => Crystal
      -            ]
      -        */
      -
      -.. php:staticmethod:: expand(array $data, string $separator = '.')
      -
      -    Expands an array that was previously flattened with
      -    :php:meth:`Hash::flatten()`::
      -
      -        $data = [
      -            '0.Post.id' => 1,
      -            '0.Post.title' => First Post,
      -            '0.Author.id' => 1,
      -            '0.Author.user' => Kyle,
      -            '1.Post.id' => 2,
      -            '1.Post.title' => Second Post,
      -            '1.Author.id' => 3,
      -            '1.Author.user' => Crystal,
      -        ];
      -        $res = Hash::expand($data);
      -        /* $res now looks like:
      -        [
      -            [
      -                'Post' => ['id' => '1', 'title' => 'First Post'],
      -                'Author' => ['id' => '1', 'user' => 'Kyle'],
      -            ],
      -            [
      -                'Post' => ['id' => '2', 'title' => 'Second Post'],
      -                'Author' => ['id' => '3', 'user' => 'Crystal'],
      -            ],
      -        ];
      -        */
      -
      -.. php:staticmethod:: merge(array $data, array $merge[, array $n])
      -
      -    This function can be thought of as a hybrid between PHP's
      -    ``array_merge`` and ``array_merge_recursive``. The difference to the two
      -    is that if an array key contains another array then the function
      -    behaves recursive (unlike ``array_merge``) but does not do if for keys
      -    containing strings (unlike ``array_merge_recursive``).
      -
      -    .. note::
      -
      -        This function will work with an unlimited amount of arguments and
      -        typecasts non-array parameters into arrays.
      -
      -    ::
      -
      -        $array = [
      -            [
      -                'id' => '48c2570e-dfa8-4c32-a35e-0d71cbdd56cb',
      -                'name' => 'mysql raleigh-workshop-08 < 2008-09-05.sql ',
      -                'description' => 'Importing an sql dump',
      -            ],
      -            [
      -                'id' => '48c257a8-cf7c-4af2-ac2f-114ecbdd56cb',
      -                'name' => 'pbpaste | grep -i Unpaid | pbcopy',
      -                'description' => 'Remove all lines that say "Unpaid".',
      -            ]
      -        ];
      -        $arrayB = 4;
      -        $arrayC = [0 => "test array", "cats" => "dogs", "people" => 1267];
      -        $arrayD = ["cats" => "felines", "dog" => "angry"];
      -        $res = Hash::merge($array, $arrayB, $arrayC, $arrayD);
      -
      -        /* $res now looks like:
      -        [
      -            [0] => [
      -                    [id] => 48c2570e-dfa8-4c32-a35e-0d71cbdd56cb
      -                    [name] => mysql raleigh-workshop-08 < 2008-09-05.sql
      -                    [description] => Importing an sql dump
      -            ]
      -            [1] => [
      -                    [id] => 48c257a8-cf7c-4af2-ac2f-114ecbdd56cb
      -                    [name] => pbpaste | grep -i Unpaid | pbcopy
      -                    [description] => Remove all lines that say "Unpaid".
      -            ]
      -            [2] => 4
      -            [3] => test array
      -            [cats] => felines
      -            [people] => 1267
      -            [dog] => angry
      -        ]
      -        */
      -
      -.. php:staticmethod:: numeric(array $data)
      -
      -    Checks to see if all the values in the array are numeric::
      -
      -        $data = ['one'];
      -        $res = Hash::numeric(array_keys($data));
      -        // $res is true
      -
      -        $data = [1 => 'one'];
      -        $res = Hash::numeric($data);
      -        // $res is false
      -
      -.. php:staticmethod:: dimensions (array $data)
      -
      -    Counts the dimensions of an array. This method will only
      -    consider the dimension of the first element in the array::
      -
      -        $data = ['one', '2', 'three'];
      -        $result = Hash::dimensions($data);
      -        // $result == 1
      -
      -        $data = ['1' => '1.1', '2', '3'];
      -        $result = Hash::dimensions($data);
      -        // $result == 1
      -
      -        $data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => '3.1.1']];
      -        $result = Hash::dimensions($data);
      -        // $result == 2
      -
      -        $data = ['1' => '1.1', '2', '3' => ['3.1' => '3.1.1']];
      -        $result = Hash::dimensions($data);
      -        // $result == 1
      -
      -        $data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => ['3.1.1' => '3.1.1.1']]];
      -        $result = Hash::dimensions($data);
      -        // $result == 2
      -
      -.. php:staticmethod:: maxDimensions(array $data)
      -
      -    Similar to :php:meth:`~Hash::dimensions()`, however this method returns,
      -    the deepest number of dimensions of any element in the array::
      -
      -        $data = ['1' => '1.1', '2', '3' => ['3.1' => '3.1.1']];
      -        $result = Hash::maxDimensions($data);
      -        // $result == 2
      -
      -        $data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => ['3.1.1' => '3.1.1.1']]];
      -        $result = Hash::maxDimensions($data);
      -        // $result == 3
      -
      -.. php:staticmethod:: map(array $data, $path, $function)
      -
      -    Creates a new array, by extracting ``$path``, and mapping ``$function``
      -    across the results. You can use both expression and matching elements with
      -    this method::
      -
      -        // Call the noop function $this->noop() on every element of $data
      -        $result = Hash::map($data, "{n}", [$this, 'noop']);
      -
      -        public function noop(array $array)
      -        {
      -            // Do stuff to array and return the result
      -            return $array;
      -        }
      -
      -.. php:staticmethod:: reduce(array $data, $path, $function)
      -
      -    Creates a single value, by extracting ``$path``, and reducing the extracted
      -    results with ``$function``. You can use both expression and matching elements
      -    with this method.
      -
      -.. php:staticmethod:: apply(array $data, $path, $function)
      -
      -    Apply a callback to a set of extracted values using ``$function``. The function
      -    will get the extracted values as the first argument::
      -
      -        $data = [
      -            ['date' => '01-01-2016', 'booked' => true],
      -            ['date' => '01-01-2016', 'booked' => false],
      -            ['date' => '02-01-2016', 'booked' => true]
      -        ];
      -        $result = Hash::apply($data, '{n}[booked=true].date', 'array_count_values');
      -        /* $result now looks like:
      -            [
      -                '01-01-2016' => 1,
      -                '02-01-2016' => 1,
      -            ]
      -        */
      -
      -.. php:staticmethod:: sort(array $data, $path, $dir, $type = 'regular')
      -
      -    Sorts an array by any value, determined by a :ref:`hash-path-syntax`
      -    Only expression elements are supported by this method::
      -
      -        $a = [
      -            0 => ['Person' => ['name' => 'Jeff']],
      -            1 => ['Shirt' => ['color' => 'black']]
      -        ];
      -        $result = Hash::sort($a, '{n}.Person.name', 'asc');
      -        /* $result now looks like:
      -            [
      -                [0] => [
      -                        [Shirt] => [
      -                                [color] => black
      -                        ]
      -                ]
      -                [1] => [
      -                        [Person] => [
      -                                [name] => Jeff
      -                        ]
      -                ]
      -            ]
      -        */
      -
      -    ``$dir`` can be either ``asc`` or ``desc``. ``$type``
      -    can be one of the following values:
      -
      -    * ``regular`` for regular sorting.
      -    * ``numeric`` for sorting values as their numeric equivalents.
      -    * ``string`` for sorting values as their string value.
      -    * ``natural`` for sorting values in a human friendly way. Will
      -      sort ``foo10`` below ``foo2`` as an example.
      -
      -.. php:staticmethod:: diff(array $data, array $compare)
      -
      -    Computes the difference between two arrays::
      -
      -        $a = [
      -            0 => ['name' => 'main'],
      -            1 => ['name' => 'about']
      -        ];
      -        $b = [
      -            0 => ['name' => 'main'],
      -            1 => ['name' => 'about'],
      -            2 => ['name' => 'contact']
      -        ];
      -
      -        $result = Hash::diff($a, $b);
      -        /* $result now looks like:
      -            [
      -                [2] => [
      -                        [name] => contact
      -                ]
      -            ]
      -        */
      -
      -.. php:staticmethod:: mergeDiff(array $data, array $compare)
      -
      -    This function merges two arrays and pushes the differences in
      -    data to the bottom of the resultant array.
      -
      -    **Example 1**
      -    ::
      -
      -        $array1 = ['ModelOne' => ['id' => 1001, 'field_one' => 'a1.m1.f1', 'field_two' => 'a1.m1.f2']];
      -        $array2 = ['ModelOne' => ['id' => 1003, 'field_one' => 'a3.m1.f1', 'field_two' => 'a3.m1.f2', 'field_three' => 'a3.m1.f3']];
      -        $res = Hash::mergeDiff($array1, $array2);
      -
      -        /* $res now looks like:
      -            [
      -                [ModelOne] => [
      -                        [id] => 1001
      -                        [field_one] => a1.m1.f1
      -                        [field_two] => a1.m1.f2
      -                        [field_three] => a3.m1.f3
      -                    ]
      -            ]
      -        */
      -
      -    **Example 2**
      -    ::
      -
      -        $array1 = ["a" => "b", 1 => 20938, "c" => "string"];
      -        $array2 = ["b" => "b", 3 => 238, "c" => "string", ["extra_field"]];
      -        $res = Hash::mergeDiff($array1, $array2);
      -        /* $res now looks like:
      -            [
      -                [a] => b
      -                [1] => 20938
      -                [c] => string
      -                [b] => b
      -                [3] => 238
      -                [4] => [
      -                        [0] => extra_field
      -                ]
      -            ]
      -        */
      -
      -.. php:staticmethod:: normalize(array $data, $assoc = true, $default = null)
      -
      -    Normalizes an array. If ``$assoc`` is ``true``, the resulting array will be
      -    normalized to be an associative array. Numeric keys with values, will be
      -    converted to string keys with ``$default`` values. Normalizing an array,
      -    makes using the results with :php:meth:`Hash::merge()` easier::
      -
      -        $a = ['Tree', 'CounterCache',
      -            'Upload' => [
      -                'folder' => 'products',
      -                'fields' => ['image_1_id', 'image_2_id']
      -            ]
      -        ];
      -        $result = Hash::normalize($a);
      -        /* $result now looks like:
      -            [
      -                [Tree] => null
      -                [CounterCache] => null
      -                [Upload] => [
      -                        [folder] => products
      -                        [fields] => [
      -                                [0] => image_1_id
      -                                [1] => image_2_id
      -                        ]
      -                ]
      -            ]
      -        */
      -
      -        $b = [
      -            'Cacheable' => ['enabled' => false],
      -            'Limit',
      -            'Bindable',
      -            'Validator',
      -            'Transactional',
      -        ];
      -        $result = Hash::normalize($b);
      -        /* $result now looks like:
      -            [
      -                [Cacheable] => [
      -                        [enabled] => false
      -                ]
      -
      -                [Limit] => null
      -                [Bindable] => null
      -                [Validator] => null
      -                [Transactional] => null
      -            ]
      -        */
      -
      -.. versionchanged:: 4.5.0
      -    The ``$default`` parameter was added.
      -
      -.. php:staticmethod:: nest(array $data, array $options = [])
      -
      -    Takes a flat array set, and creates a nested, or threaded data structure.
      -
      -    **Options:**
      -
      -    - ``children`` The key name to use in the result set for children. Defaults
      -      to 'children'.
      -    - ``idPath`` The path to a key that identifies each entry. Should be
      -      compatible with :php:meth:`Hash::extract()`. Defaults to ``{n}.$alias.id``
      -    - ``parentPath`` The path to a key that identifies the parent of each entry.
      -      Should be compatible with :php:meth:`Hash::extract()`. Defaults to ``{n}.$alias.parent_id``
      -    - ``root`` The id of the desired top-most result.
      -
      -    For example, if you had the following array of data::
      -
      -        $data = [
      -            ['ThreadPost' => ['id' => 1, 'parent_id' => null]],
      -            ['ThreadPost' => ['id' => 2, 'parent_id' => 1]],
      -            ['ThreadPost' => ['id' => 3, 'parent_id' => 1]],
      -            ['ThreadPost' => ['id' => 4, 'parent_id' => 1]],
      -            ['ThreadPost' => ['id' => 5, 'parent_id' => 1]],
      -            ['ThreadPost' => ['id' => 6, 'parent_id' => null]],
      -            ['ThreadPost' => ['id' => 7, 'parent_id' => 6]],
      -            ['ThreadPost' => ['id' => 8, 'parent_id' => 6]],
      -            ['ThreadPost' => ['id' => 9, 'parent_id' => 6]],
      -            ['ThreadPost' => ['id' => 10, 'parent_id' => 6]]
      -        ];
      -
      -        $result = Hash::nest($data, ['root' => 6]);
      -        /* $result now looks like:
      -            [
      -                (int) 0 => [
      -                    'ThreadPost' => [
      -                        'id' => (int) 6,
      -                        'parent_id' => null
      -                    ],
      -                    'children' => [
      -                        (int) 0 => [
      -                            'ThreadPost' => [
      -                                'id' => (int) 7,
      -                                'parent_id' => (int) 6
      -                            ],
      -                            'children' => []
      -                        ],
      -                        (int) 1 => [
      -                            'ThreadPost' => [
      -                                'id' => (int) 8,
      -                                'parent_id' => (int) 6
      -                            ],
      -                            'children' => []
      -                        ],
      -                        (int) 2 => [
      -                            'ThreadPost' => [
      -                                'id' => (int) 9,
      -                                'parent_id' => (int) 6
      -                            ],
      -                            'children' => []
      -                        ],
      -                        (int) 3 => [
      -                            'ThreadPost' => [
      -                                'id' => (int) 10,
      -                                'parent_id' => (int) 6
      -                            ],
      -                            'children' => []
      -                        ]
      -                    ]
      -                ]
      -            ]
      -            */
      -
      -.. meta::
      -    :title lang=en: Hash
      -    :keywords lang=en: array array,path array,array name,numeric key,regular expression,result set,person name,brackets,syntax,cakephp,elements,php,set path
      diff --git a/en/core-libraries/httpclient.rst b/en/core-libraries/httpclient.rst
      deleted file mode 100644
      index e698c8b9c5..0000000000
      --- a/en/core-libraries/httpclient.rst
      +++ /dev/null
      @@ -1,593 +0,0 @@
      -Http Client
      -###########
      -
      -.. php:namespace:: Cake\Http
      -
      -.. php:class:: Client(mixed $config = [])
      -
      -CakePHP includes a PSR-18 compliant HTTP client which can be used for
      -making requests. It is a great way to communicate with webservices, and
      -remote APIs.
      -
      -Doing Requests
      -==============
      -
      -Doing requests is simple and straight forward.  Doing a GET request looks like::
      -
      -    use Cake\Http\Client;
      -
      -    $http = new Client();
      -
      -    // Simple get
      -    $response = $http->get('http://example.com/test.html');
      -
      -    // Simple get with querystring
      -    $response = $http->get('http://example.com/search', ['q' => 'widget']);
      -
      -    // Simple get with querystring & additional headers
      -    $response = $http->get('http://example.com/search', ['q' => 'widget'], [
      -      'headers' => ['X-Requested-With' => 'XMLHttpRequest'],
      -    ]);
      -
      -Doing POST and PUT requests is equally simple::
      -
      -    // Send a POST request with application/x-www-form-urlencoded encoded data
      -    $http = new Client();
      -    $response = $http->post('http://example.com/posts/add', [
      -      'title' => 'testing',
      -      'body' => 'content in the post',
      -    ]);
      -
      -    // Send a PUT request with application/x-www-form-urlencoded encoded data
      -    $response = $http->put('http://example.com/posts/add', [
      -      'title' => 'testing',
      -      'body' => 'content in the post',
      -    ]);
      -
      -    // Other methods as well.
      -    $http->delete(/* ... */);
      -    $http->head(/* ... */);
      -    $http->patch(/* ... */);
      -
      -If you have created a PSR-7 request object you can send it using
      -``sendRequest()``::
      -
      -    use Cake\Http\Client;
      -    use Cake\Http\Client\Request as ClientRequest;
      -
      -    $request = new ClientRequest(
      -        'http://example.com/search',
      -        ClientRequest::METHOD_GET
      -    );
      -    $http = new Client();
      -    $response = $http->sendRequest($request);
      -
      -Creating Multipart Requests with Files
      -======================================
      -
      -You can include files in request bodies by including a filehandle in the array::
      -
      -    $http = new Client();
      -    $response = $http->post('http://example.com/api', [
      -      'image' => fopen('/path/to/a/file', 'r'),
      -    ]);
      -
      -The filehandle will be read until its end; it will not be rewound before being read.
      -
      -Building Multipart Request Bodies
      ----------------------------------
      -
      -There may be times when you need to build a request body in a very specific way.
      -In these situations you can often use ``Cake\Http\Client\FormData`` to craft
      -the specific multipart HTTP request you want::
      -
      -    use Cake\Http\Client\FormData;
      -
      -    $data = new FormData();
      -
      -    // Create an XML part
      -    $xml = $data->newPart('xml', $xmlString);
      -    // Set the content type.
      -    $xml->type('application/xml');
      -    $data->add($xml);
      -
      -    // Create a file upload with addFile()
      -    // This will append the file to the form data as well.
      -    $file = $data->addFile('upload', fopen('/some/file.txt', 'r'));
      -    $file->contentId('abc123');
      -    $file->disposition('attachment');
      -
      -    // Send the request.
      -    $response = $http->post(
      -        'http://example.com/api',
      -        (string)$data,
      -        ['headers' => ['Content-Type' => $data->contentType()]]
      -    );
      -
      -Sending Request Bodies
      -======================
      -
      -When dealing with REST APIs you often need to send request bodies that are not
      -form encoded. Http\\Client exposes this through the type option::
      -
      -    // Send a JSON request body.
      -    $http = new Client();
      -    $response = $http->post(
      -      'http://example.com/tasks',
      -      json_encode($data),
      -      ['type' => 'json']
      -    );
      -
      -The ``type`` key can either be a one of 'json', 'xml' or a full mime type.
      -When using the ``type`` option, you should provide the data as a string. If you're
      -doing a GET request that needs both querystring parameters and a request body
      -you can do the following::
      -
      -    // Send a JSON body in a GET request with query string parameters.
      -    $http = new Client();
      -    $response = $http->get(
      -      'http://example.com/tasks',
      -      ['q' => 'test', '_content' => json_encode($data)],
      -      ['type' => 'json']
      -    );
      -
      -.. _http_client_request_options:
      -
      -Request Method Options
      -======================
      -
      -Each HTTP method takes an ``$options`` parameter which is used to provide
      -addition request information.  The following keys can be used in ``$options``:
      -
      -- ``headers`` - Array of additional headers
      -- ``cookie`` - Array of cookies to use.
      -- ``proxy`` - Array of proxy information.
      -- ``auth`` - Array of authentication data, the ``type`` key is used to delegate to
      -  an authentication strategy. By default Basic auth is used.
      -- ``ssl_verify_peer`` - defaults to ``true``. Set to ``false`` to disable SSL certification
      -  verification (not recommended).
      -- ``ssl_verify_peer_name`` - defaults to ``true``. Set to ``false`` to disable
      -  host name verification when verifying SSL certificates (not recommended).
      -- ``ssl_verify_depth`` - defaults to 5. Depth to traverse in the CA chain.
      -- ``ssl_verify_host`` - defaults to ``true``. Validate the SSL certificate against the host name.
      -- ``ssl_cafile`` - defaults to built in cafile. Overwrite to use custom CA bundles.
      -- ``timeout`` - Duration to wait before timing out in seconds.
      -- ``type`` - Send a request body in a custom content type. Requires ``$data`` to
      -  either be a string, or the ``_content`` option to be set when doing GET
      -  requests.
      -- ``redirect`` - Number of redirects to follow. Defaults to ``false``.
      -- ``curl`` - An array of additional curl options (if the curl adapter is used),
      -  for example, ``[CURLOPT_SSLKEY => 'key.pem']``.
      -
      -The options parameter is always the 3rd parameter in each of the HTTP methods.
      -They can also be used when constructing ``Client`` to create
      -:ref:`scoped clients `.
      -
      -Authentication
      -==============
      -
      -``Cake\Http\Client`` supports a few different authentication systems.  Different
      -authentication strategies can be added by developers. Auth strategies are called
      -before the request is sent, and allow headers to be added to the request
      -context.
      -
      -Using Basic Authentication
      ---------------------------
      -
      -An example of basic authentication::
      -
      -    $http = new Client();
      -    $response = $http->get('http://example.com/profile/1', [], [
      -      'auth' => ['username' => 'mark', 'password' => 'secret'],
      -    ]);
      -
      -By default ``Cake\Http\Client`` will use basic authentication if there is no
      -``'type'`` key in the auth option.
      -
      -Using Digest Authentication
      ----------------------------
      -
      -An example of basic authentication::
      -
      -    $http = new Client();
      -    $response = $http->get('http://example.com/profile/1', [], [
      -        'auth' => [
      -            'type' => 'digest',
      -            'username' => 'mark',
      -            'password' => 'secret',
      -            'realm' => 'myrealm',
      -            'nonce' => 'onetimevalue',
      -            'qop' => 1,
      -            'opaque' => 'someval',
      -        ],
      -    ]);
      -
      -By setting the 'type' key to 'digest', you tell the authentication subsystem to
      -use digest authentication. Digest authentication supports the following
      -algorithms:
      -
      -* MD5
      -* SHA-256
      -* SHA-512-256
      -* MD5-sess
      -* SHA-256-sess
      -* SHA-512-256-sess
      -
      -The algorithm will be automatically chosen based on the server challenge.
      -
      -OAuth 1 Authentication
      -----------------------
      -
      -Many modern web-services require OAuth authentication to access their APIs.
      -The included OAuth authentication assumes that you already have your consumer
      -key and consumer secret::
      -
      -    $http = new Client();
      -    $response = $http->get('http://example.com/profile/1', [], [
      -        'auth' => [
      -            'type' => 'oauth',
      -            'consumerKey' => 'bigkey',
      -            'consumerSecret' => 'secret',
      -            'token' => '...',
      -            'tokenSecret' => '...',
      -            'realm' => 'tickets',
      -        ],
      -    ]);
      -
      -OAuth 2 Authentication
      -----------------------
      -
      -Because OAuth2 is often a single header, there is not a specialized
      -authentication adapter. Instead you can create a client with the access token::
      -
      -    $http = new Client([
      -        'headers' => ['Authorization' => 'Bearer ' . $accessToken],
      -    ]);
      -    $response = $http->get('https://example.com/api/profile/1');
      -
      -Proxy Authentication
      ---------------------
      -
      -Some proxies require authentication to use them. Generally this authentication
      -is Basic, but it can be implemented by any authentication adapter.  By default
      -Http\\Client will assume Basic authentication, unless the type key is set::
      -
      -    $http = new Client();
      -    $response = $http->get('http://example.com/test.php', [], [
      -        'proxy' => [
      -            'username' => 'mark',
      -            'password' => 'testing',
      -            'proxy' => '127.0.0.1:8080',
      -        ],
      -    ]);
      -
      -The second proxy parameter must be a string with an IP or a domain without
      -protocol. The username and password information will be passed through the
      -request headers, while the proxy string will be passed through
      -`stream_context_create()
      -`_.
      -
      -.. _http_client_scoped_client:
      -
      -Creating Scoped Clients
      -=======================
      -
      -Having to re-type the domain name, authentication and proxy settings can become
      -tedious & error prone.  To reduce the chance for mistake and relieve some of the
      -tedium, you can create scoped clients::
      -
      -    // Create a scoped client.
      -    $http = new Client([
      -        'host' => 'api.example.com',
      -        'scheme' => 'https',
      -        'auth' => ['username' => 'mark', 'password' => 'testing'],
      -    ]);
      -
      -    // Do a request to api.example.com
      -    $response = $http->get('/test.php');
      -
      -If your scoped client only needs information from the URL you can use
      -``createFromUrl()``::
      -
      -    $http = Client::createFromUrl('https://api.example.com/v1/test');
      -
      -The above would create a client instance with the ``protocol``, ``host``, and
      -``basePath`` options set.
      -
      -The following information can be used when creating a scoped client:
      -
      -* host
      -* basePath
      -* scheme
      -* proxy
      -* auth
      -* port
      -* cookies
      -* timeout
      -* ssl_verify_peer
      -* ssl_verify_depth
      -* ssl_verify_host
      -
      -Any of these options can be overridden by specifying them when doing requests.
      -host, scheme, proxy, port are overridden in the request URL::
      -
      -    // Using the scoped client we created earlier.
      -    $response = $http->get('http://foo.com/test.php');
      -
      -The above will replace the domain, scheme, and port.  However, this request will
      -continue using all the other options defined when the scoped client was created.
      -See :ref:`http_client_request_options` for more information on the options
      -supported.
      -
      -Setting and Managing Cookies
      -============================
      -
      -Http\\Client can also accept cookies when making requests. In addition to
      -accepting cookies, it will also automatically store valid cookies set in
      -responses. Any response with cookies, will have them stored in the originating
      -instance of Http\\Client. The cookies stored in a Client instance are
      -automatically included in future requests to domain + path combinations that
      -match::
      -
      -    $http = new Client([
      -        'host' => 'cakephp.org'
      -    ]);
      -
      -    // Do a request that sets some cookies
      -    $response = $http->get('/');
      -
      -    // Cookies from the first request will be included
      -    // by default.
      -    $response2 = $http->get('/changelogs');
      -
      -You can always override the auto-included cookies by setting them in the
      -request's ``$options`` parameters::
      -
      -    // Replace a stored cookie with a custom value.
      -    $response = $http->get('/changelogs', [], [
      -        'cookies' => ['sessionid' => '123abc'],
      -    ]);
      -
      -You can add cookie objects to the client after creating it using the ``addCookie()``
      -method::
      -
      -    use Cake\Http\Cookie\Cookie;
      -
      -    $http = new Client([
      -        'host' => 'cakephp.org'
      -    ]);
      -    $http->addCookie(new Cookie('session', 'abc123'));
      -
      -Client Events
      -=============
      -
      -``Client`` will emit events when requests are sent. The
      -``HttpClient.beforeSend`` event is fired before a request is sent, and
      -``HttpClient.afterSend`` is fired after a request is sent. You can modify the
      -request, or set a response in a ``beforeSend`` listener. The ``afterSend`` event
      -is triggered for all requests, even those that have their responses set by
      -a ``beforeSend`` event.
      -
      -
      -.. _httpclient-response-objects:
      -
      -Response Objects
      -================
      -
      -.. php:namespace:: Cake\Http\Client
      -
      -.. php:class:: Response
      -
      -Response objects have a number of methods for inspecting the response data.
      -
      -Reading Response Bodies
      ------------------------
      -
      -You read the entire response body as a string::
      -
      -    // Read the entire response as a string.
      -    $response->getStringBody();
      -
      -You can also access the stream object for the response and use its methods::
      -
      -    // Get a Psr\Http\Message\StreamInterface containing the response body
      -    $stream = $response->getBody();
      -
      -    // Read a stream 100 bytes at a time.
      -    while (!$stream->eof()) {
      -        echo $stream->read(100);
      -    }
      -
      -.. _http-client-xml-json:
      -
      -Reading JSON and XML Response Bodies
      -------------------------------------
      -
      -Since JSON and XML responses are commonly used, response objects provide a way
      -to use accessors to read decoded data. JSON data is decoded into an array, while
      -XML data is decoded into a ``SimpleXMLElement`` tree::
      -
      -    // Get some XML
      -    $http = new Client();
      -    $response = $http->get('http://example.com/test.xml');
      -    $xml = $response->getXml();
      -
      -    // Get some JSON
      -    $http = new Client();
      -    $response = $http->get('http://example.com/test.json');
      -    $json = $response->getJson();
      -
      -The decoded response data is stored in the response object, so accessing it
      -multiple times has no additional cost.
      -
      -Accessing Response Headers
      ---------------------------
      -
      -You can access headers through a few different methods. Header names are always
      -treated as case-insensitive values when accessing them through methods::
      -
      -    // Get all the headers as an associative array.
      -    $response->getHeaders();
      -
      -    // Get a single header as an array.
      -    $response->getHeader('content-type');
      -
      -    // Get a header as a string
      -    $response->getHeaderLine('content-type');
      -
      -    // Get the response encoding
      -    $response->getEncoding();
      -
      -Accessing Cookie Data
      ----------------------
      -
      -You can read cookies with a few different methods depending on how much
      -data you need about the cookies::
      -
      -    // Get all cookies (full data)
      -    $response->getCookies();
      -
      -    // Get a single cookie's value.
      -    $response->getCookie('session_id');
      -
      -    // Get a the complete data for a single cookie
      -    // includes value, expires, path, httponly, secure keys.
      -    $response->getCookieData('session_id');
      -
      -Checking the Status Code
      -------------------------
      -
      -Response objects provide a few methods for checking status codes::
      -
      -    // Was the response a 20x
      -    $response->isOk();
      -
      -    // Was the response a 30x
      -    $response->isRedirect();
      -
      -    // Get the status code
      -    $response->getStatusCode();
      -
      -Changing Transport Adapters
      -===========================
      -
      -By default ``Http\Client`` will prefer using a ``curl`` based transport adapter.
      -If the curl extension is not available a stream based adapter will be used
      -instead. You can force select a transport adapter using a constructor option::
      -
      -    use Cake\Http\Client\Adapter\Stream;
      -
      -    $http = new Client(['adapter' => Stream::class]);
      -
      -Events
      -======
      -
      -The HTTP client triggers couple of events before and after sending a request
      -which allows you to modify either the request or response or do other tasks like
      -caching, logging etc.
      -
      -HttpClient.beforeSend
      ----------------------
      -
      -::
      -
      -    // Somewhere before calling one of the HTTP client's methods which makes a request
      -    $http->getEventManager()->on(
      -        'HttpClient.beforeSend',
      -        function (
      -            \Cake\Http\Client\ClientEvent $event,
      -            \Cake\Http\Client\Request $request,
      -            array $adapterOptions,
      -            int $redirects
      -        ) {
      -            // Modify the request
      -            $event->setRequest(....);
      -            // Modify the adapter options
      -            $event->setAdapterOptions(....);
      -
      -            // Skip making the actual request by returning a response.
      -            // You can use $event->setResult($response) to achieve the same.
      -            return new \Cake\Http\Client\Response(body: 'something');
      -        }
      -    );
      -
      -HttpClient.afterSend
      ----------------------
      -
      -::
      -
      -    // Somewhere before calling one of the HTTP client's methods which makes a request
      -    $http->getEventManager()->on(
      -        'HttpClient.afterSend',
      -        function (
      -            \Cake\Http\Client\ClientEvent $event,
      -            \Cake\Http\Client\Request $request,
      -            array $adapterOptions,
      -            int $redirects,
      -            bool $requestSent // Indicates whether the request was actually sent
      -                              // or response returned from ``beforeSend`` event
      -        ) {
      -            // Get the response
      -            $response = $event->getResponse();
      -
      -            // Return a new/modified response.
      -            // You can use $event->setResult($response) to achieve the same.
      -            return new \Cake\Http\Client\Response(body: 'something');
      -        }
      -    );
      -
      -.. _httpclient-testing:
      -
      -Testing
      -=======
      -
      -.. php:namespace:: Cake\Http\TestSuite
      -
      -.. php:trait:: HttpClientTrait
      -
      -In tests you will often want to create mock responses to external APIs. You can
      -use the ``HttpClientTrait`` to define responses to the requests your application
      -is making::
      -
      -    use Cake\Http\TestSuite\HttpClientTrait;
      -    use Cake\TestSuite\TestCase;
      -
      -    class CartControllerTests extends TestCase
      -    {
      -        use HttpClientTrait;
      -
      -        public function testCheckout()
      -        {
      -            // Mock a POST request that will be made.
      -            $this->mockClientPost(
      -                'https://example.com/process-payment',
      -                $this->newClientResponse(200, [], json_encode(['ok' => true]))
      -            );
      -            $this->post("/cart/checkout");
      -            // Do assertions.
      -        }
      -    }
      -
      -There are methods to mock the most commonly used HTTP methods::
      -
      -    $this->mockClientGet(/* ... */);
      -    $this->mockClientPatch(/* ... */);
      -    $this->mockClientPost(/* ... */);
      -    $this->mockClientPut(/* ... */);
      -    $this->mockClientDelete(/* ... */);
      -
      -.. php:method:: newClientResponse(int $code = 200, array $headers = [], string $body = '')
      -
      -As seen above you can use the ``newClientResponse()`` method to create responses
      -for the requests your application will make. The headers need to be a list of
      -strings::
      -
      -    $headers = [
      -        'Content-Type: application/json',
      -        'Connection: close',
      -    ];
      -    $response = $this->newClientResponse(200, $headers, $body)
      -
      -.. meta::
      -    :title lang=en: HttpClient
      -    :keywords lang=en: array name,array data,query parameter,query string,php class,string query,test type,string data,google,query results,webservices,apis,parameters,cakephp,meth,search results
      diff --git a/en/core-libraries/inflector.rst b/en/core-libraries/inflector.rst
      deleted file mode 100644
      index bfa0215de3..0000000000
      --- a/en/core-libraries/inflector.rst
      +++ /dev/null
      @@ -1,187 +0,0 @@
      -Inflector
      -#########
      -
      -.. php:namespace:: Cake\Utility
      -
      -.. php:class:: Inflector
      -
      -The Inflector class takes a string and can manipulate it to handle word
      -variations such as pluralization or camelizing and is normally accessed
      -statically. Example:
      -``Inflector::pluralize('example')`` returns "examples".
      -
      -You can try out the inflections online at `inflector.cakephp.org
      -`_ or `sandbox.dereuromark.de
      -`_.
      -
      -.. _inflector-methods-summary:
      -
      -Summary of Inflector Methods and Their Output
      -=============================================
      -
      -Quick summary of the Inflector built-in methods and the results they output
      -when provided a multi-word argument:
      -
      -+-------------------+---------------+---------------+
      -| Method            | Argument      | Output        |
      -+===================+===============+===============+
      -| ``pluralize()``   | BigApple      | BigApples     |
      -+                   +---------------+---------------+
      -|                   | big_apple     | big_apples    |
      -+-------------------+---------------+---------------+
      -| ``singularize()`` | BigApples     | BigApple      |
      -+                   +---------------+---------------+
      -|                   | big_apples    | big_apple     |
      -+-------------------+---------------+---------------+
      -| ``camelize()``    | big_apples    | BigApples     |
      -+                   +---------------+---------------+
      -|                   | big apple     | BigApple      |
      -+-------------------+---------------+---------------+
      -| ``underscore()``  | BigApples     | big_apples    |
      -+                   +---------------+---------------+
      -|                   | Big Apples    | big apples    |
      -+-------------------+---------------+---------------+
      -| ``humanize()``    | big_apples    | Big Apples    |
      -+                   +---------------+---------------+
      -|                   | bigApple      | BigApple      |
      -+-------------------+---------------+---------------+
      -| ``classify()``    | big_apples    | BigApple      |
      -+                   +---------------+---------------+
      -|                   | big apple     | BigApple      |
      -+-------------------+---------------+---------------+
      -| ``dasherize()``   | BigApples     | big-apples    |
      -+                   +---------------+---------------+
      -|                   | big apple     | big apple     |
      -+-------------------+---------------+---------------+
      -| ``tableize()``    | BigApple      | big_apples    |
      -+                   +---------------+---------------+
      -|                   | Big Apple     | big apples    |
      -+-------------------+---------------+---------------+
      -| ``variable()``    | big_apple     | bigApple      |
      -+                   +---------------+---------------+
      -|                   | big apples    | bigApples     |
      -+-------------------+---------------+---------------+
      -
      -Creating Plural & Singular Forms
      -================================
      -
      -.. php:staticmethod:: singularize($singular)
      -.. php:staticmethod:: pluralize($singular)
      -
      -Both ``pluralize`` and ``singularize()`` work on most English nouns. If you need
      -to support other languages, you can use :ref:`inflection-configuration` to
      -customize the rules used::
      -
      -    // Apples
      -    echo Inflector::pluralize('Apple');
      -
      -.. note::
      -
      -    ``pluralize()`` should not be used on a noun that is already in its plural form.
      -
      -.. code-block:: php
      -
      -    // Person
      -    echo Inflector::singularize('People');
      -
      -.. note::
      -
      -    ``singularize()`` should not be used on a noun that is already in its singular form.
      -
      -Creating CamelCase and under_scored Forms
      -=========================================
      -
      -.. php:staticmethod:: camelize($underscored)
      -.. php:staticmethod:: underscore($camelCase)
      -
      -These methods are useful when creating class names, or property names::
      -
      -    // ApplePie
      -    Inflector::camelize('Apple_pie')
      -
      -    // apple_pie
      -    Inflector::underscore('ApplePie');
      -
      -It should be noted that underscore will only convert camelCase formatted words.
      -Words that contains spaces will be lower-cased, but will not contain an
      -underscore.
      -
      -Creating Human Readable Forms
      -=============================
      -
      -.. php:staticmethod:: humanize($underscored)
      -
      -This method is useful when converting underscored forms into "Title Case" forms
      -for human readable values::
      -
      -    // Apple Pie
      -    Inflector::humanize('apple_pie');
      -
      -Creating Table and Class Name Forms
      -===================================
      -
      -.. php:staticmethod:: classify($underscored)
      -.. php:staticmethod:: dasherize($dashed)
      -.. php:staticmethod:: tableize($camelCase)
      -
      -When generating code, or using CakePHP's conventions you may need to inflect
      -table names or class names::
      -
      -    // UserProfileSetting
      -    Inflector::classify('user_profile_settings');
      -
      -    // user-profile-setting
      -    Inflector::dasherize('UserProfileSetting');
      -
      -    // user_profile_settings
      -    Inflector::tableize('UserProfileSetting');
      -
      -Creating Variable Names
      -=======================
      -
      -.. php:staticmethod:: variable($underscored)
      -
      -Variable names are often useful when doing meta-programming tasks that involve
      -generating code or doing work based on conventions::
      -
      -    // applePie
      -    Inflector::variable('apple_pie');
      -
      -
      -.. _inflection-configuration:
      -
      -Inflection Configuration
      -========================
      -
      -CakePHP's naming conventions can be really nice - you can name your database
      -table ``big_boxes``, your model ``BigBoxes``, your controller
      -``BigBoxesController``, and everything just works together automatically. The
      -way CakePHP knows how to tie things together is by *inflecting* the words
      -between their singular and plural forms.
      -
      -There are occasions (especially for our non-English speaking friends) where you
      -may run into situations where CakePHP's inflector (the class that pluralizes,
      -singularizes, camelCases, and under\_scores) might not work as you'd like. If
      -CakePHP won't recognize your Foci or Fish, you can tell CakePHP about your
      -special cases.
      -
      -Loading Custom Inflections
      ---------------------------
      -
      -.. php:staticmethod:: rules($type, $rules, $reset = false)
      -
      -Define new inflection and transliteration rules for Inflector to use.  Often,
      -this method is used in your **config/bootstrap.php**::
      -
      -    Inflector::rules('singular', ['/^(bil)er$/i' => '\1', '/^(inflec|contribu)tors$/i' => '\1ta']);
      -    Inflector::rules('uninflected', ['singulars']);
      -    Inflector::rules('irregular', ['phylum' => 'phyla']); // The key is singular form, value is plural form
      -
      -The supplied rules will be merged into the respective inflection sets defined in
      -``Cake/Utility/Inflector``, with the added rules taking precedence over the core
      -rules. You can use ``Inflector::reset()`` to clear rules and restore the
      -original Inflector state.
      -
      -.. meta::
      -    :title lang=en: Inflector
      -    :keywords lang=en: apple orange,word variations,apple pie,person man,latin versions,profile settings,php class,initial state,puree,slug,apples,oranges,user profile,underscore
      diff --git a/en/core-libraries/internationalization-and-localization.rst b/en/core-libraries/internationalization-and-localization.rst
      deleted file mode 100644
      index 8747c58c6c..0000000000
      --- a/en/core-libraries/internationalization-and-localization.rst
      +++ /dev/null
      @@ -1,701 +0,0 @@
      -Internationalization & Localization
      -###################################
      -
      -One of the best ways for an application to reach a larger audience is to cater
      -to multiple languages. This can often prove to be a daunting task, but the
      -internationalization and localization features in CakePHP make it much easier.
      -
      -First, it's important to understand some terminology. *Internationalization*
      -refers to the ability of an application to be localized. The term *localization*
      -refers to the adaptation of an application to meet specific language (or
      -culture) requirements (i.e. a "locale"). Internationalization and localization
      -are often abbreviated as i18n and l10n respectively; 18 and 10 are the number
      -of characters between the first and last character.
      -
      -Setting Up Translations
      -=======================
      -
      -There are only a few steps to go from a single-language application to a
      -multi-lingual application, the first of which is to make use of the
      -:php:func:`__()` function in your code. Below is an example of some code for a
      -single-language application::
      -
      -    

      Popular Articles

      - -To internationalize your code, all you need to do is to wrap strings in -:php:func:`__()` like so:: - -

      - -Doing nothing else, these two code examples are functionally identical - they -will both send the same content to the browser. The :php:func:`__()` function -will translate the passed string if a translation is available, or return it -unmodified. - -Language Files --------------- - -Translations can be made available by using language files stored in the -application. The default format for CakePHP translation files is the -`Gettext `_ format. Files need to be -placed under **resources/locales/** and within this directory, there should be a -subfolder for each language the application needs to support:: - - resources/ - locales/ - en_US/ - default.po - en_GB/ - default.po - validation.po - es/ - default.po - -The default domain is 'default', therefore the locale folder should at least -contain the **default.po** file as shown above. A domain refers to any arbitrary -grouping of translation messages. When no group is used, then the default group -is selected. - -The core strings messages extracted from the CakePHP library can be stored -separately in a file named **cake.po** in **resources/locales/**. -The `CakePHP localized library `_ houses -translations for the client-facing translated strings in the core (the cake -domain). To use these files, link or copy them into their expected location: -**resources/locales//cake.po**. If your locale is incomplete or incorrect, -please submit a PR in this repository to fix it. - -Plugins can also contain translation files, the convention is to use the -``under_scored`` version of the plugin name as the domain for the translation -messages:: - - MyPlugin/ - resources/ - locales/ - fr/ - my_plugin.po - additional.po - de/ - my_plugin.po - -Translation folders can be the two or three letter ISO code of the language or -the full ICU locale name such as ``fr_FR``, ``es_AR``, ``da_DK`` which contains -both the language and the country where it is spoken. - -See https://www.localeplanet.com/icu/ for the full list of locales. - -.. versionchanged:: 4.5.0 - As of 4.5.0 plugins can contain multiple translation domains. Use - ``MyPlugin.additional`` to reference plugin domains. - -An example translation file could look like this: - -.. code-block:: pot - - msgid "My name is {0}" - msgstr "Je m'appelle {0}" - - msgid "I'm {0,number} years old" - msgstr "J'ai {0,number} ans" - -.. note:: - Translations are cached - Make sure that you always clear the cache after - making changes to translations! You can either use the - :doc:`cache tool ` and run for example - ``bin/cake cache clear _cake_core_``, or manually clear the ``tmp/cache/persistent`` - folder (if using file based caching). - -Extract Pot Files with I18n Shell ---------------------------------- - -To create the pot files from `__()` and other internationalized types of -messages that can be found in the application code, you can use the i18n command. -Please read the :doc:`following chapter ` to -learn more. - -Setting the Default Locale --------------------------- - -The default locale can be set in your **config/app.php** file by setting -``App.defaultLocale``:: - - 'App' => [ - ... - 'defaultLocale' => env('APP_DEFAULT_LOCALE', 'en_US'), - ... - ] - -This will control several aspects of the application, including the default -translations language, the date format, number format and currency whenever any -of those is displayed using the localization libraries that CakePHP provides. - -Changing the Locale at Runtime ------------------------------- - -To change the language for translated strings you can call this method:: - - use Cake\I18n\I18n; - - I18n::setLocale('de_DE'); - -This will also change how numbers and dates are formatted when using one of the -localization tools. - -Using Translation Functions -=========================== - -CakePHP provides several functions that will help you internationalize your -application. The most frequently used one is :php:func:`__()`. This function -is used to retrieve a single translation message or return the same string if no -translation was found:: - - echo __('Popular Articles'); - -If you need to group your messages, for example, translations inside a plugin, -you can use the :php:func:`__d()` function to fetch messages from another -domain:: - - echo __d('my_plugin', 'Trending right now'); - -.. note:: - - If you want to translate plugins that are vendor namespaced, you must use - the domain string ``vendor/plugin_name``. But the related language file - will become ``plugins///resources/locales//plugin_name.po`` - inside your plugin folder. - -Sometimes translations strings can be ambiguous for people translating them. -This can happen if two strings are identical but refer to different things. For -example, 'letter' has multiple meanings in English. To solve that problem, you -can use the :php:func:`__x()` function:: - - echo __x('written communication', 'He read the first letter'); - - echo __x('alphabet learning', 'He read the first letter'); - -The first argument is the context of the message and the second is the message -to be translated. - -.. code-block:: pot - - msgctxt "written communication" - msgid "He read the first letter" - msgstr "Er las den ersten Brief" - -Using Variables in Translation Messages ---------------------------------------- - -Translation functions allow you to interpolate variables into the messages using -special markers defined in the message itself or in the translated string:: - - echo __("Hello, my name is {0}, I'm {1} years old", ['Sara', 12]); - -Markers are numeric, and correspond to the keys in the passed array. You can -also pass variables as independent arguments to the function:: - - echo __("Small step for {0}, Big leap for {1}", 'Man', 'Humanity'); - -All translation functions support placeholder replacements:: - - __d('validation', 'The field {0} cannot be left empty', 'Name'); - - __x('alphabet', 'He read the letter {0}', 'Z'); - -The ``'`` (single quote) character acts as an escape code in translation -messages. Any variables between single quotes will not be replaced and is -treated as literal text. For example:: - - __("This variable '{0}' be replaced.", 'will not'); - -By using two adjacent quotes your variables will be replaced properly:: - - __("This variable ''{0}'' be replaced.", 'will'); - -These functions take advantage of the -`ICU MessageFormatter `_ -so you can translate messages and localize dates, numbers and currency at the -same time:: - - echo __( - 'Hi {0}, your balance on the {1,date} is {2,number,currency}', - ['Charles', new DateTime('2014-01-13 11:12:00'), 1354.37] - ); - - // Returns - Hi Charles, your balance on the Jan 13, 2014, 11:12 AM is $ 1,354.37 - -Numbers in placeholders can be formatted as well with fine grain control of the -output:: - - echo __( - 'You have traveled {0,number} kilometers in {1,number,integer} weeks', - [5423.344, 5.1] - ); - - // Returns - You have traveled 5,423.34 kilometers in 5 weeks - - echo __('There are {0,number,#,###} people on earth', 6.1 * pow(10, 8)); - - // Returns - There are 6,100,000,000 people on earth - -This is the list of formatter specifiers you can put after the word ``number``: - -* ``integer``: Removes the decimal part -* ``currency``: Puts the locale currency symbol and rounds decimals -* ``percent``: Formats the number as a percentage - -Dates can also be formatted by using the word ``date`` after the placeholder -number. A list of extra options follows: - -* ``short`` -* ``medium`` -* ``long`` -* ``full`` - -The word ``time`` after the placeholder number is also accepted and it -understands the same options as ``date``. - -You can also use named placeholders like ``{name}`` in the message strings. -When using named placeholders, pass the placeholder and replacement in an array using key/value pairs, -for example:: - - // echos: Hi. My name is Sara. I'm 12 years old. - echo __("Hi. My name is {name}. I'm {age} years old.", ['name' => 'Sara', 'age' => 12]); - -Plurals -------- - -One crucial part of internationalizing your application is getting your messages -pluralized correctly depending on the language they are shown. CakePHP provides -a couple ways to correctly select plurals in your messages. - -Using ICU Plural Selection -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The first one is taking advantage of the ``ICU`` message format that comes by -default in the translation functions. In the translations file you could have -the following strings - -.. code-block:: pot - - msgid "{0,plural,=0{No records found} =1{Found 1 record} other{Found # records}}" - msgstr "{0,plural,=0{Ningún resultado} =1{1 resultado} other{# resultados}}" - - msgid "{placeholder,plural,=0{No records found} =1{Found 1 record} other{Found {1} records}}" - msgstr "{placeholder,plural,=0{Ningún resultado} =1{1 resultado} other{{1} resultados}}" - -And in the application use the following code to output either of the -translations for such string:: - - __('{0,plural,=0{No records found }=1{Found 1 record} other{Found # records}}', [0]); - - // Returns "Ningún resultado" as the argument {0} is 0 - - __('{0,plural,=0{No records found} =1{Found 1 record} other{Found # records}}', [1]); - - // Returns "1 resultado" because the argument {0} is 1 - - __('{placeholder,plural,=0{No records found} =1{Found 1 record} other{Found {1} records}}', [0, 'many', 'placeholder' => 2]) - - // Returns "many resultados" because the argument {placeholder} is 2 and - // argument {1} is 'many' - -A closer look to the format we just used will make it evident how messages are -built:: - - { [count placeholder],plural, case1{message} case2{message} case3{...} ... } - -The ``[count placeholder]`` can be the array key number of any of the variables -you pass to the translation function. It will be used for selecting the correct -plural form. - -Note that to reference ``[count placeholder]`` within ``{message}`` you have to -use ``#``. - -You can of course use simpler message ids if you don't want to type the full -plural selection sequence in your code - -.. code-block:: pot - - msgid "search.results" - msgstr "{0,plural,=0{Ningún resultado} =1{1 resultado} other{{1} resultados}}" - -Then use the new string in your code:: - - __('search.results', [2, 2]); - - // Returns: "2 resultados" - -The latter version has the downside that there is a need to have a translation -messages file even for the default language, but has the advantage that it makes -the code more readable and leaves the complicated plural selection strings in -the translation files. - -Sometimes using direct number matching in plurals is impractical. For example, -languages like Arabic require a different plural when you refer -to few things and other plural form for many things. In those cases you can -use the ICU matching aliases. Instead of writing:: - - =0{No results} =1{...} other{...} - -You can do:: - - zero{No Results} one{One result} few{...} many{...} other{...} - -Make sure you read the -`Language Plural Rules Guide `_ -to get a complete overview of the aliases you can use for each language. - -Using Gettext Plural Selection -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The second plural selection format accepted is using the built-in capabilities -of Gettext. In this case, plurals will be stored in the ``.po`` -file by creating a separate message translation line per plural form: - -.. code-block:: pot - - # One message identifier for singular - msgid "One file removed" - # Another one for plural - msgid_plural "{0} files removed" - # Translation in singular - msgstr[0] "Un fichero eliminado" - # Translation in plural - msgstr[1] "{0} ficheros eliminados" - -When using this other format, you are required to use another translation -function:: - - // Returns: "10 ficheros eliminados" - $count = 10; - __n('One file removed', '{0} files removed', $count, $count); - - // It is also possible to use it inside a domain - __dn('my_plugin', 'One file removed', '{0} files removed', $count, $count); - -The number inside ``msgstr[]`` is the number assigned by Gettext for the plural -form of the language. Some languages have more than two plural forms, for -example Croatian: - -.. code-block:: pot - - msgid "One file removed" - msgid_plural "{0} files removed" - msgstr[0] "{0} datoteka je uklonjena" - msgstr[1] "{0} datoteke su uklonjene" - msgstr[2] "{0} datoteka je uklonjeno" - -Please visit the `Launchpad languages page `_ -for a detailed explanation of the plural form numbers for each language. - -Creating Your Own Translators -============================= - -If you need to diverge from CakePHP conventions regarding where and how -translation messages are stored, you can create your own translation message -loader. The easiest way to create your own translator is by defining a loader -for a single domain and locale:: - - use Cake\I18n\Package; - // Prior to 4.2 you need to use Aura\Intl\Package - - I18n::setTranslator('animals', function () { - $package = new Package( - 'default', // The formatting strategy (ICU) - 'default' // The fallback domain - ); - $package->setMessages([ - 'Dog' => 'Chien', - 'Cat' => 'Chat', - 'Bird' => 'Oiseau' - ... - ]); - - return $package; - }, 'fr_FR'); - -The above code can be added to your **config/bootstrap.php** so that -translations can be found before any translation function is used. The absolute -minimum that is required for creating a translator is that the loader function -should return a ``Cake\I18n\Package`` object (prior to 4.2 it should be an ``Aura\Intl\Package`` object). -Once the code is in place you can use the translation functions as usual:: - - I18n::setLocale('fr_FR'); - __d('animals', 'Dog'); // Returns "Chien" - -As you see, ``Package`` objects take translation messages as an array. You can -pass the ``setMessages()`` method however you like: with inline code, including -another file, calling another function, etc. CakePHP provides a few loader -functions you can reuse if you just need to change where messages are loaded. -For example, you can still use **.po** files, but loaded from another location:: - - use Cake\I18n\MessagesFileLoader as Loader; - - // Load messages from resources/locales/folder/sub_folder/filename.po - I18n::setTranslator( - 'animals', - new Loader('filename', 'folder/sub_folder', 'po'), - 'fr_FR' - ); - -Creating Message Parsers ------------------------- - -It is possible to continue using the same conventions CakePHP uses, but use -a message parser other than ``PoFileParser``. For example, if you wanted to load -translation messages using ``YAML``, you will first need to created the parser -class:: - - namespace App\I18n\Parser; - - class YamlFileParser - { - public function parse($file) - { - return yaml_parse_file($file); - } - } - -The file should be created in the **src/I18n/Parser** directory of your -application. Next, create the translations file under -**resources/locales/fr_FR/animals.yaml** - -.. code-block:: yaml - - Dog: Chien - Cat: Chat - Bird: Oiseau - -And finally, configure the translation loader for the domain and locale:: - - use Cake\I18n\MessagesFileLoader as Loader; - - I18n::setTranslator( - 'animals', - new Loader('animals', 'fr_FR', 'yaml'), - 'fr_FR' - ); - -.. _creating-generic-translators: - -Creating Generic Translators ----------------------------- - -Configuring translators by calling ``I18n::setTranslator()`` for each domain and -locale you need to support can be tedious, specially if you need to support more -than a few different locales. To avoid this problem, CakePHP lets you define -generic translator loaders for each domain. - -Imagine that you wanted to load all translations for the default domain and for -any language from an external service:: - - use Cake\I18n\Package; - // Prior to 4.2 you need to use Aura\Intl\Package - - I18n::config('default', function ($domain, $locale) { - $locale = Locale::parseLocale($locale); - $lang = $locale['language']; - $messages = file_get_contents("http://example.com/translations/$lang.json"); - - return new Package( - 'default', // Formatter - null, // Fallback (none for default domain) - json_decode($messages, true) - ) - }); - -The above example calls an example external service to load a JSON file with the -translations and then just build a ``Package`` object for any locale that is -requested in the application. - -If you'd like to change how packages are loaded for all packages, that don't -have specific loaders set you can replace the fallback package loader by using -the ``_fallback`` package:: - - I18n::config('_fallback', function ($domain, $locale) { - // Custom code that yields a package here. - }); - -Plurals and Context in Custom Translators ------------------------------------------ - -The arrays used for ``setMessages()`` can be crafted to instruct the translator -to store messages under different domains or to trigger Gettext-style plural -selection. The following is an example of storing translations for the same key -in different contexts:: - - [ - 'He reads the letter {0}' => [ - 'alphabet' => 'Él lee la letra {0}', - 'written communication' => 'Él lee la carta {0}', - ], - ] - -Similarly, you can express Gettext-style plurals using the messages array by -having a nested array key per plural form:: - - [ - 'I have read one book' => 'He leído un libro', - 'I have read {0} books' => [ - 'He leído un libro', - 'He leído {0} libros', - ], - ] - -Using Different Formatters --------------------------- - -In previous examples we have seen that Packages are built using ``default`` as -first argument, and it was indicated with a comment that it corresponded to the -formatter to be used. Formatters are classes responsible for interpolating -variables in translation messages and selecting the correct plural form. - -If you're dealing with a legacy application, or you don't need the power offered -by the ICU message formatting, CakePHP also provides the ``sprintf`` formatter:: - - return Package('sprintf', 'fallback_domain', $messages); - -The messages to be translated will be passed to the ``sprintf()`` function for -interpolating the variables:: - - __('Hello, my name is %s and I am %d years old', 'José', 29); - -It is possible to set the default formatter for all translators created by -CakePHP before they are used for the first time. This does not include manually -created translators using the ``setTranslator()`` and ``config()`` methods:: - - I18n::defaultFormatter('sprintf'); - -Localizing Dates and Numbers -============================ - -When outputting Dates and Numbers in your application, you will often need that -they are formatted according to the preferred format for the country or region -that you wish your page to be displayed. - -In order to change how dates and numbers are displayed you just need to change -the current locale setting and use the right classes:: - - use Cake\I18n\I18n; - use Cake\I18n\DateTime; - use Cake\I18n\Number; - - I18n::setLocale('fr-FR'); - - $date = new DateTime('2015-04-05 23:00:00'); - - echo $date; // Displays 05/04/2015 23:00 - - echo Number::format(524.23); // Displays 524,23 - -Make sure you read the :doc:`/core-libraries/time` and :doc:`/core-libraries/number` -sections to learn more about formatting options. - -By default dates returned for the ORM results use the ``Cake\I18n\DateTime`` class, -so displaying them directly in you application will be affected by changing the -current locale. - -.. _parsing-localized-dates: - -Parsing Localized Datetime Data -------------------------------- - -When accepting localized data from the request, it is nice to accept datetime -information in a user's localized format. In a controller, or -:doc:`/controllers/middleware` you can configure the Date, Time, and -DateTime types to parse localized formats:: - - use Cake\Database\TypeFactory; - - // Enable default locale format parsing. - TypeFactory::build('datetime')->useLocaleParser(); - - // Configure a custom datetime format parser format. - TypeFactory::build('datetime')->useLocaleParser()->setLocaleFormat('dd-M-y'); - - // You can also use IntlDateFormatter constants. - TypeFactory::build('datetime')->useLocaleParser() - ->setLocaleFormat([IntlDateFormatter::SHORT, -1]); - -The default parsing format is the same as the default string format. - -.. _converting-request-data-from-user-timezone: - -Converting Request Data from the User's Timezone ------------------------------------------------- - -When handling data from users in different timezones you will need to convert -the datetimes in request data into your application's timezone. You can use -``setUserTimezone()`` from a controller or :doc:`/controllers/middleware` to -make this process simpler:: - - // Set the user's timezone - TypeFactory::build('datetime')->setUserTimezone($user->timezone); - -Once set, when your application creates or updates entities from request data, -the ORM will automatically convert datetime values from the user's timezone into -your application's timezone. This ensures that your application is always -working in the timezone defined in ``App.defaultTimezone``. - -If your application handles datetime information in a number of actions you can -use a middleware to define both timezone conversion and locale parsing:: - - namespace App\Middleware; - - use Cake\Database\TypeFactory; - use Psr\Http\Message\ResponseInterface; - use Psr\Http\Message\ServerRequestInterface; - use Psr\Http\Server\MiddlewareInterface; - use Psr\Http\Server\RequestHandlerInterface; - - class DatetimeMiddleware implements MiddlewareInterface - { - public function process( - ServerRequestInterface $request, - RequestHandlerInterface $handler - ): ResponseInterface { - // Get the user from the request. - // This example assumes your user entity has a timezone attribute. - $user = $request->getAttribute('identity'); - if ($user) { - TypeFactory::build('datetime') - ->useLocaleParser() - ->setUserTimezone($user->timezone); - } - - return $handler->handle($request); - } - } - -Automatically Choosing the Locale Based on Request Data -======================================================= - -By using the ``LocaleSelectorMiddleware`` in your application, CakePHP will -automatically set the locale based on the current user:: - - // in src/Application.php - use Cake\I18n\Middleware\LocaleSelectorMiddleware; - - // Update the middleware function, adding the new middleware - public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue - { - // Add middleware and set the valid locales - $middlewareQueue->add(new LocaleSelectorMiddleware(['en_US', 'fr_FR'])); - // To accept any locale header value - $middlewareQueue->add(new LocaleSelectorMiddleware(['*'])); - } - -The ``LocaleSelectorMiddleware`` will use the ``Accept-Language`` header to -automatically set the user's preferred locale. You can use the locale list -option to restrict which locales will automatically be used. - -Translate Content/Entities -========================== - -If you want to translate content/entities then you should look at the :doc:`Translate Behavior `. - -.. meta:: - :title lang=en: Internationalization & Localization - :keywords lang=en: internationalization localization,internationalization and localization,language application,gettext,l10n,pot,i18n,translation,languages diff --git a/en/core-libraries/logging.rst b/en/core-libraries/logging.rst deleted file mode 100644 index 0326d1f264..0000000000 --- a/en/core-libraries/logging.rst +++ /dev/null @@ -1,551 +0,0 @@ -Logging -####### - -While CakePHP core Configure Class settings can really help you see -what's happening under the hood, there are certain times that -you'll need to log data to the disk in order to find out what's -going on. With technologies like SOAP, AJAX, and REST APIs, debugging can be -rather difficult. - -Logging can also be a way to find out what's been going on in your -application over time. What search terms are being used? What sorts -of errors are my users being shown? How often is a particular query -being executed? - -Logging data in CakePHP is done with the ``log()`` function. It is provided by the -``LogTrait``, which is the common ancestor for many CakePHP classes. If the -context is a CakePHP class (Controller, Component, View,...), you can log your -data. You can also use ``Log::write()`` directly. See :ref:`writing-to-logs`. - -.. _log-configuration: - -Logging Configuration -===================== - -Configuring ``Log`` should be done during your application's bootstrap phase. -The **config/app.php** file is intended for just this. You can define -as many or as few loggers as your application needs. Loggers should be -configured using :php:class:`Cake\\Log\\Log`. An example would be:: - - use Cake\Log\Engine\FileLog; - use Cake\Log\Log; - - // Classname using logger 'class' constant - Log::setConfig('info', [ - 'className' => FileLog::class, - 'path' => LOGS, - 'levels' => ['info'], - 'file' => 'info', - ]); - - // Short classname - Log::setConfig('debug', [ - 'className' => 'File', - 'path' => LOGS, - 'levels' => ['notice', 'debug'], - 'file' => 'debug', - ]); - - // Fully namespaced name. - Log::setConfig('error', [ - 'className' => 'Cake\Log\Engine\FileLog', - 'path' => LOGS, - 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'], - 'file' => 'error', - ]); - -The above creates three loggers, named ``info``, ``debug`` and ``error``. -Each is configured to handle different levels of messages. They also store their -log messages in separate files, so we can separate debug/notice/info logs -from more serious errors. See the section on :ref:`logging-levels` for more -information on the different levels and what they mean. - -Once a configuration is created you cannot change it. Instead you should drop -the configuration and re-create it using :php:meth:`Cake\\Log\\Log::drop()` and -:php:meth:`Cake\\Log\\Log::setConfig()`. - -It is also possible to create loggers by providing a closure. This is useful -when you need full control over how the logger object is built. The closure -has to return the constructed logger instance. For example:: - - Log::setConfig('special', function () { - return new \Cake\Log\Engine\FileLog(['path' => LOGS, 'file' => 'log']); - }); - -Configuration options can also be provided as a :term:`DSN` string. This is -useful when working with environment variables or :term:`PaaS` providers:: - - Log::setConfig('error', [ - 'url' => 'file:///full/path/to/logs/?levels[]=warning&levels[]=error&file=error', - ]); - -.. warning:: - If you do not configure logging engines, log messages will not be stored. - -Error and Exception Logging -=========================== - -Errors and Exceptions can also be logged. By configuring the corresponding -values in your **config/app.php** file. Errors will be displayed when debug is -``true`` and logged when debug is ``false``. To log uncaught exceptions, set the -``log`` option to ``true``. See :doc:`/development/configuration` for more -information. - -.. _writing-to-logs: - -Writing to Logs -=============== - -Writing to the log files can be done in two different ways. The first -is to use the static :php:meth:`Cake\\Log\\Log::write()` method:: - - Log::write('debug', 'Something did not work'); - -The second is to use the ``log()`` shortcut function available on any -class using the ``LogTrait``. Calling ``log()`` will internally call -``Log::write()``:: - - // Executing this inside a class using LogTrait - $this->log('Something did not work!', 'debug'); - -All configured log streams are written to sequentially each time -:php:meth:`Cake\\Log\\Log::write()` is called. If you have not configured any -logging engines ``log()`` will return ``false`` and no log messages will be -written. - -Using Placeholders in Messages ------------------------------- - -If you need to log dynamically defined data, you can use placeholders in your -log messages and provide an array of key/value pairs in the ``$context`` -parameter:: - - // Will log `Could not process for userid=1` - Log::write('error', 'Could not process for userid={user}', ['user' => $user->id]); - -Placeholders that do not have keys defined will not be replaced. If you need to -use a literal braced word, you must escape the placeholder:: - - // Will log `No {replace}` - Log::write('error', 'No \\{replace}', ['replace' => 'no']); - -If you include objects in your logging placeholders those objects must implement -one of the following methods: - -* ``__toString()`` -* ``toArray()`` -* ``__debugInfo()`` - -.. _logging-levels: - -Using Levels ------------- - -CakePHP supports the standard POSIX set of logging levels. Each level represents -an increasing level of severity: - -* Emergency: system is unusable -* Alert: action must be taken immediately -* Critical: critical conditions -* Error: error conditions -* Warning: warning conditions -* Notice: normal but significant condition -* Info: informational messages -* Debug: debug-level messages - -You can refer to these levels by name when configuring loggers, and when writing -log messages. Alternatively, you can use convenience methods like -:php:meth:`Cake\\Log\\Log::error()` to clearly indicate the logging -level. Using a level that is not in the above levels will result in an -exception. - -.. note:: - When ``levels`` is set to an empty value in a logger's configuration, it - will take messages of any level. - -.. _logging-scopes: - -Logging Scopes --------------- - -Often times you'll want to configure different logging behavior for different -subsystems or parts of your application. Take for example an e-commerce shop. -You'll probably want to handle logging for orders and payments differently than -you do other less critical logs. - -CakePHP exposes this concept as logging scopes. When log messages are written -you can include a scope name. If there is a configured logger for that scope, -the log messages will be directed to those loggers. For example:: - - use Cake\Log\Engine\FileLog; - - // Configure logs/shops.log to receive all levels, but only - // those with `orders` and `payments` scope. - Log::setConfig('shops', [ - 'className' => FileLog::class, - 'path' => LOGS, - 'levels' => [], - 'scopes' => ['orders', 'payments'], - 'file' => 'shops.log', - ]); - - // Configure logs/payments.log to receive all levels, but only - // those with `payments` scope. - Log::setConfig('payments', [ - 'className' => FileLog::class, - 'path' => LOGS, - 'levels' => [], - 'scopes' => ['payments'], - 'file' => 'payments.log', - ]); - - Log::warning('this gets written only to shops.log', ['scope' => ['orders']]); - Log::warning('this gets written to both shops.log and payments.log', ['scope' => ['payments']]); - -Scopes can also be passed as a single string or a numerically indexed array. -Note that using this form will limit the ability to pass more data as context:: - - Log::warning('This is a warning', ['orders']); - Log::warning('This is a warning', 'payments'); - -.. note:: - When ``scopes`` is set to an empty array or ``null`` in a logger's - configuration, it will take messages of any scope. Setting it to ``false`` - will only match messages without scope. - -.. _file-log: - -Logging to Files -================ - -As its name implies ``FileLog`` writes log messages to files. The level of log -message being written determines the name of the file the message is stored in. -If a level is not supplied, :php:const:`LOG_ERR` is used which writes to the -error log. The default log location is **logs/$level.log**:: - - // Executing this inside a CakePHP class - $this->log("Something didn't work!"); - - // Results in this being appended to logs/error.log - // 2007-11-02 10:22:02 Error: Something didn't work! - -The configured directory must be writable by the web server user in -order for logging to work correctly. - -You can configure additional/alternate FileLog locations when configuring -a logger. FileLog accepts a ``path`` which allows for -custom paths to be used:: - - Log::setConfig('custom_path', [ - 'className' => 'File', - 'path' => '/path/to/custom/place/' - ]); - -``FileLog`` engine takes the following options: - -* ``size`` Used to implement basic log file rotation. If log file size - reaches specified size the existing file is renamed by appending timestamp - to filename and new log file is created. Can be integer bytes value or - human readable string values like '10MB', '100KB' etc. Defaults to 10MB. -* ``rotate`` Log files are rotated specified times before being removed. - If value is 0, old versions are removed rather then rotated. Defaults to 10. -* ``mask`` Set the file permissions for created files. If left empty the default - permissions are used. - -.. note:: - - Missing directories will be automatically created to avoid - unnecessary errors thrown when using the FileEngine. - -.. _syslog-log: - -Logging to Syslog -================= - -In production environments it is highly recommended that you setup your system to -use syslog instead of the file logger. This will perform much better as any -writes will be done in a (almost) non-blocking fashion and your operating system -logger can be configured separately to rotate files, pre-process writes or use -a completely different storage for your logs. - -Using syslog is pretty much like using the default FileLog engine, you just need -to specify ``Syslog`` as the engine to be used for logging. The following -configuration snippet will replace the default logger with syslog, this should -be done in the **config/bootstrap.php** file:: - - Log::setConfig('default', [ - 'engine' => 'Syslog' - ]); - -The configuration array accepted for the Syslog logging engine understands the -following keys: - -* ``format``: An sprintf template string with two placeholders, the first one - for the error level, and the second for the message itself. This key is - useful to add additional information about the server or process in the - logged message. For example: ``%s - Web Server 1 - %s`` will look like - ``error - Web Server 1 - An error occurred in this request`` after - replacing the placeholders. This option is deprecated. You should use - :ref:`logging-formatters` instead. -* ``prefix``: An string that will be prefixed to every logged message. -* ``flag``: An integer flag to be used for opening the connection to the - logger, by default ``LOG_ODELAY`` will be used. See ``openlog`` documentation - for more options -* ``facility``: The logging slot to use in syslog. By default ``LOG_USER`` is - used. See ``syslog`` documentation for more options - -Creating Log Engines -==================== - -Log engines can be part of your application, or part of -plugins. If for example you had a database logger called -``DatabaseLog``. As part of your application it would be placed in -**src/Log/Engine/DatabaseLog.php**. As part of a plugin it would be placed in -**plugins/LoggingPack/src/Log/Engine/DatabaseLog.php**. To configure log -engine you should use :php:meth:`Cake\\Log\\Log::setConfig()`. For example -configuring our DatabaseLog would look like:: - - // For src/Log - Log::setConfig('otherFile', [ - 'className' => 'Database', - 'model' => 'LogEntry', - // ... - ]); - - // For plugin called LoggingPack - Log::setConfig('otherFile', [ - 'className' => 'LoggingPack.Database', - 'model' => 'LogEntry', - // ... - ]); - -When configuring a log engine the ``className`` parameter is used to -locate and load the log handler. All of the other configuration -properties are passed to the log engine's constructor as an array. :: - - namespace App\Log\Engine; - use Cake\Log\Engine\BaseLog; - - class DatabaseLog extends BaseLog - { - public function __construct(array $config = []) - { - parent::__construct($config); - // ... - } - - public function log($level, string $message, array $context = []) - { - // Write to the database. - } - } - -CakePHP requires that all logging engine implement ``Psr\Log\LoggerInterface``. -The class :php:class:`Cake\Log\Engine\BaseLog` is an easy way to satisfy the -interface as it only requires you to implement the ``log()`` method. - -.. _logging-formatters: - -Logging Formatters -================== - -Logging formatters allow you to control how log messages are formatted -independent of the storage engine. Each core provided logging engine comes with -a formatter configured to maintain backwards compatible output. However, you can -adjust the formatters to fit your requirements. Formatters are configured -alongside the logging engine:: - - use Cake\Log\Engine\SyslogLog; - use App\Log\Formatter\CustomFormatter; - - // Simple formatting configuration with no options. - Log::setConfig('error', [ - 'className' => SyslogLog::class, - 'formatter' => CustomFormatter::class, - ]); - - // Configure a formatter with additional options. - Log::setConfig('error', [ - 'className' => SyslogLog::class, - 'formatter' => [ - 'className' => CustomFormatter::class, - 'key' => 'value', - ], - ]); - -To implement your own logging formatter you need to extend -``Cake\Log\Format\AbstractFormatter`` or one of its subclasses. The primary -method you need to implement is ``format($level, $message, $context)`` which is -responsible for formatting log messages. - -.. _log-testing: - -Testing Logs -============ - -To test logging, add ``Cake\TestSuite\LogTestTrait`` to your test case. The -``LogTestTrait`` uses PHPUnit hooks to attach log engines that intercept the log -messages your application is making. Once you have captured logs you can perform -assertions on log messages your application is emitting. For example:: - - namespace App\Test\TestCase\Controller; - - use Cake\TestSuite\LogTestTrait; - use Cake\TestSuite\TestCase; - - class UsersControllerTest extends TestCase - { - use LogTestTrait; - - public function setUp(): void - { - parent::setUp(); - $this->setupLog([ - 'error' => ['scopes' => ['app.security']] - ]); - } - - public function testResetPassword() - { - $this->post('/users/resetpassword', ['email' => 'bob@example.com']); - $this->assertLogMessageContains('info', 'bob@example.com reset password', 'app.security'); - } - } - -You use ``setupLog()`` to define the log messages you wish to capture and -perform assertions on. After logs have been emitted you can make assertions on -the contents of logs, or the absence of them: - -* ``assertLogMessage(string $level, string $expectedMessage, ?string $scope - = null, string $failMsg = '')`` Assert that a log message was found. -* ``assertLogMessageContains(string $level, string $expectedMessage, ?string - $scope = null, string $failMsg = '')`` Assert that a log message contains the - substring. -* ``assertLogAbsent(string $level, ?string $failMsg = '')`` Assert that no log - messages of the given level were captured. - -The ``LogTestTrait`` will automatically clean up any loggers that were -configured. - -Log API -======= - -.. php:namespace:: Cake\Log - -.. php:class:: Log - - A simple class for writing to logs. - -.. php:staticmethod:: setConfig($key, $config) - - :param string $name: Name for the logger being connected, used - to drop a logger later on. - :param array $config: Array of configuration information and - constructor arguments for the logger. - - Get or set the configuration for a Logger. See :ref:`log-configuration` for - more information. - -.. php:staticmethod:: configured() - - :returns: An array of configured loggers. - - Get the names of the configured loggers. - -.. php:staticmethod:: drop($name) - - :param string $name: Name of the logger you wish to no longer receive - messages. - -.. php:staticmethod:: write($level, $message, $scope = []) - - Write a message into all the configured loggers. - ``$level`` indicates the level of log message being created. - ``$message`` is the message of the log entry being written to. - ``$scope`` is the scope(s) a log message is being created in. - -.. php:staticmethod:: levels() - -Call this method without arguments, eg: `Log::levels()` to obtain current -level configuration. - -Convenience Methods -------------------- - -The following convenience methods were added to log `$message` with the -appropriate log level. - -.. php:staticmethod:: emergency($message, $scope = []) -.. php:staticmethod:: alert($message, $scope = []) -.. php:staticmethod:: critical($message, $scope = []) -.. php:staticmethod:: error($message, $scope = []) -.. php:staticmethod:: warning($message, $scope = []) -.. php:staticmethod:: notice($message, $scope = []) -.. php:staticmethod:: info($message, $scope = []) -.. php:staticmethod:: debug($message, $scope = []) - -Logging Trait -============= - -.. php:trait:: LogTrait - - A trait that provides shortcut methods for logging - -.. php:method:: log($msg, $level = LOG_ERR) - - Log a message to the logs. By default messages are logged as - ERROR messages. - -Using Monolog -============= - -Monolog is a popular logger for PHP. Since it implements the same interfaces as -the CakePHP loggers, you can use them in your application as the default -logger. - -After installing Monolog using composer, configure the logger using the -``Log::setConfig()`` method:: - - // config/bootstrap.php - - use Monolog\Logger; - use Monolog\Handler\StreamHandler; - - Log::setConfig('default', function () { - $log = new Logger('app'); - $log->pushHandler(new StreamHandler('path/to/your/combined.log')); - - return $log; - }); - - // Optionally stop using the now redundant default loggers - Log::drop('debug'); - Log::drop('error'); - -Use similar methods if you want to configure a different logger for your console:: - - // config/bootstrap_cli.php - - use Monolog\Logger; - use Monolog\Handler\StreamHandler; - - Log::setConfig('default', function () { - $log = new Logger('cli'); - $log->pushHandler(new StreamHandler('path/to/your/combined-cli.log')); - - return $log; - }); - - // Optionally stop using the now redundant default CLI loggers - Configure::delete('Log.debug'); - Configure::delete('Log.error'); - -.. note:: - - When using a console specific logger, make sure to conditionally configure - your application logger. This will prevent duplicate log entries. - -.. meta:: - :title lang=en: Logging - :description lang=en: Log CakePHP data to the disk to help debug your application over longer periods of time. - :keywords lang=en: cakephp logging,log errors,debug,logging data,cakelog class,ajax logging,soap logging,debugging,logs diff --git a/en/core-libraries/number.rst b/en/core-libraries/number.rst deleted file mode 100644 index f59429a03f..0000000000 --- a/en/core-libraries/number.rst +++ /dev/null @@ -1,360 +0,0 @@ -Number -###### - -.. php:namespace:: Cake\I18n - -.. php:class:: Number - -If you need :php:class:`NumberHelper` functionalities outside of a ``View``, -use the ``Number`` class:: - - namespace App\Controller; - - use Cake\I18n\Number; - - class UsersController extends AppController - { - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('Authentication.Authentication'); - } - - public function afterLogin() - { - $identity = $this->Authentication->getIdentity(); - $storageUsed = $identity->storage_used; - if ($storageUsed > 5000000) { - // Notify users of quota - $this->Flash->success(__('You are using {0} storage', Number::toReadableSize($storageUsed))); - } - } - } - -.. start-cakenumber - -All of these functions return the formatted number; they do not -automatically echo the output into the view. - -Formatting Currency Values -========================== - -.. php:method:: currency(mixed $value, string $currency = null, array $options = []) - -This method is used to display a number in common currency formats -(EUR, GBP, USD), based on the 3-letter ISO 4217 currency code. Usage in a view looks like:: - - // Called as NumberHelper - echo $this->Number->currency($value, $currency); - - // Called as Number - echo Number::currency($value, $currency); - -The first parameter, ``$value``, should be a floating point number -that represents the amount of money you are expressing. The second -parameter is a string used to choose a predefined currency formatting -scheme: - -+---------------------+----------------------------------------------------+ -| $currency | 1234.56, formatted by currency type | -+=====================+====================================================+ -| EUR | €1.234,56 | -+---------------------+----------------------------------------------------+ -| GBP | £1,234.56 | -+---------------------+----------------------------------------------------+ -| USD | $1,234.56 | -+---------------------+----------------------------------------------------+ - -The third parameter is an array of options for further defining the -output. The following options are available: - -+---------------------+----------------------------------------------------+ -| Option | Description | -+=====================+====================================================+ -| before | Text to display before the rendered number. | -+---------------------+----------------------------------------------------+ -| after | Text to display after the rendered number. | -+---------------------+----------------------------------------------------+ -| zero | The text to use for zero values; can be a string | -| | or a number. ie. 0, 'Free!'. | -+---------------------+----------------------------------------------------+ -| places | Number of decimal places to use, ie. 2 | -+---------------------+----------------------------------------------------+ -| precision | Maximal number of decimal places to use, ie. 2 | -+---------------------+----------------------------------------------------+ -| locale | The locale name to use for formatting number, | -| | ie. "fr_FR". | -+---------------------+----------------------------------------------------+ -| fractionSymbol | String to use for fraction numbers, ie. ' cents'. | -+---------------------+----------------------------------------------------+ -| fractionPosition | Either 'before' or 'after' to place the fraction | -| | symbol. | -+---------------------+----------------------------------------------------+ -| pattern | An ICU number pattern to use for formatting the | -| | number ie. #,###.00 | -+---------------------+----------------------------------------------------+ -| useIntlCode | Set to ``true`` to replace the currency symbol | -| | with the international currency code. | -+---------------------+----------------------------------------------------+ - -If ``$currency`` value is ``null``, the default currency will be retrieved from -:php:meth:`Cake\\I18n\\Number::defaultCurrency()`. To format currencies in an -accounting format you should set the currency format:: - - Number::setDefaultCurrencyFormat(Number::FORMAT_CURRENCY_ACCOUNTING); - -Setting the Default Currency -============================ - -.. php:method:: setDefaultCurrency($currency) - -Setter for the default currency. This removes the need to always pass the -currency to :php:meth:`Cake\\I18n\\Number::currency()` and change all -currency outputs by setting other default. If ``$currency`` is set to ``null``, -it will clear the currently stored value. - -Getting the Default Currency -============================ - -.. php:method:: getDefaultCurrency() - -Getter for the default currency. If default currency was set earlier using -``setDefaultCurrency()``, then that value will be returned. By default, it will -retrieve the ``intl.default_locale`` ini value if set and ``'en_US'`` if not. - -Formatting Floating Point Numbers -================================= - -.. php:method:: precision(float $value, int $precision = 3, array $options = []) - -This method displays a number with the specified amount of -precision (decimal places). It will round in order to maintain the -level of precision defined. :: - - // Called as NumberHelper - echo $this->Number->precision(456.91873645, 2); - - // Outputs - 456.92 - - // Called as Number - echo Number::precision(456.91873645, 2); - -Formatting Percentages -====================== - -.. php:method:: toPercentage(mixed $value, int $precision = 2, array $options = []) - -+---------------------+----------------------------------------------------+ -| Option | Description | -+=====================+====================================================+ -| multiply | Boolean to indicate whether the value has to be | -| | multiplied by 100. Useful for decimal percentages. | -+---------------------+----------------------------------------------------+ - -Like :php:meth:`Cake\\I18n\\Number::precision()`, this method formats a number -according to the supplied precision (where numbers are rounded to meet the -given precision). This method also expresses the number as a percentage -and appends the output with a percent sign. :: - - // Called as NumberHelper. Output: 45.69% - echo $this->Number->toPercentage(45.691873645); - - // Called as Number. Output: 45.69% - echo Number::toPercentage(45.691873645); - - // Called with multiply. Output: 45.7% - echo Number::toPercentage(0.45691, 1, [ - 'multiply' => true - ]); - -Interacting with Human Readable Values -====================================== - -.. php:method:: toReadableSize(string $size) - -This method formats data sizes in human readable forms. It provides -a shortcut way to convert bytes to KB, MB, GB, and TB. The size is -displayed with a two-digit precision level, according to the size -of data supplied (i.e. higher sizes are expressed in larger -terms):: - - // Called as NumberHelper - echo $this->Number->toReadableSize(0); // 0 Byte - echo $this->Number->toReadableSize(1024); // 1 KB - echo $this->Number->toReadableSize(1321205.76); // 1.26 MB - echo $this->Number->toReadableSize(5368709120); // 5 GB - - // Called as Number - echo Number::toReadableSize(0); // 0 Byte - echo Number::toReadableSize(1024); // 1 KB - echo Number::toReadableSize(1321205.76); // 1.26 MB - echo Number::toReadableSize(5368709120); // 5 GB - -Formatting Numbers -================== - -.. php:method:: format(mixed $value, array $options = []) - -This method gives you much more control over the formatting of -numbers for use in your views (and is used as the main method by -most of the other NumberHelper methods). Using this method might -looks like:: - - // Called as NumberHelper - $this->Number->format($value, $options); - - // Called as Number - Number::format($value, $options); - -The ``$value`` parameter is the number that you are planning on -formatting for output. With no ``$options`` supplied, the number -1236.334 would output as 1,236. Note that the default precision is -zero decimal places. - -The ``$options`` parameter is where the real magic for this method -resides. - -- If you pass an integer then this becomes the amount of precision - or places for the function. -- If you pass an associated array, you can use the following keys: - -+---------------------+----------------------------------------------------+ -| Option | Description | -+=====================+====================================================+ -| places | Number of decimal places to use, ie. 2 | -+---------------------+----------------------------------------------------+ -| precision | Maximum number of decimal places to use, ie. 2 | -+---------------------+----------------------------------------------------+ -| pattern | An ICU number pattern to use for formatting the | -| | number ie. #,###.00 | -+---------------------+----------------------------------------------------+ -| locale | The locale name to use for formatting number, | -| | ie. "fr_FR". | -+---------------------+----------------------------------------------------+ -| before | Text to display before the rendered number. | -+---------------------+----------------------------------------------------+ -| after | Text to display after the rendered number. | -+---------------------+----------------------------------------------------+ - -Example:: - - // Called as NumberHelper - echo $this->Number->format('123456.7890', [ - 'places' => 2, - 'before' => '¥ ', - 'after' => ' !' - ]); - // Output '¥ 123,456.79 !' - - echo $this->Number->format('123456.7890', [ - 'locale' => 'fr_FR' - ]); - // Output '123 456,79 !' - - // Called as Number - echo Number::format('123456.7890', [ - 'places' => 2, - 'before' => '¥ ', - 'after' => ' !' - ]); - // Output '¥ 123,456.79 !' - - echo Number::format('123456.7890', [ - 'locale' => 'fr_FR' - ]); - // Output '123 456,79 !' - -.. php:method:: ordinal(mixed $value, array $options = []) - -This method will output an ordinal number. - -Examples:: - - echo Number::ordinal(1); - // Output '1st' - - echo Number::ordinal(2); - // Output '2nd' - - echo Number::ordinal(2, [ - 'locale' => 'fr_FR' - ]); - // Output '2e' - - echo Number::ordinal(410); - // Output '410th' - -Format Differences -================== - -.. php:method:: formatDelta(mixed $value, array $options = []) - -This method displays differences in value as a signed number:: - - // Called as NumberHelper - $this->Number->formatDelta($value, $options); - - // Called as Number - Number::formatDelta($value, $options); - -The ``$value`` parameter is the number that you are planning on -formatting for output. With no ``$options`` supplied, the number -1236.334 would output as 1,236. Note that the default precision is -zero decimal places. - -The ``$options`` parameter takes the same keys as :php:meth:`Number::format()` itself: - -+---------------------+----------------------------------------------------+ -| Option | Description | -+=====================+====================================================+ -| places | Number of decimal places to use, ie. 2 | -+---------------------+----------------------------------------------------+ -| precision | Maximum number of decimal places to use, ie. 2 | -+---------------------+----------------------------------------------------+ -| locale | The locale name to use for formatting number, | -| | ie. "fr_FR". | -+---------------------+----------------------------------------------------+ -| before | Text to display before the rendered number. | -+---------------------+----------------------------------------------------+ -| after | Text to display after the rendered number. | -+---------------------+----------------------------------------------------+ - -Example:: - - // Called as NumberHelper - echo $this->Number->formatDelta('123456.7890', [ - 'places' => 2, - 'before' => '[', - 'after' => ']' - ]); - // Output '[+123,456.79]' - - // Called as Number - echo Number::formatDelta('123456.7890', [ - 'places' => 2, - 'before' => '[', - 'after' => ']' - ]); - // Output '[+123,456.79]' - -.. end-cakenumber - -Configure formatters -==================== - -.. php:method:: config(string $locale, int $type = NumberFormatter::DECIMAL, array $options = []) - -This method allows you to configure formatter defaults which persist across calls -to various methods. - -Example:: - - Number::config('en_IN', \NumberFormatter::CURRENCY, [ - 'pattern' => '#,##,##0' - ]); - -.. meta:: - :title lang=en: NumberHelper - :description lang=en: The Number Helper contains convenience methods that enable display numbers in common formats in your views. - :keywords lang=en: number helper,currency,number format,number precision,format file size,format numbers diff --git a/en/core-libraries/plugin.rst b/en/core-libraries/plugin.rst deleted file mode 100644 index 1b0065a13e..0000000000 --- a/en/core-libraries/plugin.rst +++ /dev/null @@ -1,53 +0,0 @@ -Plugin Class -############ - -.. php:namespace:: Cake\Core - -.. php:class:: Plugin - -The Plugin class is responsible for resource location and path management of plugins. - -Locating Plugins -================ - -.. php:staticmethod:: path(string $plugin) - -Plugins can be located with Plugin. Using ``Plugin::path('DebugKit');`` -for example, will give you the full path to the DebugKit plugin:: - - $path = Plugin::path('DebugKit'); - -Check if a Plugin is Loaded -=========================== - -You can check dynamically inside your code if a specific plugin has been loaded:: - - $isLoaded = Plugin::isLoaded('DebugKit'); - -Use ``Plugin::loaded()`` if you want to get a list of all currently loaded plugins. - -Finding Paths to Namespaces -=========================== - -.. php:staticmethod:: classPath(string $plugin) - -Used to get the location of the plugin's class files:: - - $path = App::classPath('DebugKit'); - -Finding Paths to Resources -========================== - -.. php:staticmethod:: templatePath(string $plugin) - -The method returns the path to the plugins' templates:: - - $path = Plugin::templatePath('DebugKit'); - -The same goes for the config path:: - - $path = Plugin::configPath('DebugKit'); - -.. meta:: - :title lang=en: Plugin Class - :keywords lang=en: compatible implementation,model behaviors,path management,loading files,php class,class loading,model behavior,class location,component model,management class,autoloader,classname,directory location,override,conventions,lib,textile,cakephp,php classes,loaded diff --git a/en/core-libraries/registry-objects.rst b/en/core-libraries/registry-objects.rst deleted file mode 100644 index 25fb6580fa..0000000000 --- a/en/core-libraries/registry-objects.rst +++ /dev/null @@ -1,57 +0,0 @@ -Registry Objects -################ - -The registry classes provide a simple way to create and retrieve loaded -instances of a given object type. There are registry classes for Components, -Helpers, Tasks, and Behaviors. - -While the examples below will use Components, the same behavior can be expected -for Helpers, Behaviors, and Tasks in addition to Components. - -Loading Objects -=============== - -Objects can be loaded on-the-fly using add() -Example:: - - $this->loadComponent('Acl.Acl'); - $this->addHelper('Flash') - -This will result in the ``Acl`` property and ``Flash`` helper being loaded. -Configuration can also be set on-the-fly. Example:: - - $this->loadComponent('Cookie', ['name' => 'sweet']); - -Any keys and values provided will be passed to the Component's constructor. The -one exception to this rule is ``className``. Classname is a special key that is -used to alias objects in a registry. This allows you to have component names -that do not reflect the classnames, which can be helpful when extending core -components:: - - $this->Flash = $this->loadComponent('Flash', ['className' => 'MyCustomFlash']); - $this->Flash->error(); // Actually using MyCustomFlash::error(); - -Triggering Callbacks -==================== - -Callbacks are not provided by registry objects. You should use the -:doc:`events system ` to dispatch any events/callbacks -for your application. - -Disabling Callbacks -=================== - -In previous versions, collection objects provided a ``disable()`` method to disable -objects from receiving callbacks. You should use the features in the events system to -accomplish this now. For example, you could disable component callbacks in the -following way:: - - // Remove MyComponent from callbacks. - $this->getEventManager()->off($this->MyComponent); - - // Re-enable MyComponent for callbacks. - $this->getEventManager()->on($this->MyComponent); - -.. meta:: - :title lang=en: Object Registry - :keywords lang=en: array name,loading components,several different kinds,unified api,loading objects,component names,special key,core components,callbacks,prg,callback,alias,fatal error,collections,memory,priority,priorities diff --git a/en/core-libraries/security.rst b/en/core-libraries/security.rst deleted file mode 100644 index d1f1524087..0000000000 --- a/en/core-libraries/security.rst +++ /dev/null @@ -1,106 +0,0 @@ -Security Utility -################ - -.. php:namespace:: Cake\Utility - -.. php:class:: Security - -The `security library -`_ -handles basic security measures such as providing methods for -hashing and encrypting data. - -Encrypting and Decrypting Data -============================== - -.. php:staticmethod:: encrypt($text, $key, $hmacSalt = null) -.. php:staticmethod:: decrypt($cipher, $key, $hmacSalt = null) - -Encrypt ``$text`` using AES-256. The ``$key`` should be a value with a -lots of variance in the data much like a good password. The returned result -will be the encrypted value with an HMAC checksum. - -The `openssl `_ extension is required for encrypting/decrypting. - -An example use would be:: - - // Assuming key is stored somewhere it can be re-used for - // decryption later. - $key = 'wt1U5MACWJFTXGenFoZoiLwQGrLgdbHA'; - $result = Security::encrypt($value, $key); - -If you do not supply an HMAC salt, the value of ``Security::getSalt()`` will be used. -Encrypted values can be decrypted using -:php:meth:`Cake\\Utility\\Security::decrypt()`. - -This method should **never** be used to store passwords. - -Decrypt a previously encrypted value. The ``$key`` and ``$hmacSalt`` -parameters must match the values used to encrypt or decryption will fail. An -example use would be:: - - // Assuming the key is stored somewhere it can be re-used for - // Decryption later. - $key = 'wt1U5MACWJFTXGenFoZoiLwQGrLgdbHA'; - - $cipher = $user->secrets; - $result = Security::decrypt($cipher, $key); - -If the value cannot be decrypted due to changes in the key or HMAC salt -``false`` will be returned. - -Hashing Data -============ - -.. php:staticmethod:: hash( $string, $type = NULL, $salt = false ) - -Create a hash from string using given method. Fallback on next -available method. If ``$salt`` is set to ``true``, the application's salt -value will be used:: - - // Using the application's salt value - $sha1 = Security::hash('CakePHP Framework', 'sha1', true); - - // Using a custom salt value - $sha1 = Security::hash('CakePHP Framework', 'sha1', 'my-salt'); - - // Using the default hash algorithm - $hash = Security::hash('CakePHP Framework'); - -The ``hash()`` method supports the following hashing strategies: - -- md5 -- sha1 -- sha256 - -And any other hash algorithm that PHP's ``hash()`` function supports. - -.. warning:: - - You should not be using ``hash()`` for passwords in new applications. - Instead you should use the ``DefaultPasswordHasher`` class which uses bcrypt - by default. - -Getting Secure Random Data -========================== - -.. php:staticmethod:: randomBytes($length) - -Get ``$length`` number of bytes from a secure random source. This function draws -data from one of the following sources: - -* PHP's ``random_bytes`` function. -* ``openssl_random_pseudo_bytes`` from the SSL extension. - -If neither source is available a warning will be emitted and an unsafe value -will be used for backwards compatibility reasons. - -.. php:staticmethod:: randomString($length) - -Get a random string ``$length`` long from a secure random source. This method -draws from the same random source as ``randomBytes()`` and will encode the data -as a hexadecimal string. - -.. meta:: - :title lang=en: Security - :keywords lang=en: security api,secret password,cipher text,php class,class security,text key,security library,object instance,security measures,basic security,security level,string type,fallback,hash,data security,singleton,inactivity,php encrypt,implementation,php security diff --git a/en/core-libraries/text.rst b/en/core-libraries/text.rst deleted file mode 100644 index 30d0111958..0000000000 --- a/en/core-libraries/text.rst +++ /dev/null @@ -1,386 +0,0 @@ -Text -#### - -.. php:namespace:: Cake\Utility - -.. php:class:: Text - -The Text class includes convenience methods for creating and manipulating -strings and is normally accessed statically. Example: -``Text::uuid()``. - -If you need :php:class:`Cake\\View\\Helper\\TextHelper` functionalities outside -of a ``View``, use the ``Text`` class:: - - namespace App\Controller; - - use Cake\Utility\Text; - - class UsersController extends AppController - { - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('Auth') - }; - - public function afterLogin() - { - $message = $this->Users->find('new_message')->first(); - if (!empty($message)) { - // Notify user of new message - $this->Flash->success(__( - 'You have a new message: {0}', - Text::truncate($message['Message']['body'], 255, ['html' => true]) - )); - } - } - } - -Convert Strings into ASCII -========================== - -.. php:staticmethod:: transliterate($string, $transliteratorId = null) - -Transliterate by default converts all characters in provided string into -equivalent ASCII characters. The method expects UTF-8 encoding. The character -conversion can be controlled using transliteration identifiers which you can -pass using the ``$transliteratorId`` argument or change the default identifier -string using ``Text::setTransliteratorId()``. ICU transliteration identifiers -are basically of form ``:`` and you can specify -multiple conversion pairs separated by ``;``. You can find more info about -transliterator identifiers -`here `_:: - - // apple puree - Text::transliterate('apple purée'); - - // Ubermensch (only latin characters are transliterated) - Text::transliterate('Übérmensch', 'Latin-ASCII;'); - -Creating URL Safe Strings -========================= - -.. php:staticmethod:: slug(string $string, array|string $options = []) - -Slug transliterates all characters into ASCII versions and converting unmatched -characters and spaces to dashes. The slug method expects UTF-8 encoding. - -You can provide an array of options that controls slug. ``$options`` can also be -a string in which case it will be used as replacement string. The supported -options are: - -* ``replacement`` Replacement string, defaults to '-'. -* ``transliteratorId`` A valid tranliterator id string. If default ``null`` - ``Text::$_defaultTransliteratorId`` to be used. - If ``false`` no transliteration will be done, only non words will be removed. -* ``preserve`` Specific non-word character to preserve. Defaults to ``null``. - For example, this option can be set to '.' to generate clean file names:: - - // apple-puree - Text::slug('apple purée'); - - // apple_puree - Text::slug('apple purée', '_'); - - // foo-bar.tar.gz - Text::slug('foo bar.tar.gz', ['preserve' => '.']); - -Generating UUIDs -================ - -.. php:staticmethod:: uuid() - -The UUID method is used to generate unique identifiers as per :rfc:`4122`. The -UUID is a 128-bit string in the format of -``485fc381-e790-47a3-9794-1337c0a8fe68``. :: - - Text::uuid(); // 485fc381-e790-47a3-9794-1337c0a8fe68 - -Simple String Parsing -===================== - -.. php:staticmethod:: tokenize(string $data, string $separator = ',', string $leftBound = '(', string $rightBound = ')') - -Tokenizes a string using ``$separator``, ignoring any instance of ``$separator`` -that appears between ``$leftBound`` and ``$rightBound``. - -This method can be useful when splitting up data that has regular formatting -such as tag lists:: - - $data = "cakephp 'great framework' php"; - $result = Text::tokenize($data, ' ', "'", "'"); - // Result contains - ['cakephp', "'great framework'", 'php']; - -.. php:method:: parseFileSize(string $size, mixed $default = false) - -This method unformats a number from a human-readable byte size to an integer -number of bytes:: - - $int = Text::parseFileSize('2GB'); - -Formatting Strings -================== - -.. php:staticmethod:: insert(string $str, array $data, array $options = []) - -The insert method is used to create string templates and to allow for key/value -replacements:: - - Text::insert( - 'My name is :name and I am :age years old.', - ['name' => 'Bob', 'age' => '65'] - ); - // Returns: "My name is Bob and I am 65 years old." - -.. php:staticmethod:: cleanInsert(string $str, array $options) - -Cleans up a ``Text::insert`` formatted string with given ``$options`` depending -on the 'clean' key in ``$options``. The default method used is text but html is -also available. The goal of this function is to replace all whitespace and -unneeded markup around placeholders that did not get replaced by -``Text::insert``. - -You can use the following options in the options array:: - - $options = [ - 'clean' => [ - 'method' => 'text', // or html - ], - 'before' => '', - 'after' => '' - ]; - -Wrapping Text -============= - -.. php:staticmethod:: wrap(string $text, array|int $options = []) - -Wraps a block of text to a set width and indents blocks as well. -Can intelligently wrap text so words are not sliced across lines:: - - $text = 'This is the song that never ends.'; - $result = Text::wrap($text, 22); - - // Returns - This is the song that - never ends. - -You can provide an array of options that control how wrapping is done. The -supported options are: - -* ``width`` The width to wrap to. Defaults to 72. -* ``wordWrap`` Whether or not to wrap whole words. Defaults to ``true``. -* ``indent`` The character to indent lines with. Defaults to ''. -* ``indentAt`` The line number to start indenting text. Defaults to 0. - -.. php:staticmethod:: wrapBlock(string $text, array|int $options = []) - -If you need to ensure that the total width of the generated block won't -exceed a certain length even with internal indentation, you need to use -``wrapBlock()`` instead of ``wrap()``. This is particularly useful to generate -text for the console for example. It accepts the same options as ``wrap()``:: - - $text = 'This is the song that never ends. This is the song that never ends.'; - $result = Text::wrapBlock($text, [ - 'width' => 22, - 'indent' => ' → ', - 'indentAt' => 1 - ]); - - // Returns - This is the song that - → never ends. This - → is the song that - → never ends. - -.. start-text - -Highlighting Substrings -======================= - -.. php:method:: highlight(string $text, array|string $phrase, array $options = []) - -Highlights ``$phrase`` in ``$text`` using the ``$options['format']`` string -specified or a default string. - -Options: - -- ``format`` string - The piece of HTML with the phrase that will be - highlighted -- ``html`` bool - If ``true``, will ignore any HTML tags, ensuring that only - the correct text is highlighted - -Example:: - - // Called as TextHelper - echo $this->Text->highlight( - $lastSentence, - 'using', - ['format' => '\1'] - ); - - // Called as Text - use Cake\Utility\Text; - - echo Text::highlight( - $lastSentence, - 'using', - ['format' => '\1'] - ); - -Output: - -.. code-block: html - - Highlights $phrase in $text using the - $options['format'] string specified or a default string. - -Truncating Text -=============== - -.. php:method:: truncate(string $text, int $length = 100, array $options = []) - -If ``$text`` is longer than ``$length``, this method truncates it at ``$length`` -and adds a suffix consisting of ``'ellipsis'``, if defined. If ``'exact'`` is -passed as ``false``, the truncation will occur at the first whitespace after the -point at which ``$length`` is exceeded. If ``'html'`` is passed as ``true``, -HTML tags will be respected and will not be cut off. - -``$options`` is used to pass all extra parameters, and has the following -possible keys by default, all of which are optional:: - - [ - 'ellipsis' => '...', - 'exact' => true, - 'html' => false - ] - -Example:: - - // Called as TextHelper - echo $this->Text->truncate( - 'The killer crept forward and tripped on the rug.', - 22, - [ - 'ellipsis' => '...', - 'exact' => false - ] - ); - - // Called as Text - use Cake\Utility\Text; - - echo Text::truncate( - 'The killer crept forward and tripped on the rug.', - 22, - [ - 'ellipsis' => '...', - 'exact' => false - ] - ); - -Output:: - - The killer crept... - -Truncating the Tail of a String -=============================== - -.. php:method:: tail(string $text, int $length = 100, array $options = []) - -If ``$text`` is longer than ``$length``, this method removes an initial -substring with length consisting of the difference and prepends a prefix -consisting of ``'ellipsis'``, if defined. If ``'exact'`` is passed as ``false``, -the truncation will occur at the first whitespace prior to the point at which -truncation would otherwise take place. - -``$options`` is used to pass all extra parameters, and has the following -possible keys by default, all of which are optional:: - - [ - 'ellipsis' => '...', - 'exact' => true - ] - -Example:: - - $sampleText = 'I packed my bag and in it I put a PSP, a PS3, a TV, ' . - 'a C# program that can divide by zero, death metal t-shirts' - - // Called as TextHelper - echo $this->Text->tail( - $sampleText, - 70, - [ - 'ellipsis' => '...', - 'exact' => false - ] - ); - - // Called as Text - use Cake\Utility\Text; - - echo Text::tail( - $sampleText, - 70, - [ - 'ellipsis' => '...', - 'exact' => false - ] - ); - -Output:: - - ...a TV, a C# program that can divide by zero, death metal t-shirts - -Extracting an Excerpt -===================== - -.. php:method:: excerpt(string $text, string $phrase, int $radius = 100, string $ellipsis = '…') - -Extracts an excerpt from ``$text`` surrounding the ``$phrase`` with a number -of characters on each side determined by ``$radius``, and prefix/suffix with -``$ellipsis``. This method is especially handy for search results. The query -string or keywords can be shown within the resulting document. :: - - // Called as TextHelper - echo $this->Text->excerpt($lastParagraph, 'method', 50, '...'); - - // Called as Text - use Cake\Utility\Text; - - echo Text::excerpt($lastParagraph, 'method', 50, '...'); - -Output:: - - ... by $radius, and prefix/suffix with $ellipsis. This method is especially - handy for search results. The query... - -Converting an Array to Sentence Form -==================================== - -.. php:method:: toList(array $list, ?string $and = null, $separator = ', ') - -Creates a comma-separated list where the last two items are joined with 'and':: - - $colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; - - // Called as TextHelper - echo $this->Text->toList($colors); - - // Called as Text - use Cake\Utility\Text; - - echo Text::toList($colors); - -Output:: - - red, orange, yellow, green, blue, indigo and violet - -.. end-text - -.. meta:: - :title lang=en: Text - :keywords lang=en: slug,transliterate,ascii,array php,array name,string options,data options,result string,class string,string data,string class,placeholders,default method,key value,markup,rfc,replacements,convenience,templates diff --git a/en/core-libraries/time.rst b/en/core-libraries/time.rst deleted file mode 100644 index 00ca458823..0000000000 --- a/en/core-libraries/time.rst +++ /dev/null @@ -1,443 +0,0 @@ -Date & Time -########### - -.. php:namespace:: Cake\I18n - -.. php:class:: DateTime - -If you need :php:class:`TimeHelper` functionalities outside of a ``View``, -use the ``DateTime`` class:: - - use Cake\I18n\DateTime; - - class UsersController extends AppController - { - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('Authentication.Authentication'); - } - - public function afterLogin() - { - $identity = $this->Authentication->getIdentity(); - $time = new DateTime($identity->date_of_birth); - if ($time->isToday()) { - // Greet user with a happy birthday message - $this->Flash->success(__('Happy birthday to you...')); - } - } - } - -Under the hood, CakePHP uses `Chronos `_ -to power its ``DateTime`` utility. Anything you can do with ``Chronos`` and -PHP's ``DateTimeImmutable``, you can do with ``DateTime``. - -For more details on Chronos please see `the API documentation -`_. - -.. start-time - -Creating DateTime Instances -=========================== - -``DateTime`` are immutable objects as immutability prevents accidental changes -to data, and avoids order based dependency issues. - -There are a few ways to create ``DateTime`` instances:: - - use Cake\I18n\DateTime; - - // Create from a string datetime. - $time = DateTime::createFromFormat( - 'Y-m-d H:i:s', - '2021-01-31 22:11:30', - 'America/New_York' - ); - - // Create from a timestamp and set timezone - $time = DateTime::createFromTimestamp(1612149090, 'America/New_York'); - - // Get the current time. - $time = DateTime::now(); - - // Or just use 'new' - $time = new DateTime('2021-01-31 22:11:30', 'America/New_York'); - - $time = new DateTime('2 hours ago'); - -The ``DateTime`` class constructor can take any parameter that the internal ``DateTimeImmutable`` -PHP class can. When passing a number or numeric string, it will be interpreted -as a UNIX timestamp. - -In test cases, you can mock out ``now()`` using ``setTestNow()``:: - - // Fixate time. - $time = new DateTime('2021-01-31 22:11:30'); - DateTime::setTestNow($time); - - // Outputs '2021-01-31 22:11:30' - $now = DateTime::now(); - echo $now->i18nFormat('yyyy-MM-dd HH:mm:ss'); - - // Outputs '2021-01-31 22:11:30' - $now = DateTime::parse('now'); - echo $now->i18nFormat('yyyy-MM-dd HH:mm:ss'); - -Manipulation -============ - -Remember, ``DateTime`` instance always return a new instance from setters -instead of modifying itself:: - - $time = DateTime::now(); - - // Create and reassign a new instance - $newTime = $time->year(2013) - ->month(10) - ->day(31); - // Outputs '2013-10-31 22:11:30' - echo $newTime->i18nFormat('yyyy-MM-dd HH:mm:ss'); - -You can also use the methods provided by PHP's built-in ``DateTime`` class:: - - $time = $time->setDate(2013, 10, 31); - -Failing to reassign the new ``DateTime`` instances will result in the -original, unmodified instance being used:: - - $time->year(2013) - ->month(10) - ->day(31); - // Outputs '2021-01-31 22:11:30' - echo $time->i18nFormat('yyyy-MM-dd HH:mm:ss'); - -You can create another instance with modified dates, through subtraction and -addition of their components:: - - $time = DateTime::create(2021, 1, 31, 22, 11, 30); - $newTime = $time->subDays(5) - ->addHours(-2) - ->addMonth(1); - // Outputs '2/26/21, 8:11 PM' - echo $newTime; - - // Using strtotime strings. - $newTime = $time->modify('+1 month -5 days -2 hours'); - // Outputs '2/26/21, 8:11 PM' - echo $newTime; - -You can get the internal components of a date by accessing its properties:: - - $time = DateTime::create(2021, 1, 31, 22, 11, 30); - echo $time->year; // 2021 - echo $time->month; // 1 - echo $time->day; // 31 - echo $time->timezoneName; // America/New_York - -Formatting -========== - -.. php:staticmethod:: setJsonEncodeFormat($format) - -This method sets the default format used when converting an object to json:: - - DateTime::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss'); // For any immutable DateTime - Date::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss'); // For any mutable Date - - $time = DateTime::parse('2021-01-31 22:11:30'); - echo json_encode($time); // Outputs '2021-01-31 22:11:30' - - Date::setJsonEncodeFormat(static function($time) { - return $time->format(DATE_ATOM); - }); - -.. note:: - This method must be called statically. - -.. note:: - Be aware that this is not a PHP Datetime string format! You need to use a - ICU date formatting string as specified in the following resource: - https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax. - -.. versionchanged:: 4.1.0 - The ``callable`` parameter type was added. - - -.. php:method:: i18nFormat($format = null, $timezone = null, $locale = null) - -A very common thing to do with ``Time`` instances is to print out formatted -dates. CakePHP makes this a snap:: - - $time = DateTime::parse('2021-01-31 22:11:30'); - - // Prints a localized datetime stamp. Outputs '1/31/21, 10:11 PM' - echo $time; - - // Outputs '1/31/21, 10:11 PM' for the en-US locale - echo $time->i18nFormat(); - - // Use the full date and time format. Outputs 'Sunday, January 31, 2021 at 10:11:30 PM Eastern Standard Time' - echo $time->i18nFormat(\IntlDateFormatter::FULL); - - // Use full date but short time format. Outputs 'Sunday, January 31, 2021 at 10:11 PM' - echo $time->i18nFormat([\IntlDateFormatter::FULL, \IntlDateFormatter::SHORT]); - - // Outputs '2021-Jan-31 22:11:30' - echo $time->i18nFormat('yyyy-MMM-dd HH:mm:ss'); - -It is possible to specify the desired format for the string to be displayed. -You can either pass `IntlDateFormatter constants -`_ as the first -argument of this function, or pass a full ICU date formatting string as -specified in the following resource: -https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax. - -You can also format dates with non-gregorian calendars:: - - // On ICU version 66.1 - $time = DateTime::create(2021, 1, 31, 22, 11, 30); - - // Outputs 'Sunday, Bahman 12, 1399 AP at 10:11:30 PM Eastern Standard Time' - echo $time->i18nFormat(\IntlDateFormatter::FULL, null, 'en-IR@calendar=persian'); - - // Outputs 'Sunday, January 31, 3 Reiwa at 10:11:30 PM Eastern Standard Time' - echo $time->i18nFormat(\IntlDateFormatter::FULL, null, 'en-JP@calendar=japanese'); - - // Outputs 'Sunday, Twelfth Month 19, 2020(geng-zi) at 10:11:30 PM Eastern Standard Time' - echo $time->i18nFormat(\IntlDateFormatter::FULL, null, 'en-CN@calendar=chinese'); - - // Outputs 'Sunday, Jumada II 18, 1442 AH at 10:11:30 PM Eastern Standard Time' - echo $time->i18nFormat(\IntlDateFormatter::FULL, null, 'en-SA@calendar=islamic'); - -The following calendar types are supported: - -* japanese -* buddhist -* chinese -* persian -* indian -* islamic -* hebrew -* coptic -* ethiopic - -.. note:: - For constant strings i.e. IntlDateFormatter::FULL Intl uses ICU library - that feeds its data from CLDR (https://cldr.unicode.org/) which version - may vary depending on PHP installation and give different results. - -.. php:method:: nice() - -Print out a predefined 'nice' format:: - - $time = DateTime::parse('2021-01-31 22:11:30', new \DateTimeZone('America/New_York')); - - // Outputs 'Jan 31, 2021, 10:11 PM' in en-US - echo $time->nice(); - -You can alter the timezone in which the date is displayed without altering the -``DateTime`` object itself. This is useful when you store dates in one timezone, but -want to display them in a user's own timezone:: - - // Outputs 'Monday, February 1, 2021 at 4:11:30 AM Central European Standard Time' - echo $time->i18nFormat(\IntlDateFormatter::FULL, 'Europe/Paris'); - - // Outputs 'Monday, February 1, 2021 at 12:11:30 PM Japan Standard Time' - echo $time->i18nFormat(\IntlDateFormatter::FULL, 'Asia/Tokyo'); - - // Timezone is unchanged. Outputs 'America/New_York' - echo $time->timezoneName; - -Leaving the first parameter as ``null`` will use the default formatting string:: - - // Outputs '2/1/21, 4:11 AM' - echo $time->i18nFormat(null, 'Europe/Paris'); - -Finally, it is possible to use a different locale for displaying a date:: - - // Outputs 'lundi 1 février 2021 à 04:11:30 heure normale d’Europe centrale' - echo $time->i18nFormat(\IntlDateFormatter::FULL, 'Europe/Paris', 'fr-FR'); - - // Outputs '1 févr. 2021 à 04:11' - echo $time->nice('Europe/Paris', 'fr-FR'); - -Setting the Default Locale and Format String --------------------------------------------- - -The default locale in which dates are displayed when using ``nice`` -``i18nFormat`` is taken from the directive -`intl.default_locale `_. -You can, however, modify this default at runtime:: - - DateTime::setDefaultLocale('es-ES'); - Date::setDefaultLocale('es-ES'); - - // Outputs '31 ene. 2021 22:11' - echo $time->nice(); - -From now on, datetimes will be displayed in the Spanish preferred format unless -a different locale is specified directly in the formatting method. - -Likewise, it is possible to alter the default formatting string to be used for -``i18nFormat``:: - - DateTime::setToStringFormat(\IntlDateFormatter::SHORT); // For any DateTime - Date::setToStringFormat(\IntlDateFormatter::SHORT); // For any Date - - // The same method exists on Date, and DateTime - DateTime::setToStringFormat([ - \IntlDateFormatter::FULL, - \IntlDateFormatter::SHORT - ]); - // Outputs 'Sunday, January 31, 2021 at 10:11 PM' - echo $time; - - // The same method exists on Date and DateTime - DateTime::setToStringFormat("EEEE, MMMM dd, yyyy 'at' KK:mm:ss a"); - // Outputs 'Sunday, January 31, 2021 at 10:11:30 PM' - echo $time; - -It is recommended to always use the constants instead of directly passing a date -format string. - -.. note:: - Be aware that this is not a PHP Datetime string format! You need to use a - ICU date formatting string as specified in the following resource: - https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax. - -Formatting Relative Times -------------------------- - -.. php:method:: timeAgoInWords(array $options = []) - -Often it is useful to print times relative to the present:: - - $time = new DateTime('Jan 31, 2021'); - // On June 12, 2021, this would output '4 months, 1 week, 6 days ago' - echo $time->timeAgoInWords( - ['format' => 'MMM d, YYY', 'end' => '+1 year'] - ); - -The ``end`` option lets you define at which point after which relative times -should be formatted using the ``format`` option. The ``accuracy`` option lets -us control what level of detail should be used for each interval range:: - - // Outputs '4 months ago' - echo $time->timeAgoInWords([ - 'accuracy' => ['month' => 'month'], - 'end' => '1 year' - ]); - -By setting ``accuracy`` to a string, you can specify what is the maximum level -of detail you want output:: - - $time = new DateTime('+23 hours'); - // Outputs 'in about a day' - echo $time->timeAgoInWords([ - 'accuracy' => 'day' - ]); - -Conversion -========== - -.. php:method:: toQuarter() - -Once created, you can convert ``DateTime`` instances into timestamps or quarter -values:: - - $time = new DateTime('2021-01-31'); - echo $time->toQuarter(); // Outputs '1' - echo $time->toUnixString(); // Outputs '1612069200' - -Comparing With the Present -========================== - -.. php:method:: isYesterday() -.. php:method:: isThisWeek() -.. php:method:: isThisMonth() -.. php:method:: isThisYear() - -You can compare a ``DateTime`` instance with the present in a variety of ways:: - - $time = new DateTime('+3 days'); - - debug($time->isYesterday()); - debug($time->isThisWeek()); - debug($time->isThisMonth()); - debug($time->isThisYear()); - -Each of the above methods will return ``true``/``false`` based on whether or -not the ``DateTime`` instance matches the present. - -Comparing With Intervals -======================== - -.. php:method:: isWithinNext($interval) - -You can see if a ``DateTime`` instance falls within a given range using -``wasWithinLast()`` and ``isWithinNext()``:: - - $time = new DateTime('+3 days'); - - // Within 2 days. Outputs 'false' - debug($time->isWithinNext('2 days')); - - // Within 2 next weeks. Outputs 'true' - debug($time->isWithinNext('2 weeks')); - -.. php:method:: wasWithinLast($interval) - -You can also compare a ``DateTime`` instance within a range in the past:: - - $time = new DateTime('-72 hours'); - - // Within past 2 days. Outputs 'false' - debug($time->wasWithinLast('2 days')); - - // Within past 3 days. Outputs 'true' - debug($time->wasWithinLast('3 days')); - - // Within past 2 weeks. Outputs 'true' - debug($time->wasWithinLast('2 weeks')); - -.. end-time - -Date -==== - -.. php:class:: Date - -The immutable ``Date`` class in CakePHP represents calendar dates unaffected by -time and timezones. The ``Date`` class wraps the ``Cake\\Chronos\\ChronosDate`` class. - -.. note:: - - Unlike the ``DateTime`` class, ``Date`` does not extends the ``DateTimeInterface``. - So you cannot cannot directly compare a ``Date`` instance with a ``DateTime`` instance. - But you can do comparisons like ``$dateTime->toNative() > $date->toNative()``. - -Time -==== - -.. php:class:: Time - -The ``Time`` class represents clock times independent of date or time zones -Similar to the ``DateTime`` and ```Date`` classes, the ``Time`` class is also immutable. -It wraps the ``Cake\\Chronos\\ChronosTime`` class. - -Accepting Localized Request Data -================================ - -When creating text inputs that manipulate dates, you'll probably want to accept -and parse localized datetime strings. See the :ref:`parsing-localized-dates`. - -.. meta:: - :title lang=en: Time - :description lang=en: Time class helps you format time and test time. - :keywords lang=en: time,format time,timezone,unix epoch,time strings,time zone offset,utc,gmt - -Supported Timezones -=================== - -CakePHP supports all valid PHP timezones. For a list of supported timezones, `see this page `_. diff --git a/en/core-libraries/validation.rst b/en/core-libraries/validation.rst deleted file mode 100644 index 4e4b37ddca..0000000000 --- a/en/core-libraries/validation.rst +++ /dev/null @@ -1,634 +0,0 @@ -Validation -########## - -.. php:namespace:: Cake\Validation - -The validation package in CakePHP provides features to build validators that can -validate arbitrary arrays of data with ease. You can find a `list of available -Validation rules in the API -`__. - -.. _creating-validators: - -Creating Validators -=================== - -.. php:class:: Validator - -Validator objects define the rules that apply to a set of fields. -Validator objects contain a mapping between fields and validation sets. In -turn, the validation sets contain a collection of rules that apply to the field -they are attached to. Creating a validator is simple:: - - use Cake\Validation\Validator; - - $validator = new Validator(); - -Once created, you can start defining sets of rules for the fields you want to -validate:: - - $validator - ->requirePresence('title') - ->notEmptyString('title', 'Please fill this field') - ->add('title', [ - 'length' => [ - 'rule' => ['minLength', 10], - 'message' => 'Titles need to be at least 10 characters long', - ] - ]) - ->allowEmptyDateTime('published') - ->add('published', 'boolean', [ - 'rule' => 'boolean', - ]) - ->requirePresence('body') - ->add('body', 'length', [ - 'rule' => ['minLength', 50], - 'message' => 'Articles must have a substantial body.', - ]); - -As seen in the example above, validators are built with a fluent interface that -allows you to define rules for each field you want to validate. - -There were a few methods called in the example above, so let's go over the -various features. The ``add()`` method allows you to add new rules to -a validator. You can either add rules individually or in groups as seen above. - -Requiring Field Presence ------------------------- - -The ``requirePresence()`` method requires the field to be present in any -validated array. If the field is absent, validation will fail. The -``requirePresence()`` method has 4 modes: - -* ``true`` The field's presence is always required. -* ``false`` The field's presence is not required. -* ``create`` The field's presence is required when validating a **create** - operation. -* ``update`` The field's presence is required when validating an **update** - operation. - -By default, ``true`` is used. Key presence is checked by using -``array_key_exists()`` so that null values will count as present. You can set -the mode using the second parameter:: - - $validator->requirePresence('author_id', 'create'); - -If you have multiple fields that are required, you can define them as a list:: - - // Define multiple fields for create - $validator->requirePresence(['author_id', 'title'], 'create'); - - // Define multiple fields for mixed modes - $validator->requirePresence([ - 'author_id' => [ - 'mode' => 'create', - 'message' => 'An author is required.', - ], - 'published' => [ - 'mode' => 'update', - 'message' => 'The published state is required.', - ], - ]); - -Allowing Empty Fields ---------------------- - -Validators offer several methods to control which fields accept empty values and -which empty values are accepted and not forwarded to other validation rules for -the named field. CakePHP provides empty value support for different shapes -of data: - -#. ``allowEmptyString()`` Should be used when you want to only accept - an empty string. -#. ``allowEmptyArray()`` Should be used when you want to accept an array. -#. ``allowEmptyDate()`` Should be used when you want to accept an empty string, - or an array that is marshalled into a date field. -#. ``allowEmptyTime()`` Should be used when you want to accept an empty string, - or an array that is marshalled into a time field. -#. ``allowEmptyDateTime()`` Should be used when you want to accept an empty - string or an array that is marshalled into a datetime or timestamp field. -#. ``allowEmptyFile()`` Should be used when you want to accept an array that - contains an empty uploaded file. - -You also can use following specific validators: ``notEmptyString()``, ``notEmptyArray()``, ``notEmptyFile()``, ``notEmptyDate()``, ``notEmptyTime()``, ``notEmptyDateTime()``. - -The ``allowEmpty*`` methods support a ``when`` parameter that allows you to control -when a field can or cannot be empty: - -* ``false`` The field is not allowed to be empty. -* ``create`` The field can be empty when validating a **create** - operation. -* ``update`` The field can be empty when validating an **update** - operation. -* A callback that returns ``true`` or ``false`` to indicate whether a field is - allowed to be empty. See the :ref:`conditional-validation` section for examples on - how to use this parameter. - -An example of these methods in action is:: - - $validator->allowEmptyDateTime('published') - ->allowEmptyString('title', 'Title cannot be empty', false) - ->allowEmptyString('body', 'Body cannot be empty', 'update') - ->allowEmptyFile('header_image', 'update'); - ->allowEmptyDateTime('posted', 'update'); - -Adding Validation Rules ------------------------ - -The ``Validator`` class provides methods that make building validators simple -and expressive. For example adding validation rules to a username could look -like:: - - $validator = new Validator(); - $validator - ->email('username') - ->ascii('username') - ->lengthBetween('username', [4, 8]); - -See the `Validator API documentation -`_ for the -full set of validator methods. - -.. _custom-validation-rules: - -Using Custom Validation Rules ------------------------------ - -In addition to using methods on the ``Validator``, and coming from providers, you -can also use any callable, including anonymous functions, as validation rules:: - - // Use a global function - $validator->add('title', 'custom', [ - 'rule' => 'validate_title', - 'message' => 'The title is not valid' - ]); - - // Use an array callable that is not in a provider - $validator->add('title', 'custom', [ - 'rule' => [$this, 'method'], - 'message' => 'The title is not valid' - ]); - - // Use a closure - $extra = 'Some additional value needed inside the closure'; - $validator->add('title', 'custom', [ - 'rule' => function ($value, $context) use ($extra) { - // Custom logic that returns true/false - }, - 'message' => 'The title is not valid' - ]); - - // Use a rule from a custom provider - $validator->add('title', 'custom', [ - 'rule' => 'customRule', - 'provider' => 'custom', - 'message' => 'The title is not unique enough' - ]); - -Closures or callable methods will receive 2 arguments when called. The first -will be the value for the field being validated. The second is a context array -containing data related to the validation process: - -- **data**: The original data passed to the validation method, useful if you - plan to create rules comparing values. -- **providers**: The complete list of rule provider objects, useful if you - need to create complex rules by calling multiple providers. -- **newRecord**: Whether the validation call is for a new record or - a preexisting one. - -Closures should return boolean true if the validation passes. If it fails, -return boolean false or for a custom error message return a string, see the -:ref:`Conditional/Dynamic Error Messages ` -section for further details. - -.. _dynamic_validation_error_messages: - -Conditional/Dynamic Error Messages ----------------------------------- - -Validation rule methods, being it :ref:`custom callables `, -or :ref:`methods supplied by providers `, can either -return a boolean, indicating whether the validation succeeded, or they can return -a string, which means that the validation failed, and that the returned string -should be used as the error message. - -Possible existing error messages defined via the ``message`` option will be -overwritten by the ones returned from the validation rule method:: - - $validator->add('length', 'custom', [ - 'rule' => function ($value, $context) { - if (!$value) { - return false; - } - - if ($value < 10) { - return 'Error message when value is less than 10'; - } - - if ($value > 20) { - return 'Error message when value is greater than 20'; - } - - return true; - }, - 'message' => 'Generic error message used when `false` is returned' - ]); - -.. _conditional-validation: - -Conditional Validation ----------------------- - -When defining validation rules, you can use the ``on`` key to define when -a validation rule should be applied. If left undefined, the rule will always be -applied. Other valid values are ``create`` and ``update``. Using one of these -values will make the rule apply to only create or update operations. - -Additionally, you can provide a callable function that will determine whether or -not a particular rule should be applied:: - - $validator->add('picture', 'file', [ - 'rule' => ['mimeType', ['image/jpeg', 'image/png']], - 'on' => function ($context) { - return !empty($context['data']['show_profile_picture']); - } - ]); - -You can access the other submitted field values using the ``$context['data']`` -array. The above example will make the rule for 'picture' optional depending on -whether the value for ``show_profile_picture`` is empty. You could also use the -``uploadedFile`` validation rule to create optional file upload inputs:: - - $validator->add('picture', 'file', [ - 'rule' => ['uploadedFile', ['optional' => true]], - ]); - -The ``allowEmpty*``, ``notEmpty*`` and ``requirePresence()`` methods will also -accept a callback function as their last argument. If present, the callback -determines whether or not the rule should be applied. For example, a field is -sometimes allowed to be empty:: - - $validator->allowEmptyString('tax', 'This field is required', function ($context) { - return !$context['data']['is_taxable']; - }); - -Likewise, a field can be required to be populated when certain conditions are -met:: - - $validator->notEmptyString('email_frequency', 'This field is required', function ($context) { - return !empty($context['data']['wants_newsletter']); - }); - -In the above example, the ``email_frequency`` field cannot be left empty if the -the user wants to receive the newsletter. - -Further it's also possible to require a field to be present under certain -conditions only:: - - $validator->requirePresence('full_name', function ($context) { - if (isset($context['data']['action'])) { - return $context['data']['action'] === 'subscribe'; - } - - return false; - }); - $validator->requirePresence('email'); - -This would require the ``full_name`` field to be present only in case the user -wants to create a subscription, while the ``email`` field would always be -required. - -The ``$context`` parameter passed to custom conditional callbacks contains the -following keys: - -* ``data`` The data being validated. -* ``newRecord`` a boolean indicating whether a new or existing record is being - validated. -* ``field`` The current field being validated. -* ``providers`` The validation providers attached to the current validator. - - -Marking Rules as the Last to Run --------------------------------- - -When fields have multiple rules, each validation rule will be run even if the -previous one has failed. This allows you to collect as many validation errors as -you can in a single pass. If you want to stop execution after -a specific rule has failed, you can set the ``last`` option to ``true``:: - - $validator = new Validator(); - $validator - ->add('body', [ - 'minLength' => [ - 'rule' => ['minLength', 10], - 'last' => true, - 'message' => 'Comments must have a substantial body.', - ], - 'maxLength' => [ - 'rule' => ['maxLength', 250], - 'message' => 'Comments cannot be too long.', - ], - ]); - -If the minLength rule fails in the example above, the maxLength rule will not be -run. - -Make Rules 'last' by default -============================ - -You can have the ``last`` option automatically applied to each rule you can use -the ``setStopOnFailure()`` method to enable this behavior:: - - public function validationDefault(Validator $validator): Validator - { - $validator - ->setStopOnFailure() - ->requirePresence('email', 'create') - ->notBlank('email') - ->email('email'); - - return $validator; - } - -When enabled all fields will stop validation on the first failing rule instead -of checking all possible rules. In this case only a single error message will -appear under the form field. - -.. _adding-validation-providers: - -Adding Validation Providers ---------------------------- - -The ``Validator``, ``ValidationSet`` and ``ValidationRule`` classes do not -provide any validation methods themselves. Validation rules come from -'providers'. You can bind any number of providers to a Validator object. -Validator instances come with a 'default' provider setup automatically. The -default provider is mapped to the :php:class:`~Cake\\Validation\\Validation` -class. This makes it simple to use the methods on that class as validation -rules. When using Validators and the ORM together, additional providers are -configured for the table and entity objects. You can use the ``setProvider()`` -method to add any additional providers your application needs:: - - $validator = new Validator(); - - // Use an object instance. - $validator->setProvider('custom', $myObject); - - // Use a class name. Methods must be static. - $validator->setProvider('custom', 'App\Model\Validation'); - -Validation providers can be objects, or class names. If a class name is used the -methods must be static. To use a provider other than 'default', be sure to set -the ``provider`` key in your rule:: - - // Use a rule from the table provider - $validator->add('title', 'custom', [ - 'rule' => 'customTableMethod', - 'provider' => 'table' - ]); - -If you wish to add a ``provider`` to all ``Validator`` objects that are created -in the future, you can use the ``addDefaultProvider()`` method as follows:: - - use Cake\Validation\Validator; - - // Use an object instance. - Validator::addDefaultProvider('custom', $myObject); - - // Use a class name. Methods must be static. - Validator::addDefaultProvider('custom', 'App\Model\Validation'); - -.. note:: - - DefaultProviders must be added before the ``Validator`` object is created - therefore **config/bootstrap.php** is the best place to set up your - default providers. - -You can use the `Localized plugin `_ to -get providers based on countries. With this plugin, you'll be able to validate -model fields, depending on a country, ie:: - - namespace App\Model\Table; - - use Cake\ORM\Table; - use Cake\Validation\Validator; - - class PostsTable extends Table - { - public function validationDefault(Validator $validator): Validator - { - // add the provider to the validator - $validator->setProvider('fr', 'Cake\Localized\Validation\FrValidation'); - // use the provider in a field validation rule - $validator->add('phoneField', 'myCustomRuleNameForPhone', [ - 'rule' => 'phone', - 'provider' => 'fr' - ]); - - return $validator; - } - } - -The localized plugin uses the two letter ISO code of the countries for -validation, like en, fr, de. - -There are a few methods that are common to all classes, defined through the -`ValidationInterface interface `_:: - - phone() to check a phone number - postal() to check a postal code - personId() to check a country specific person ID - -Nesting Validators ------------------- - -When validating :doc:`/core-libraries/form` with nested data, or when working -with models that contain array data types, it is necessary to validate the -nested data you have. CakePHP makes it simple to add validators to specific -attributes. For example, assume you are working with a non-relational database -and need to store an article and its comments:: - - $data = [ - 'title' => 'Best article', - 'comments' => [ - ['comment' => ''], - ], - ]; - -To validate the comments you would use a nested validator:: - - $validator = new Validator(); - $validator->add('title', 'not-blank', ['rule' => 'notBlank']); - - $commentValidator = new Validator(); - $commentValidator->add('comment', 'not-blank', ['rule' => 'notBlank']); - - // Connect the nested validators. - $validator->addNestedMany('comments', $commentValidator); - - // Get all errors including those from nested validators. - $validator->validate($data); - -You can create 1:1 'relationships' with ``addNested()`` and 1:N 'relationships' -with ``addNestedMany()``. With both methods, the nested validator's errors will -contribute to the parent validator's errors and influence the final result. -Like other validator features, nested validators support error messages and -conditional application:: - - $validator->addNestedMany( - 'comments', - $commentValidator, - 'Invalid comment', - 'create' - ); - -The error message for a nested validator can be found in the ``_nested`` key. - -.. _reusable-validators: - -Creating Reusable Validators ----------------------------- - -While defining validators inline where they are used makes for good example -code, it doesn't lead to maintainable applications. Instead, you should -create ``Validator`` sub-classes for your reusable validation logic:: - - // In src/Model/Validation/ContactValidator.php - namespace App\Model\Validation; - - use Cake\Validation\Validator; - - class ContactValidator extends Validator - { - public function __construct() - { - parent::__construct(); - // Add validation rules here. - } - } - -Validating Data -=============== - -Now that you've created a validator and added the rules you want to it, you can -start using it to validate data. Validators are able to validate array -data. For example, if you wanted to validate a contact form before creating and -sending an email you could do the following:: - - use Cake\Validation\Validator; - - $validator = new Validator(); - $validator - ->requirePresence('email') - ->add('email', 'validFormat', [ - 'rule' => 'email', - 'message' => 'E-mail must be valid', - ]) - ->requirePresence('name') - ->notEmptyString('name', 'We need your name.') - ->requirePresence('comment') - ->notEmptyString('comment', 'You need to give a comment.'); - - $errors = $validator->validate($this->request->getData()); - if (empty($errors)) { - // Send an email. - } - -The ``getErrors()`` method will return a non-empty array when there are validation -failures. The returned array of errors will be structured like:: - - $errors = [ - 'email' => ['E-mail must be valid'], - ]; - -If you have multiple errors on a single field, an array of error messages will -be returned per field. By default the ``getErrors()`` method applies rules for -the 'create' mode. If you'd like to apply 'update' rules you can do the -following:: - - $errors = $validator->validate($this->request->getData(), false); - if (!$errors) { - // Send an email. - } - -.. note:: - - If you need to validate entities you should use methods like - :php:meth:`~Cake\\ORM\\Table::newEntity()`, - :php:meth:`~Cake\\ORM\\Table::newEntities()`, - :php:meth:`~Cake\\ORM\\Table::patchEntity()`, - :php:meth:`~Cake\\ORM\\Table::patchEntities()` - as they are designed for that. - -Validating Entity Data -====================== - -Validation is meant for checking request data coming from forms or other user -interfaces used to populate the entities. - -The request data is validated automatically when using the ``newEntity()``, -``newEntities()``, ``patchEntity()`` or ``patchEntities()`` methods of ``Table`` class:: - - // In the ArticlesController class - $article = $this->Articles->newEntity($this->request->getData()); - if ($article->getErrors()) { - // Do work to show error messages. - } - -Similarly, when you need to validate multiple entities at a time, you can -use the ``newEntities()`` method:: - - // In the ArticlesController class - $entities = $this->Articles->newEntities($this->request->getData()); - foreach ($entities as $entity) { - if (!$entity->getErrors()) { - $this->Articles->save($entity); - } - } - -The ``newEntity()``, ``patchEntity()``, ``newEntities()`` and ``patchEntities()`` -methods allow you to specify which associations are validated, and which -validation sets to apply using the ``options`` parameter:: - - $valid = $this->Articles->newEntity($article, [ - 'associated' => [ - 'Comments' => [ - 'associated' => ['User'], - 'validate' => 'special', - ], - ], - ]); - -Apart from validating user provided data maintaining integrity of data regardless -where it came from is important. To solve this problem CakePHP offers a second -level of validation which is called "application rules". You can read more about -them in the :ref:`Applying Application Rules ` section. - -Core Validation Rules -===================== - -CakePHP provides a basic suite of validation methods in the ``Validation`` -class. The Validation class contains a variety of static methods that provide -validators for several common validation situations. - -The `API documentation -`_ for the -``Validation`` class provides a good list of the validation rules that are -available, and their basic usage. - -Some of the validation methods accept additional parameters to define boundary -conditions or valid options. You can provide these boundary conditions and -options as follows:: - - $validator = new Validator(); - $validator - ->add('title', 'minLength', [ - 'rule' => ['minLength', 10], - ]) - ->add('rating', 'validValue', [ - 'rule' => ['range', 1, 5], - ]); - -Core rules that take additional parameters should have an array for the -``rule`` key that contains the rule as the first element, and the additional -parameters as the remaining parameters. diff --git a/en/core-libraries/xml.rst b/en/core-libraries/xml.rst deleted file mode 100644 index 8debe215c5..0000000000 --- a/en/core-libraries/xml.rst +++ /dev/null @@ -1,189 +0,0 @@ -Xml -### - -.. php:namespace:: Cake\Utility - -.. php:class:: Xml - -The Xml class allows you to transform arrays into SimpleXMLElement or -DOMDocument objects, and back into arrays again. - -Loading XML documents -===================== - -.. php:staticmethod:: build($input, array $options = []) - -You can load XML-ish data using ``Xml::build()``. Depending on your -``$options`` parameter, this method will return a SimpleXMLElement (default) -or DOMDocument object. You can use ``Xml::build()`` to build XML -objects from a variety of sources. For example, you can load XML from -strings:: - - $text = ' - - 1 - Best post - ... - '; - $xml = Xml::build($text); - -You can also build Xml objects from local files by overriding the default option:: - - // Local file - $xml = Xml::build('/home/awesome/unicorns.xml', ['readFile' => true]); - -You can also build Xml objects using an array:: - - $data = [ - 'post' => [ - 'id' => 1, - 'title' => 'Best post', - 'body' => ' ... ', - ] - ]; - $xml = Xml::build($data); - -If your input is invalid, the Xml class will throw an exception:: - - $xmlString = 'What is XML?'; - try { - $xmlObject = Xml::build($xmlString); // Here will throw an exception - } catch (\Cake\Utility\Exception\XmlException $e) { - throw new InternalErrorException(); - } - -.. note:: - - `DOMDocument `_ and - `SimpleXML `_ implement different APIs. - Be sure to use the correct methods on the object you request from Xml. - -Loading HTML documents -====================== - -HTML documents can be parsed into ``SimpleXmlElement`` or ``DOMDocument`` -objects with ``loadHtml()``:: - - $html = Xml::loadHtml($htmlString, ['return' => 'domdocument']); - -By default entity loading and huge document parsing are disabled. These modes -can be enabled with the ``loadEntities`` and ``parseHuge`` options respectively. - -Transforming a XML String in Array -================================== - -.. php:staticmethod:: toArray($obj) - -Converting XML strings into arrays is simple with the Xml class as well. By -default you'll get a SimpleXml object back:: - - $xmlString = 'value'; - $xmlArray = Xml::toArray(Xml::build($xmlString)); - -If your XML is invalid a ``Cake\Utility\Exception\XmlException`` will be raised. - -Transforming an Array into a String of XML -========================================== - -:: - - $xmlArray = ['root' => ['child' => 'value']]; - // You can use Xml::build() too. - $xmlObject = Xml::fromArray($xmlArray, ['format' => 'tags']); - $xmlString = $xmlObject->asXML(); - -Your array must have only one element in the "top level" and it can not be -numeric. If the array is not in this format, Xml will throw an exception. -Examples of invalid arrays:: - - // Top level with numeric key - [ - ['key' => 'value'] - ]; - - // Multiple keys in top level - [ - 'key1' => 'first value', - 'key2' => 'other value' - ]; - -By default array values will be output as XML tags. If you want to define -attributes or text values you can prefix the keys that are supposed to be -attributes with ``@``. For value text, use ``@`` as the key:: - - $xmlArray = [ - 'project' => [ - '@id' => 1, - 'name' => 'Name of project, as tag', - '@' => 'Value of project', - ], - ]; - $xmlObject = Xml::fromArray($xmlArray); - $xmlString = $xmlObject->asXML(); - -The content of ``$xmlString`` will be:: - - - Value of projectName of project, as tag - -Using Namespaces ----------------- - -To use XML Namespaces, create a key in your array with the name ``xmlns:`` -in a generic namespace or input the prefix ``xmlns:`` in a custom namespace. See -the samples:: - - $xmlArray = [ - 'root' => [ - 'xmlns:' => 'https://cakephp.org', - 'child' => 'value', - ] - ]; - $xml1 = Xml::fromArray($xmlArray); - - $xmlArray( - 'root' => [ - 'tag' => [ - 'xmlns:pref' => 'https://cakephp.org', - 'pref:item' => [ - 'item 1', - 'item 2' - ] - ] - ] - ); - $xml2 = Xml::fromArray($xmlArray); - -The value of ``$xml1`` and ``$xml2`` will be, respectively:: - - - value - - - item 1item 2 - -Creating a Child ----------------- - -After you have created your XML document, you just use the native interfaces for -your document type to add, remove, or manipulate child nodes:: - - // Using SimpleXML - $myXmlOriginal = 'value'; - $xml = Xml::build($myXmlOriginal); - $xml->root->addChild('young', 'new value'); - - // Using DOMDocument - $myXmlOriginal = 'value'; - $xml = Xml::build($myXmlOriginal, ['return' => 'domdocument']); - $child = $xml->createElement('young', 'new value'); - $xml->firstChild->appendChild($child); - -.. tip:: - - After manipulating your XML using SimpleXMLElement or DomDocument you can - use ``Xml::toArray()`` without a problem. - -.. meta:: - :title lang=en: Xml - :keywords lang=en: array php,xml class,xml objects,post xml,xml object,string url,string data,xml parser,php 5,bakery,constructor,php xml,cakephp,php file,unicorns,meth diff --git a/en/debug-kit.rst b/en/debug-kit.rst deleted file mode 100644 index 050b73a5c6..0000000000 --- a/en/debug-kit.rst +++ /dev/null @@ -1,4 +0,0 @@ -Debug Kit -######### - -This page has `moved `__. diff --git a/en/deployment.rst b/en/deployment.rst deleted file mode 100644 index bcb31d85a1..0000000000 --- a/en/deployment.rst +++ /dev/null @@ -1,150 +0,0 @@ -Deployment -########## - -Once your app is ready to be deployed there are a few things you should do. - -Moving files -============ - -You can clone your repository onto your production server and then checkout the -commit/tag you want to run. Then, run ``composer install``. While this requires -some knowledge about git and an existing install of ``git`` and ``composer`` -this process will take care about library dependencies and file and folder -permissions. - -Be aware that when deploying via FTP you will have to fix file and -folder permissions. - -You can also use this deployment technique to setup a staging or demo-server -(pre-production) and keep it in sync with your local environment. - -Adjusting Configuration -======================= - -You'll want to make a few adjustments to your application's configuration for -a production environment. The value of ``debug`` is extremely important. -Turning debug = ``false`` disables a number of development features that should -never be exposed to the Internet at large. Disabling debug changes the following -features: - -* Debug messages, created with :php:func:`pr()`, :php:func:`debug()` and :php:func:`dd()` are - disabled. -* Core CakePHP caches duration are defaulted to 365 days, instead of 10 seconds - as in development. -* Error views are less informative, and generic error pages are displayed - instead of detailed error messages with stack traces. -* PHP Warnings and Errors are not displayed. - -In addition to the above, many plugins and application extensions use ``debug`` -to modify their behavior. - -You can check against an environment variable to set the debug level dynamically -between environments. This will avoid deploying an application with debug -``true`` and also save yourself from having to change the debug level each time -before deploying to a production environment. - -For example, you can set an environment variable in your Apache configuration:: - - SetEnv CAKEPHP_DEBUG 1 - -And then you can set the debug level dynamically in **app_local.php**:: - - $debug = (bool)getenv('CAKEPHP_DEBUG'); - - return [ - 'debug' => $debug, - ..... - ]; - -It is recommended that you put configuration that is shared across all -of your application's environments in **config/app.php**. For configuration that -varies between environments either use **config/app_local.php** or environment -variables. - -Check Your Security -=================== - -If you're throwing your application out into the wild, it's a good idea to make -sure it doesn't have any obvious leaks: - -* Ensure you are using the :ref:`csrf-middleware` component or middleware. -* You may want to enable the :doc:`/controllers/components/form-protection` component. - It can help prevent several types of form tampering and reduce the possibility - of mass-assignment issues. -* Ensure your models have the correct :doc:`/core-libraries/validation` rules - enabled. -* Check that only your ``webroot`` directory is publicly visible, and that your - secrets (such as your app salt, and any security keys) are private and unique - as well. - -Set Document Root -================= - -Setting the document root correctly on your application is an important step to -keeping your code secure and your application safer. CakePHP applications -should have the document root set to the application's ``webroot``. This -makes the application and configuration files inaccessible through a URL. -Setting the document root is different for different webservers. See the -:ref:`url-rewriting` documentation for webserver specific -information. - -In all cases you will want to set the virtual host/domain's document to be -``webroot/``. This removes the possibility of files outside of the webroot -directory being executed. - -.. _symlink-assets: - -Improve Your Application's Performance -====================================== - -Class loading can take a big share of your application's processing time. -In order to avoid this problem, it is recommended that you run this command in -your production server once the application is deployed:: - - php composer.phar dumpautoload -o - -Since handling static assets, such as images, JavaScript and CSS files of -plugins, through the ``Dispatcher`` is incredibly inefficient, it is strongly -recommended to symlink them for production. This can be done by using -the ``plugin`` command:: - - bin/cake plugin assets symlink - -The above command will symlink the ``webroot`` directory of all loaded plugins -to appropriate path in the app's ``webroot`` directory. - -If your filesystem doesn't allow creating symlinks the directories will be -copied instead of being symlinked. You can also explicitly copy the directories -using:: - - bin/cake plugin assets copy - -CakePHP uses ``assert()`` internally to provide runtime type checking and -provide better error messages during development. You can have PHP skip these -assertions by updating your ``php.ini`` to include: - -.. code-block:: ini - - ; Turn off assert() code generation. - zend.assertions = -1 - -Skipping code generation for ``assert()`` will yield faster runtime performance, -and is recommended for applications that have good test coverage or that are -using a static analyzer. - -Deploying an update -=================== - -On each deploy you'll likely have a few tasks to co-ordinate on your web server. Some typical ones -are: - -1. Install dependencies with ``composer install``. Avoid using ``composer - update`` when doing deploys as you could get unexpected versions of packages. -2. Run database `migrations `__ with either the Migrations plugin - or another tool. -3. Clear model schema cache with ``bin/cake schema_cache clear``. The :doc:`/console-commands/schema-cache` - has more information on this command. - -.. meta:: - :title lang=en: Deployment - :keywords lang=en: stack traces,application extensions,set document,installation documentation,development features,generic error,document root,func,debug,caches,error messages,configuration files,webroot,deployment,cakephp,applications diff --git a/en/development/application.rst b/en/development/application.rst deleted file mode 100644 index 767f691327..0000000000 --- a/en/development/application.rst +++ /dev/null @@ -1,90 +0,0 @@ -Application -########### - -The ``Application`` is the heart of your application. It controls -how your application is configured, and what plugins, middleware, console -commands and routes are included. - -You can find your ``Application`` class at **src/Application.php**. By default -it will be pretty slim and only define a few default -:doc:`/controllers/middleware`. Applications can define the following hook -methods: - -* ``bootstrap`` Used to load :doc:`configuration files - `, define constants and other global functions. - By default this will include **config/bootstrap.php**. This is the ideal place - to load :doc:`/plugins` and global :doc:`event listeners `. -* ``routes`` Used to load :doc:`routes `. By default this - will include **config/routes.php**. -* ``middleware`` Used to add :doc:`middleware ` to your application. -* ``console`` Used to add :doc:`console commands ` to your - application. By default this will automatically discover console commands in - your application and all plugins. - -Bootstrapping your Application -============================== - -If you have any additional configuration needs, you should add them to your -application's **config/bootstrap.php** file. This file is included before each -request, and CLI command. - -This file is ideal for a number of common bootstrapping tasks: - -- Defining convenience functions. -- Declaring constants. -- Defining cache configuration. -- Defining logging configuration. -- Loading custom inflections. -- Loading configuration files. - -It might be tempting to place formatting functions there in order to use them in -your controllers. As you'll see in the :doc:`/controllers` and :doc:`/views` -sections there are better ways you add custom logic to your application. - -.. _application-bootstrap: - -Application::bootstrap() ------------------------- - -In addition to the **config/bootstrap.php** file which should be used to -configure low-level concerns of your application, you can also use the -``Application::bootstrap()`` hook method to load/initialize plugins, and attach -global event listeners:: - - // in src/Application.php - namespace App; - - use Cake\Http\BaseApplication; - - class Application extends BaseApplication - { - public function bootstrap() - { - // Call the parent to `require_once` config/bootstrap.php - parent::bootstrap(); - - // CakePHP has the ability to fallback to using the `Cake\ORM\Table` - // class to represent your database tables when a related class is - // not created for that table. But using this "auto-tables" feature - // can make debugging more difficult in some scenarios. So we disable - // this feature except for the CLI environment (since the classes - // would not be present when using the `bake` code generation tool). - if (PHP_SAPI !== 'cli') { - FactoryLocator::add( - 'Table', - (new TableLocator())->allowFallbackClass(false) - ); - } - - // Load MyPlugin - $this->addPlugin('MyPlugin'); - } - } - -Loading plugins and events in ``Application::bootstrap()`` makes -:ref:`integration-testing` easier as events and routes will be re-processed on -each test method. - -.. meta:: - :title lang=en: CakePHP Application - :keywords lang=en: http, middleware, psr-7, events, plugins, application, baseapplication,auto tables,auto-tables,generic table,class diff --git a/en/development/configuration.rst b/en/development/configuration.rst deleted file mode 100644 index e96595c6e1..0000000000 --- a/en/development/configuration.rst +++ /dev/null @@ -1,569 +0,0 @@ -Configuration -############# - -While conventions remove the need to configure all of CakePHP, you'll still need -to configure a few things like your database credentials. - -Additionally, there are optional configuration options that allow you to swap -out default values & implementations with ones tailored to your application. - -.. index:: app.php, app_local.example.php - -.. index:: configuration - -Configuring your Application -============================ - -Configuration is generally stored in either PHP or INI files, and loaded during -the application bootstrap. CakePHP comes with one configuration file by default, -but if required you can add additional configuration files and load them in -your application's bootstrap code. :php:class:`Cake\\Core\\Configure` is used -for global configuration, and classes like ``Cache`` provide ``setConfig()`` -methods to make configuration simple and transparent. - -The application skeleton features a **config/app.php** file which should contain -configuration that doesn't vary across the various environments your application -is deployed in. The **config/app_local.php** file should contain the -configuration data that varies between environments and should be managed by -configuration management, or your deployment tooling. Both of these files reference environment variables -through the ``env()`` function that enables configuration values to set through -the server environment. - -Loading Additional Configuration Files --------------------------------------- - -If your application has many configuration options it can be helpful to split -configuration into multiple files. After creating each of the files in your -**config/** directory you can load them in **bootstrap.php**:: - - use Cake\Core\Configure; - use Cake\Core\Configure\Engine\PhpConfig; - - Configure::setConfig('default', new PhpConfig()); - Configure::load('app', 'default', false); - Configure::load('other_config', 'default'); - -.. _environment-variables: - -Environment Variables -===================== - -Many modern cloud providers, like Heroku, let you define environment -variables for configuration data. You can configure your CakePHP through -environment variables in the `12factor app style `_. -Environment variables allow your application to require less state making your -application easier to manage when it is deployed across a number of -environments. - -As you can see in your **app.php**, the ``env()`` function is used to read -configuration from the environment, and build the application configuration. -CakePHP uses :term:`DSN` strings for databases, logs, email transports and cache -configurations allowing you to easily vary these libraries in each environment. - -For local development, CakePHP leverages `dotenv -`_ to make local development -automatically reload environment variables. Use composer to require this library -and then there is a block of code in ``bootstrap.php`` that needs to be -uncommented to harness it. - -You will see a ``config/.env.example`` in your -application. By copying this file into ``config/.env`` and customizing the -values you can configure your application. - -You should avoid committing the ``config/.env`` file to your repository and -instead use the ``config/.env.example`` as a template with placeholder values so -everyone on your team knows what environment variables are in use and what -should go in each one. - -Once your environment variables have been set, you can use ``env()`` to read -data from the environment:: - - $debug = env('APP_DEBUG', false); - -The second value passed to the env function is the default value. This value -will be used if no environment variable exists for the given key. - -.. _general-configuration: - -General Configuration ---------------------- - -Below is a description of the variables and how they affect your CakePHP -application. - -debug - Changes CakePHP debugging output. ``false`` = Production mode. No error - messages, errors, or warnings shown. ``true`` = Errors and warnings shown. -App.namespace - The namespace to find app classes under. - - .. note:: - - When changing the namespace in your configuration, you will also - need to update your **composer.json** file to use this namespace - as well. Additionally, create a new autoloader by running - ``php composer.phar dumpautoload``. - -.. _core-configuration-baseurl: - -App.baseUrl - Un-comment this definition if you **don’t** plan to use Apache’s - mod\_rewrite with CakePHP. Don’t forget to remove your .htaccess - files too. -App.base - The base directory the app resides in. If ``false`` this - will be auto detected. If not ``false``, ensure your string starts - with a `/` and does NOT end with a `/`. For example, `/basedir` is a valid - App.base. -App.encoding - Define what encoding your application uses. This encoding - is used to generate the charset in the layout, and encode entities. - It should match the encoding values specified for your database. -App.webroot - The webroot directory. -App.wwwRoot - The file path to webroot. -App.fullBaseUrl - The fully qualified domain name (including protocol) to your application's - root. This is used when generating absolute URLs. By default this value - is generated using the ``$_SERVER`` environment. However, you should define it - manually to optimize performance or if you are concerned about people - manipulating the ``Host`` header. - In a CLI context (from command) the `fullBaseUrl` cannot be read from $_SERVER, - as there is no webserver involved. You do need to specify it yourself if - you do need to generate URLs from a shell (for example, when sending emails). -App.imageBaseUrl - Web path to the public images directory under webroot. If you are using - a :term:`CDN` you should set this value to the CDN's location. -App.cssBaseUrl - Web path to the public css directory under webroot. If you are using - a :term:`CDN` you should set this value to the CDN's location. -App.jsBaseUrl - Web path to the public js directory under webroot. If you are using - a :term:`CDN` you should set this value to the CDN's location. -App.paths - Configure paths for non class based resources. Supports the - ``plugins``, ``templates``, ``locales`` subkeys, which allow the definition - of paths for plugins, view templates and locale files respectively. -App.uploadedFilesAsObjects - Defines whether uploaded files are being represented as objects (``true``), - or arrays (``false``). This option is being treated as enabled by default. - See the :ref:`File Uploads section ` in the Request & - Response Objects chapter for more information. -Security.salt - A random string used in hashing. This value is also used as the - HMAC salt when doing symmetric encryption. -Asset.timestamp - Appends a timestamp which is last modified time of the particular - file at the end of asset files URLs (CSS, JavaScript, Image) when - using proper helpers. Valid values: - - - (bool) ``false`` - Doesn't do anything (default) - - (bool) ``true`` - Appends the timestamp when debug is ``true`` - - (string) 'force' - Always appends the timestamp. -Asset.cacheTime - Sets the asset cache time. This determines the http header ``Cache-Control``'s - ``max-age``, and the http header's ``Expire``'s time for assets. - This can take anything that you version of PHP's `strtotime function - `_ can take. - The default is ``+1 day``. - -Using a CDN ------------ - -To use a CDN for loading your static assets, change ``App.imageBaseUrl``, -``App.cssBaseUrl``, ``App.jsBaseUrl`` to point the CDN URI, for example: -``https://mycdn.example.com/`` (note the trailing ``/``). - -All images, scripts and styles loaded via HtmlHelper will prepend the absolute -CDN path, matching the same relative path used in the application. Please note -there is a specific use case when using plugin based assets: plugins will not -use the plugin's prefix when absolute ``...BaseUrl`` URI is used, for example By -default: - -* ``$this->Helper->assetUrl('TestPlugin.logo.png')`` resolves to ``test_plugin/logo.png`` - -If you set ``App.imageBaseUrl`` to ``https://mycdn.example.com/``: - -* ``$this->Helper->assetUrl('TestPlugin.logo.png')`` resolves to ``https://mycdn.example.com/logo.png``. - -Database Configuration ----------------------- - -See the :ref:`Database Configuration ` for information -on configuring your database connections. - -Caching Configuration ---------------------- - -See the :ref:`Caching Configuration ` for information on -configuring caching in CakePHP. - -Error and Exception Handling Configuration ------------------------------------------- - -See the :ref:`Error and Exception Configuration ` for -information on configuring error and exception handlers. - -Logging Configuration ---------------------- - -See the :ref:`log-configuration` for information on configuring logging in -CakePHP. - -Email Configuration -------------------- - -See the :ref:`Email Configuration ` for information on -configuring email presets in CakePHP. - -Session Configuration ---------------------- - -See the :ref:`session-configuration` for information on configuring session -handling in CakePHP. - -Routing configuration ---------------------- - -See the :ref:`Routes Configuration ` for more information -on configuring routing and creating routes for your application. - -.. _additional-class-paths: - -Additional Class Paths -====================== - -Additional class paths are setup through the autoloaders your application uses. -When using ``composer`` to generate your autoloader, you could do the following, -to provide fallback paths for controllers in your application:: - - "autoload": { - "psr-4": { - "App\\Controller\\": "/path/to/directory/with/controller/folders/", - "App\\": "src/" - } - } - -The above would setup paths for both the ``App`` and ``App\Controller`` -namespace. The first key will be searched, and if that path does not contain the -class/file the second key will be searched. You can also map a single namespace -to multiple directories with the following:: - - "autoload": { - "psr-4": { - "App\\": ["src/", "/path/to/directory/"] - } - } - -Plugin, View Template and Locale Paths --------------------------------------- - -Since plugins, view templates and locales are not classes, they cannot have an -autoloader configured. CakePHP provides three Configure variables to setup additional -paths for these resources. In your **config/app.php** you can set these variables:: - - return [ - // More configuration - 'App' => [ - 'paths' => [ - 'plugins' => [ - ROOT . DS . 'plugins' . DS, - '/path/to/other/plugins/', - ], - 'templates' => [ - ROOT . DS . 'templates' . DS, - ROOT . DS . 'templates2' . DS, - ], - 'locales' => [ - ROOT . DS . 'resources' . DS . 'locales' . DS, - ], - ], - ], - ]; - -Paths should end with a directory separator, or they will not work properly. - -Inflection Configuration -======================== - -See the :ref:`inflection-configuration` docs for more information. - -Configure Class -=============== - -.. php:namespace:: Cake\Core - -.. php:class:: Configure - -CakePHP's Configure class can be used to store and retrieve -application or runtime specific values. Be careful, this class -allows you to store anything in it, then use it in any other part -of your code: a sure temptation to break the MVC pattern CakePHP -was designed for. The main goal of Configure class is to keep -centralized variables that can be shared between many objects. -Remember to try to live by "convention over configuration" and you -won't end up breaking the MVC structure CakePHP provides. - -Writing Configuration data --------------------------- - -.. php:staticmethod:: write($key, $value) - -Use ``write()`` to store data in the application's configuration:: - - Configure::write('Company.name', 'Pizza, Inc.'); - Configure::write('Company.slogan', 'Pizza for your body and soul'); - -.. note:: - - The :term:`dot notation` used in the ``$key`` parameter can be used to - organize your configuration settings into logical groups. - -The above example could also be written in a single call:: - - Configure::write('Company', [ - 'name' => 'Pizza, Inc.', - 'slogan' => 'Pizza for your body and soul' - ]); - -You can use ``Configure::write('debug', $bool)`` to switch between debug and -production modes on the fly. - -.. note:: - - Any configuration changes done using ``Configure::write()`` are in memory - and will not persist across requests. - - -Reading Configuration Data --------------------------- - -.. php:staticmethod:: read($key = null, $default = null) - -Used to read configuration data from the application. If a key is supplied, the -data is returned. Using our examples from write() above, we can read that data -back:: - - // Returns 'Pizza Inc.' - Configure::read('Company.name'); - - // Returns 'Pizza for your body and soul' - Configure::read('Company.slogan'); - - Configure::read('Company'); - // Returns: - ['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul']; - - // Returns 'fallback' as Company.nope is undefined. - Configure::read('Company.nope', 'fallback'); - -If ``$key`` is left null, all values in Configure will be returned. - -.. php:staticmethod:: readOrFail($key) - -Reads configuration data just like :php:meth:`Cake\\Core\\Configure::read()` -but expects to find a key/value pair. In case the requested pair does not -exist, a :php:class:`RuntimeException` will be thrown:: - - Configure::readOrFail('Company.name'); // Yields: 'Pizza, Inc.' - Configure::readOrFail('Company.geolocation'); // Will throw an exception - - Configure::readOrFail('Company'); - - // Yields: - ['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul']; - -Checking to see if Configuration Data is Defined ------------------------------------------------- - -.. php:staticmethod:: check($key) - -Used to check if a key/path exists and has non-null value:: - - $exists = Configure::check('Company.name'); - -Deleting Configuration Data ---------------------------- - -.. php:staticmethod:: delete($key) - -Used to delete information from the application's configuration:: - - Configure::delete('Company.name'); - -Reading & Deleting Configuration Data -------------------------------------- - -.. php:staticmethod:: consume($key) - -Read and delete a key from Configure. This is useful when you want to -combine reading and deleting values in a single operation. - -.. php:staticmethod:: consumeOrFail($key) - -Consumes configuration data just like :php:meth:`Cake\\Core\\Configure::consume()` -but expects to find a key/value pair. In case the requested pair does not -exist, a :php:class:`RuntimeException` will be thrown:: - - Configure::consumeOrFail('Company.name'); // Yields: 'Pizza, Inc.' - Configure::consumeOrFail('Company.geolocation'); // Will throw an exception - - Configure::consumeOrFail('Company'); - - // Yields: - ['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul']; - -Reading and writing configuration files -======================================= - -.. php:staticmethod:: setConfig($name, $engine) - -CakePHP comes with two built-in configuration file engines. -:php:class:`Cake\\Core\\Configure\\Engine\\PhpConfig` is able to read PHP config -files, in the same format that Configure has historically read. -:php:class:`Cake\\Core\\Configure\\Engine\\IniConfig` is able to read ini config -files. See the `PHP documentation `_ for more -information on the specifics of ini files. To use a core config engine, you'll -need to attach it to Configure using :php:meth:`Configure::config()`:: - - use Cake\Core\Configure\Engine\PhpConfig; - - // Read config files from config - Configure::config('default', new PhpConfig()); - - // Read config files from another path. - Configure::config('default', new PhpConfig('/path/to/your/config/files/')); - -You can have multiple engines attached to Configure, each reading different -kinds or sources of configuration files. You can interact with attached engines -using a few other methods on Configure. To check which engine aliases are -attached you can use :php:meth:`Configure::configured()`:: - - // Get the array of aliases for attached engines. - Configure::configured(); - - // Check if a specific engine is attached - Configure::configured('default'); - -.. php:staticmethod:: drop($name) - -You can also remove attached engines. ``Configure::drop('default')`` -would remove the default engine alias. Any future attempts to load configuration -files with that engine would fail:: - - Configure::drop('default'); - -.. _loading-configuration-files: - -Loading Configuration Files ---------------------------- - -.. php:staticmethod:: load($key, $config = 'default', $merge = true) - -Once you've attached a config engine to Configure you can load configuration -files:: - - // Load my_file.php using the 'default' engine object. - Configure::load('my_file', 'default'); - -Loaded configuration files merge their data with the existing runtime -configuration in Configure. This allows you to overwrite and add new values into -the existing runtime configuration. By setting ``$merge`` to ``true``, values -will not ever overwrite the existing configuration. - -.. warning:: - When merging configuration files with `$merge = true`, dot notation in keys is - not expanded:: - - // config1.php - 'Key1' => [ - 'Key2' => [ - 'Key3' => ['NestedKey1' => 'Value'], - ], - ], - - // config2.php - 'Key1.Key2' => [ - 'Key3' => ['NestedKey2' => 'Value2'], - ] - - Configure::load('config1', 'default'); - Configure::load('config2', 'default', true); - - // Now Key1.Key2.Key3 has the value ['NestedKey2' => 'Value2'] - // instead of ['NestedKey1' => 'Value', 'NestedKey2' => 'Value2'] - -Creating or Modifying Configuration Files ------------------------------------------ - -.. php:staticmethod:: dump($key, $config = 'default', $keys = []) - -Dumps all or some of the data in Configure into a file or storage system -supported by a config engine. The serialization format is decided by the config -engine attached as $config. For example, if the 'default' engine is -a :php:class:`Cake\\Core\\Configure\\Engine\\PhpConfig`, the generated file will be -a PHP configuration file loadable by the -:php:class:`Cake\\Core\\Configure\\Engine\\PhpConfig` - -Given that the 'default' engine is an instance of PhpConfig. -Save all data in Configure to the file `my_config.php`:: - - Configure::dump('my_config', 'default'); - -Save only the error handling configuration:: - - Configure::dump('error', 'default', ['Error', 'Exception']); - -``Configure::dump()`` can be used to either modify or overwrite -configuration files that are readable with :php:meth:`Configure::load()` - -Storing Runtime Configuration ------------------------------ - -.. php:staticmethod:: store($name, $cacheConfig = 'default', $data = null) - -You can also store runtime configuration values for use in a future request. -Since configure only remembers values for the current request, you will -need to store any modified configuration information if you want to -use it in subsequent requests:: - - // Store the current configuration in the 'user_1234' key in the 'default' cache. - Configure::store('user_1234', 'default'); - -Stored configuration data is persisted in the named cache configuration. See the -:doc:`/core-libraries/caching` documentation for more information on caching. - -Restoring Runtime Configuration -------------------------------- - -.. php:staticmethod:: restore($name, $cacheConfig = 'default') - -Once you've stored runtime configuration, you'll probably need to restore it -so you can access it again. ``Configure::restore()`` does exactly that:: - - // Restore runtime configuration from the cache. - Configure::restore('user_1234', 'default'); - -When restoring configuration information it's important to restore it with -the same key, and cache configuration as was used to store it. Restored -information is merged on top of the existing runtime configuration. - -Configuration Engines ---------------------- - -CakePHP provides the ability to load configuration files from a number of -different sources, and features a pluggable system for `creating your own -configuration engines -`__. -The built in configuration engines are: - -* `JsonConfig `__ -* `IniConfig `__ -* `PhpConfig `__ - -By default your application will use ``PhpConfig``. - -.. meta:: - :title lang=en: Configuration - :keywords lang=en: finished configuration,legacy database,database configuration,value pairs,default connection,optional configuration,example database,php class,configuration database,default database,configuration steps,index database,configuration details,class database,host localhost,inflections,key value,database connection,piece of cake,basic web diff --git a/en/development/debugging.rst b/en/development/debugging.rst deleted file mode 100644 index 5a2161a52b..0000000000 --- a/en/development/debugging.rst +++ /dev/null @@ -1,233 +0,0 @@ -Debugging -######### - -Debugging is an inevitable and necessary part of any development -cycle. While CakePHP doesn't offer any tools that directly connect -with any IDE or editor, CakePHP does provide several tools to -assist in debugging and exposing what is running under the hood of -your application. - -Basic Debugging -=============== - -.. php:function:: debug(mixed $var, boolean $showHtml = null, $showFrom = true) - :noindex: - -The ``debug()`` function is a globally available function that works -similarly to the PHP function ``print_r()``. The ``debug()`` function -allows you to show the contents of a variable in a number of -different ways. First, if you'd like data to be shown in an -HTML-friendly way, set the second parameter to ``true``. The function -also prints out the line and file it is originating from by -default. - -Output from this function is only shown if the core ``$debug`` variable -has been set to ``true``. - -Also see ``dd()``, ``pr()`` and ``pj()``. - -.. php:function:: stackTrace() - -The ``stackTrace()`` function is available globally, and allows you to output -a stack trace wherever the function is called. - -.. php:function:: breakpoint() - -If you have `Psysh `_ installed you can use this -function in CLI environments to open an interactive console with the current -local scope:: - - // Some code - eval(breakpoint()); - -Will open an interactive console that can be used to check local variables -and execute other code. You can exit the interactive debugger and resume the -original execution by running ``quit`` or ``q`` in the interactive session. - -Using the Debugger Class -======================== - -.. php:namespace:: Cake\Error - -.. php:class:: Debugger - - -To use the debugger, first ensure that ``Configure::read('debug')`` is set to -``true``. You can use ``filter_var(env('DEBUG', true), -FILTER_VALIDATE_BOOLEAN),`` in **config/app.php** file to ensure that ``debug`` -is a boolean. - -The following configuration options can be set in **config/app.php** to change how -``Debugger`` behaves: - -- ``Debugger.editor`` Choose the which editor URL format you want to use. - By default atom, emacs, macvim, phpstorm, sublime, textmate, and vscode are - available. You can add additional editor link formats using - :php:meth:`Debugger::addEditor()` during your application bootstrap. -- ``Debugger.outputMask`` A mapping of ``key`` to ``replacement`` values that - ``Debugger`` should replace in dumped data and logs generated by ``Debugger``. - -Outputting Values -================= - -.. php:staticmethod:: dump($var, $depth = 3) - -Dump prints out the contents of a variable. It will print out all -properties and methods (if any) of the supplied variable:: - - $foo = [1,2,3]; - - Debugger::dump($foo); - - // Outputs - array( - 1, - 2, - 3 - ) - - // Simple object - $car = new Car(); - - Debugger::dump($car); - - // Outputs - object(Car) { - color => 'red' - make => 'Toyota' - model => 'Camry' - mileage => (int)15000 - } - -Masking Data ------------- - -When dumping data with ``Debugger`` or rendering error pages, you may want to -hide sensitive keys like passwords or API keys. In your **config/bootstrap.php** -you can mask specific keys:: - - Debugger::setOutputMask([ - 'password' => 'xxxxx', - 'awsKey' => 'yyyyy', - ]); - -As of 4.1.0 you can use the ``Debugger.outputMask`` configuration value to set -output masks. - -Logging With Stack Traces -========================= - -.. php:staticmethod:: log($var, $level = 7, $depth = 3) - -Creates a detailed stack trace log at the time of invocation. The -``log()`` method prints out data similar to that done by -``Debugger::dump()``, but to the debug.log instead of the output -buffer. Note your **tmp** directory (and its contents) must be -writable by the web server for ``log()`` to work correctly. - -Generating Stack Traces -======================= - -.. php:staticmethod:: trace($options) - -Returns the current stack trace. Each line of the trace includes -the calling method, including which file and line the call -originated from:: - - // In PostsController::index() - pr(Debugger::trace()); - - // Outputs - PostsController::index() - APP/Controller/DownloadsController.php, line 48 - Dispatcher::_invoke() - CORE/src/Routing/Dispatcher.php, line 265 - Dispatcher::dispatch() - CORE/src/Routing/Dispatcher.php, line 237 - [main] - APP/webroot/index.php, line 84 - -Above is the stack trace generated by calling ``Debugger::trace()`` in -a controller action. Reading the stack trace bottom to top shows -the order of currently running functions (stack frames). - -Getting an Excerpt From a File -============================== - -.. php:staticmethod:: excerpt($file, $line, $context) - -Grab an excerpt from the file at $path (which is an absolute -filepath), highlights line number $line with $context number of -lines around it. :: - - pr(Debugger::excerpt(ROOT . DS . LIBS . 'debugger.php', 321, 2)); - - // Will output the following. - Array - ( - [0] => * @access public - [1] => */ - [2] => function excerpt($file, $line, $context = 2) { - - [3] => $data = $lines = array(); - [4] => $data = @explode("\n", file_get_contents($file)); - ) - -Although this method is used internally, it can be handy if you're -creating your own error messages or log entries for custom -situations. - -.. php:staticmethod:: Debugger::getType($var) - -Get the type of a variable. Objects will return their class name - -Editor Integration -================== - -Exception and error pages can contain URLs that directly open in your editor or -IDE. CakePHP ships with URL formats for several popular editors, and you can add -additional editor formats if required during application bootstrap:: - - // Generate links for vscode. - Debugger::setEditor('vscode') - - // Add a custom format - // Format strings will have the {file} and {line} - // placeholders replaced. - Debugger::addEditor('custom', 'thing://open={file}&line={line}'); - - // You can also use a closure to generate URLs - Debugger::addEditor('custom', function ($file, $line) { - return "thing://open={$file}&line={$line}"; - }); - -Using Logging to Debug -====================== - -Logging messages is another good way to debug applications, and you can use -:php:class:`Cake\\Log\\Log` to do logging in your application. All objects that -use ``LogTrait`` have an instance method ``log()`` which can be used -to log messages:: - - $this->log('Got here', 'debug'); - -The above would write ``Got here`` into the debug log. You can use log entries -to help debug methods that involve redirects or complicated loops. You can also -use :php:meth:`Cake\\Log\\Log::write()` to write log messages. This method can be called -statically anywhere in your application one Log has been loaded:: - - // At the top of the file you want to log in. - use Cake\Log\Log; - - // Anywhere that Log has been imported. - Log::debug('Got here'); - -Debug Kit -========= - -DebugKit is a plugin that provides a number of good debugging tools. It -primarily provides a toolbar in the rendered HTML, that provides a plethora of -information about your application and the current request. See the `DebugKit -Documentation `__ for how to install and use -DebugKit. - -.. meta:: - :title lang=en: Debugging - :description lang=en: Debugging CakePHP with the Debugger class, logging, basic debugging and using the DebugKit plugin. - :keywords lang=en: code excerpt,stack trace,default output,error link,default error,web requests,error report,debugger,arrays,different ways,excerpt from,cakephp,ide,options diff --git a/en/development/dependency-injection.rst b/en/development/dependency-injection.rst deleted file mode 100644 index 1fcf43615b..0000000000 --- a/en/development/dependency-injection.rst +++ /dev/null @@ -1,352 +0,0 @@ -Dependency Injection -#################### - -The CakePHP service container enables you to manage class dependencies for your -application services through dependency injection. Dependency injection -automatically "injects" an object's dependencies via the constructor without -having to manually instantiate them. - -You can use the service container to define 'application services'. These -classes can use models and interact with other objects like loggers and mailers -to build re-usable workflows and business logic for your application. - -CakePHP will use the :term:`DI container` in the following situations: - -* Constructing controllers. -* Calling actions on your controllers. -* Constructing Components. -* Constructing Console Commands. -* Constructing Middleware by classname. - -Controller Example -================== - -:: - - // In src/Controller/UsersController.php - class UsersController extends AppController - { - // The $users service will be created via the service container. - public function ssoCallback(UsersService $users) - { - if ($this->request->is('post')) { - // Use the UsersService to create/get the user from a - // Single Signon Provider. - $user = $users->ensureExists($this->request->getData()); - } - } - } - - // In src/Application.php - public function services(ContainerInterface $container): void - { - $container->add(UsersService::class); - } - -In this example, the ``UsersController::ssoCallback()`` action needs to fetch -a user from a Single-Sign-On provider and ensure it exists in the local -database. Because this service is injected into our controller, we can easily -swap the implementation out with a mock object or a dummy sub-class when -testing. - -Command Example -=============== - -:: - - // In src/Command/CheckUsersCommand.php - use Cake\Console\CommandFactoryInterface; - - class CheckUsersCommand extends Command - { - public function __construct(protected UsersService $users, ?CommandFactoryInterface $factory = null) - { - parent::__construct($factory); - } - - public function execute(Arguments $args, ConsoleIo $io) - { - $valid = $this->users->check('all'); - } - - } - - // In src/Application.php - public function services(ContainerInterface $container): void - { - $container - ->add(CheckUsersCommand::class) - ->addArgument(UsersService::class) - ->addArgument(CommandFactoryInterface::class); - $container->add(UsersService::class); - } - -The injection process is a bit different here. Instead of adding the -``UsersService`` to the container we first have to add the Command as -a whole to the Container and add the ``UsersService`` as an argument. -With that you can then access that service inside the constructor -of the command. - -Component Example -================= - -:: - - // In src/Controller/Component/SearchComponent.php - class SearchComponent extends Component - { - public function __construct( - ComponentRegistry $registry, - private UserService $users, - array $config = [] - ) { - parent::__construct($registry, $config); - } - - public function something() - { - $valid = $this->users->check('all'); - } - } - - // In src/Application.php - public function services(ContainerInterface $container): void - { - $container->add(SearchComponent::class) - ->addArgument(ComponentRegistry::class) - ->addArgument(UsersService::class); - $container->add(UsersService::class); - } - -Adding Services -=============== - -In order to have services created by the container, you need to tell it which -classes it can create and how to build those classes. The -simplest definition is via a class name:: - - // Add a class by its name. - $container->add(BillingService::class); - -Your application and plugins define the services they have in the -``services()`` hook method:: - - // in src/Application.php - namespace App; - - use App\Service\BillingService; - use Cake\Core\ContainerInterface; - use Cake\Http\BaseApplication; - - class Application extends BaseApplication - { - public function services(ContainerInterface $container): void - { - $container->add(BillingService::class); - } - } - -You can define implementations for interfaces that your application uses:: - - use App\Service\AuditLogServiceInterface; - use App\Service\AuditLogService; - - // in your Application::services() method. - - // Add an implementation for an interface. - $container->add(AuditLogServiceInterface::class, AuditLogService::class); - -The container can leverage factory functions to create objects if necessary:: - - $container->add(AuditLogServiceInterface::class, function (...$args) { - return new AuditLogService(...$args); - }); - -Factory functions will receive all of the resolved dependencies for the class -as arguments. - -Once you've defined a class, you also need to define the dependencies it -requires. Those dependencies can be either objects or primitive values:: - - // Add a primitive value like a string, array or number. - $container->add('apiKey', 'abc123'); - - $container->add(BillingService::class) - ->addArgument('apiKey'); - -Your services can depend on ``ServerRequest`` in controller actions as it will -be added automatically. - -Adding Shared Services ----------------------- - -By default services are not shared. Every object (and dependencies) is created -each time it is fetched from the container. If you want to re-use a single -instance, often referred to as a singleton, you can mark a service as 'shared':: - - // in your Application::services() method. - - $container->addShared(BillingService::class); - -Extending Definitions ---------------------- - -Once a service is defined you can modify or update the service definition by -extending them. This allows you to add additional arguments to services defined -elsewhere:: - - // Add an argument to a partially defined service elsewhere. - $container->extend(BillingService::class) - ->addArgument('logLevel'); - -Tagging Services ----------------- - -By tagging services you can get all of those services resolved at the same -time. This can be used to build services that combine collections of other -services like in a reporting system:: - - $container->add(BillingReport::class)->addTag('reports'); - $container->add(UsageReport::class)->addTag('reports'); - - $container->add(ReportAggregate::class, function () use ($container) { - return new ReportAggregate($container->get('reports')); - }); - -Using Configuration Data ------------------------- - -Often you'll need configuration data in your services. While you could add -all the configuration keys your service needs into the container, that can be -tedious. To make configuration easier to work with CakePHP includes an -injectable configuration reader:: - - use Cake\Core\ServiceConfig; - - // Use a shared instance - $container->addShared(ServiceConfig::class); - -The ``ServiceConfig`` class provides a read-only view of all the data available -in ``Configure`` so you don't have to worry about accidentally changing -configuration. - -Service Providers -================= - -Service providers allow you to group related services together helping you -organize your services. Service providers can help increase your application's -performance as defined services are lazily registered after -their first use. - -Creating Service Providers --------------------------- - -An example ServiceProvider would look like:: - - namespace App\ServiceProvider; - - use Cake\Core\ContainerInterface; - use Cake\Core\ServiceProvider; - // Other imports here. - - class BillingServiceProvider extends ServiceProvider - { - protected $provides = [ - StripeService::class, - 'configKey', - ]; - - public function services(ContainerInterface $container): void - { - $container->add(StripeService::class); - $container->add('configKey', 'some value'); - } - } - -Service providers use their ``services()`` method to define all the services they -will provide. Additionally those services **must be** defined in the ``$provides`` -property. Failing to include a service in the ``$provides`` property will result -in it not be loadable from the container. - -Using Service Providers ------------------------ - -To load a service provider add it into the container using the -``addServiceProvider()`` method:: - - // in your Application::services() method. - $container->addServiceProvider(new BillingServiceProvider()); - -Bootable ServiceProviders -------------------------- - -If your service provider needs to run logic when it is added to the container, -you can implement the ``bootstrap()`` method. This situation can come up when your -service provider needs to load additional configuration files, load additional -service providers or modify a service defined elsewhere in your application. An -example of a bootable service would be:: - - namespace App\ServiceProvider; - - use Cake\Core\ServiceProvider; - // Other imports here. - - class BillingServiceProvider extends ServiceProvider - { - protected $provides = [ - StripeService::class, - 'configKey', - ]; - - public function bootstrap($container) - { - $container->addServiceProvider(new InvoicingServiceProvider()); - } - } - - -.. _mocking-services-in-tests: - -Mocking Services in Tests -========================= - -In tests that use ``ConsoleIntegrationTestTrait`` or ``IntegrationTestTrait`` -you can replace services that are injected via the container with mocks or -stubs:: - - // In a test method or setup(). - $this->mockService(StripeService::class, function () { - return new FakeStripe(); - }); - - // If you need to remove a mock - $this->removeMockService(StripeService::class); - -Any defined mocks will be replaced in your application's container during -testing, and automatically injected into your controllers and commands. Mocks -are cleaned up at the end of each test. - -Auto Wiring -=============== - -Auto Wiring is turned off by default. To enable it:: - - // In src/Application.php - public function services(ContainerInterface $container): void - { - $container->delegate( - new \League\Container\ReflectionContainer() - ); - } - -While your dependencies will now be resolved automatically, this approach will -not cache resolutions which can be detrimental to performance. To enable -caching:: - - $container->delegate( - // or consider using the value of Configure::read('debug') - new \League\Container\ReflectionContainer(true) - ); - -Read more about auto wiring in the `PHP League Container documentation -`_. diff --git a/en/development/errors.rst b/en/development/errors.rst deleted file mode 100644 index 68dbc7ec07..0000000000 --- a/en/development/errors.rst +++ /dev/null @@ -1,741 +0,0 @@ -Error & Exception Handling -########################## - -CakePHP applications come with error and exception handling setup for you. PHP -errors are trapped and displayed or logged. Uncaught exceptions are rendered -into error pages automatically. - -.. _error-configuration: - -Configuration -============= - -Error configuration is done in your application's **config/app.php** file. By -default CakePHP uses ``Cake\Error\ErrorTrap`` and ``Cake\Error\ExceptionTrap`` -to handle both PHP errors and exceptions respectively. The error configuration -allows you to customize error handling for your application. The following -options are supported: - -* ``errorLevel`` - int - The level of errors you are interested in capturing. - Use the built-in PHP error constants, and bitmasks to select the level of - error you are interested in. See :ref:`deprecation-warnings` to disable - deprecation warnings. -* ``trace`` - bool - Include stack traces for errors in log files. Stack - traces will be included in the log after each error. This is helpful for - finding where/when errors are being raised. -* ``exceptionRenderer`` - string - The class responsible for rendering uncaught - exceptions. If you choose a custom class you should place the file for that - class in **src/Error**. This class needs to implement a ``render()`` method. -* ``log`` - bool - When ``true``, exceptions + their stack traces will be - logged to :php:class:`Cake\\Log\\Log`. -* ``skipLog`` - array - An array of exception classnames that should not be - logged. This is useful to remove NotFoundExceptions or other common, but - uninteresting log messages. -* ``extraFatalErrorMemory`` - int - Set to the number of megabytes to increase - the memory limit by when a fatal error is encountered. This allows breathing - room to complete logging or error handling. -* ``logger`` (prior to 4.4.0 use ``errorLogger``) - - ``Cake\Error\ErrorLoggerInterface`` - The class responsible for logging - errors and unhandled exceptions. Defaults to ``Cake\Error\ErrorLogger``. -* ``errorRenderer`` - ``Cake\Error\ErrorRendererInterface`` - The class responsible - for rendering errors. Default is chosen based on PHP SAPI. -* ``ignoredDeprecationPaths`` - array - A list of glob compatible paths that - deprecation errors should be ignored in. Added in 4.2.0 - -By default, PHP errors are displayed when ``debug`` is ``true``, and logged -when debug is ``false``. The fatal error handler will be called independent -of ``debug`` level or ``errorLevel`` configuration, but the result will be -different based on ``debug`` level. The default behavior for fatal errors is -show a page to internal server error (``debug`` disabled) or a page with the -message, file and line (``debug`` enabled). - -.. note:: - - If you use a custom error handler, the supported options will - depend on your handler. - - -.. _deprecation-warnings: - -Deprecation Warnings -==================== - -CakePHP uses deprecation warnings to indicate when features have been -deprecated. We also recommend this system for use in your plugins and -application code when useful. You can trigger deprecation warnings with -``deprecationWarning()``:: - - deprecationWarning('5.0', 'The example() method is deprecated. Use getExample() instead.'); - -When upgrading CakePHP or plugins you may encounter new deprecation warnings. -You can temporarily disable deprecation warnings in one of a few ways: - -#. Using the ``Error.errorLevel`` setting to ``E_ALL ^ E_USER_DEPRECATED`` to - ignore *all* deprecation warnings. -#. Using the ``Error.ignoredDeprecationPaths`` configuration option to ignore - deprecations with glob compatible expressions. For example:: - - 'Error' => [ - 'ignoredDeprecationPaths' => [ - 'vendors/company/contacts/*', - 'src/Models/*', - ], - ], - - Would ignore all deprecations from your ``Models`` directory and the - ``Contacts`` plugin in your application. - -Changing Exception Handling -=========================== - -Exception handling in CakePHP offers several ways to tailor how exceptions are -handled. Each approach gives you different amounts of control over the -exception handling process. - -#. *Listen to events* This allows you to be notified through CakePHP events when - errors and exceptions have been handled. -#. *Custom templates* This allows you to change the rendered view - templates as you would any other template in your application. -#. *Custom Controller* This allows you to control how exception - pages are rendered. -#. *Custom ExceptionRenderer* This allows you to control how exception - pages and logging are performed. -#. *Create & register your own traps* This gives you complete - control over how errors & exceptions are handled, logged and rendered. Use - ``Cake\Error\ExceptionTrap`` and ``Cake\Error\ErrorTrap`` as reference when - implementing your traps. - -Listen to Events -================ - -The ``ErrorTrap`` and ``ExceptionTrap`` handlers will trigger CakePHP events -when they handle errors. You can listen to the ``Error.beforeRender`` event to be -notified of PHP errors. The ``Exception.beforeRender`` event is dispatched when an -exception is handled:: - - $errorTrap = new ErrorTrap(Configure::read('Error')); - $errorTrap->getEventManager()->on( - 'Error.beforeRender', - function (EventInterface $event, PhpError $error) { - // do your thing - } - ); - -Within an ``Error.beforeRender`` handler you have a few options: - -* Stop the event to prevent rendering. -* Return a string to skip rendering and use the provided string instead - -Within an ``Exception.beforeRender`` handler you have a few options: - -* Stop the event to prevent rendering. -* Set the ``exception`` data attribute with ``setData('exception', $err)`` - to replace the exception that is being rendered. -* Return a response from the event listener to skip rendering and use - the provided response instead. - -.. _error-views: - -Custom Templates -================ - -The default exception trap renders all uncaught exceptions your application -raises with the help of ``Cake\Error\Renderer\WebExceptionRenderer``, and your application's -``ErrorController``. - -The error page views are located at **templates/Error/**. All 4xx errors use -the **error400.php** template, and 5xx errors use the **error500.php**. Your -error templates will have the following variables available: - -* ``message`` The exception message. -* ``code`` The exception code. -* ``url`` The request URL. -* ``error`` The exception object. - -In debug mode if your error extends ``Cake\Core\Exception\CakeException`` the -data returned by ``getAttributes()`` will be exposed as view variables as well. - -.. note:: - You will need to set ``debug`` to false, to see your **error404** and - **error500** templates. In debug mode, you'll see CakePHP's development - error page. - -Custom Error Page Layout ------------------------- - -By default error templates use **templates/layout/error.php** for a layout. -You can use the ``layout`` property to pick a different layout:: - - // inside templates/Error/error400.php - $this->layout = 'my_error'; - -The above would use **templates/layout/my_error.php** as the layout for your -error pages. - -Many exceptions raised by CakePHP will render specific view templates in debug -mode. With debug turned off all exceptions raised by CakePHP will use either -**error400.php** or **error500.php** based on their status code. - -Custom Controller -================= - -The ``App\Controller\ErrorController`` class is used by CakePHP's exception -rendering to render the error page view and receives all the standard request -life-cycle events. By modifying this class you can control which components are -used and which templates are rendered. - -If your application uses :ref:`prefix-routing` you can create custom error -controllers for each routing prefix. For example, if you had an ``Admin`` -prefix. You could create the following class:: - - namespace App\Controller\Admin; - - use App\Controller\AppController; - use Cake\Event\EventInterface; - - class ErrorController extends AppController - { - /** - * beforeRender callback. - * - * @param \Cake\Event\EventInterface $event Event. - * @return void - */ - public function beforeRender(EventInterface $event) - { - $this->viewBuilder()->setTemplatePath('Error'); - } - } - -This controller would only be used when an error is encountered in a prefixed -controller, and allows you to define prefix specific logic/templates as needed. - -Exception specific logic ------------------------- - -Within your controller you can define public methods to handle custom -application errors. For example a ``MissingWidgetException`` would be handled by -a ``missingWidget()`` controller method, and CakePHP would use -``templates/Error/missing_widget.php`` as the template. For example:: - - namespace App\Controller\Admin; - - use App\Controller\AppController; - use Cake\Event\EventInterface; - - class ErrorController extends AppController - { - protected function missingWidget(MissingWidgetException $error) - { - // You can prepare additional template context or trap errors. - } - } - -.. versionadded:: 5.2.0 - Exception specific controller methods and templates were added. - -.. _custom-exceptionrenderer: - -Custom ExceptionRenderer -======================== - -If you want to control the entire exception rendering and logging process you -can use the ``Error.exceptionRenderer`` option in **config/app.php** to choose -a class that will render exception pages. Changing the ExceptionRenderer is -useful when you want to change the logic used to create an error controller, -choose the template, or control the overall rendering process. - -Your custom exception renderer class should be placed in **src/Error**. Let's -assume our application uses ``App\Exception\MissingWidgetException`` to indicate -a missing widget. We could create an exception renderer that renders specific -error pages when this error is handled:: - - // In src/Error/AppExceptionRenderer.php - namespace App\Error; - - use Cake\Error\Renderer\WebExceptionRenderer; - - class AppExceptionRenderer extends WebExceptionRenderer - { - public function missingWidget($error) - { - $response = $this->controller->getResponse(); - - return $response->withStringBody('Oops that widget is missing.'); - } - } - - // In config/app.php - 'Error' => [ - 'exceptionRenderer' => 'App\Error\AppExceptionRenderer', - // ... - ], - // ... - -The above would handle our ``MissingWidgetException``, -and allow us to provide custom display/handling logic for those application -exceptions. - -Exception rendering methods receive the handled exception as an argument, and -should return a ``Response`` object. You can also implement methods to add -additional logic when handling CakePHP errors:: - - // In src/Error/AppExceptionRenderer.php - namespace App\Error; - - use Cake\Error\Renderer\WebExceptionRenderer; - - class AppExceptionRenderer extends WebExceptionRenderer - { - public function notFound($error) - { - // Do something with NotFoundException objects. - } - } - -Changing the ErrorController Class ----------------------------------- - -The exception renderer dictates which controller is used for exception -rendering. If you want to change which controller is used to render exceptions, -override the ``_getController()`` method in your exception renderer:: - - // in src/Error/AppExceptionRenderer - namespace App\Error; - - use App\Controller\SuperCustomErrorController; - use Cake\Controller\Controller; - use Cake\Error\Renderer\WebExceptionRenderer; - - class AppExceptionRenderer extends WebExceptionRenderer - { - protected function _getController(): Controller - { - return new SuperCustomErrorController(); - } - } - - // in config/app.php - 'Error' => [ - 'exceptionRenderer' => 'App\Error\AppExceptionRenderer', - // ... - ], - // ... - - -.. index:: application exceptions - -Creating your own Application Exceptions -======================================== - -You can create your own application exceptions using any of the built in `SPL -exceptions `_, ``Exception`` -itself, or :php:exc:`Cake\\Core\\Exception\\Exception`. -If your application contained the following exception:: - - use Cake\Core\Exception\CakeException; - - class MissingWidgetException extends CakeException - { - } - -You could provide nice development errors, by creating -**templates/Error/missing_widget.php**. When in production mode, the above -error would be treated as a 500 error and use the **error500** template. - -Exceptions that subclass ``Cake\Http\Exception\HttpException``, will have their -error code used as an HTTP status code if the error code is between ``400`` and -``506``. - -The constructor for :php:exc:`Cake\\Core\\Exception\\CakeException` allows you to -pass in additional data. This additional data is interpolated into the the -``_messageTemplate``. This allows you to create data rich exceptions, that -provide more context around your errors:: - - use Cake\Core\Exception\CakeException; - - class MissingWidgetException extends CakeException - { - // Context data is interpolated into this format string. - protected $_messageTemplate = 'Seems that %s is missing.'; - - // You can set a default exception code as well. - protected $_defaultCode = 404; - } - - throw new MissingWidgetException(['widget' => 'Pointy']); - -When rendered, this your view template would have a ``$widget`` variable set. If -you cast the exception as a string or use its ``getMessage()`` method you will -get ``Seems that Pointy is missing.``. - -.. note:: - - Prior to CakePHP 4.2.0 use class ``Cake\Core\Exception\Exception`` instead - of ``Cake\Core\Exception\CakeException`` - -Logging Exceptions ------------------- - -Using the built-in exception handling, you can log all the exceptions that are -dealt with by ErrorTrap by setting the ``log`` option to ``true`` in your -**config/app.php**. Enabling this will log every exception to -:php:class:`Cake\\Log\\Log` and the configured loggers. - -.. note:: - - If you are using a custom exception handler this setting will have - no effect. Unless you reference it inside your implementation. - - -.. php:namespace:: Cake\Http\Exception - -.. _built-in-exceptions: - -Built in Exceptions for CakePHP -=============================== - -HTTP Exceptions ---------------- - -There are several built-in exceptions inside CakePHP, outside of the -internal framework exceptions, there are several -exceptions for HTTP methods - -.. php:exception:: BadRequestException - :nocontentsentry: - - Used for doing 400 Bad Request error. - -.. php:exception:: UnauthorizedException - :nocontentsentry: - - Used for doing a 401 Unauthorized error. - -.. php:exception:: ForbiddenException - :nocontentsentry: - - Used for doing a 403 Forbidden error. - -.. php:exception:: InvalidCsrfTokenException - :nocontentsentry: - - Used for doing a 403 error caused by an invalid CSRF token. - -.. php:exception:: NotFoundException - :nocontentsentry: - - Used for doing a 404 Not found error. - -.. php:exception:: MethodNotAllowedException - :nocontentsentry: - - Used for doing a 405 Method Not Allowed error. - -.. php:exception:: NotAcceptableException - :nocontentsentry: - - Used for doing a 406 Not Acceptable error. - -.. php:exception:: ConflictException - :nocontentsentry: - - Used for doing a 409 Conflict error. - -.. php:exception:: GoneException - :nocontentsentry: - - Used for doing a 410 Gone error. - -For more details on HTTP 4xx error status codes see :rfc:`2616#section-10.4`. - -.. php:exception:: InternalErrorException - :nocontentsentry: - - Used for doing a 500 Internal Server Error. - -.. php:exception:: NotImplementedException - :nocontentsentry: - - Used for doing a 501 Not Implemented Errors. - -.. php:exception:: ServiceUnavailableException - :nocontentsentry: - - Used for doing a 503 Service Unavailable error. - -For more details on HTTP 5xx error status codes see :rfc:`2616#section-10.5`. - -You can throw these exceptions from your controllers to indicate failure states, -or HTTP errors. An example use of the HTTP exceptions could be rendering 404 -pages for items that have not been found:: - - use Cake\Http\Exception\NotFoundException; - - public function view($id = null) - { - $article = $this->Articles->findById($id)->first(); - if (empty($article)) { - throw new NotFoundException(__('Article not found')); - } - $this->set('article', $article); - $this->viewBuilder()->setOption('serialize', ['article']); - } - -By using exceptions for HTTP errors, you can keep your code both clean, and give -RESTful responses to client applications and users. - -Using HTTP Exceptions in your Controllers ------------------------------------------ - -You can throw any of the HTTP related exceptions from your controller actions -to indicate failure states. For example:: - - use Cake\Network\Exception\NotFoundException; - - public function view($id = null) - { - $article = $this->Articles->findById($id)->first(); - if (empty($article)) { - throw new NotFoundException(__('Article not found')); - } - $this->set('article', 'article'); - $this->viewBuilder()->setOption('serialize', ['article']); - } - -The above would cause the configured exception handler to catch and -process the :php:exc:`NotFoundException`. By default this will create an error -page, and log the exception. - -Other Built In Exceptions -------------------------- - -In addition, CakePHP uses the following exceptions: - -.. php:namespace:: Cake\View\Exception - -.. php:exception:: MissingViewException - :nocontentsentry: - - The chosen view class could not be found. - -.. php:exception:: MissingTemplateException - :nocontentsentry: - - The chosen template file could not be found. - -.. php:exception:: MissingLayoutException - :nocontentsentry: - - The chosen layout could not be found. - -.. php:exception:: MissingHelperException - :nocontentsentry: - - The chosen helper could not be found. - -.. php:exception:: MissingElementException - :nocontentsentry: - - The chosen element file could not be found. - -.. php:exception:: MissingCellException - :nocontentsentry: - - The chosen cell class could not be found. - -.. php:exception:: MissingCellViewException - :nocontentsentry: - - The chosen cell view file could not be found. - -.. php:namespace:: Cake\Controller\Exception - -.. php:exception:: MissingComponentException - :nocontentsentry: - - A configured component could not be found. - -.. php:exception:: MissingActionException - :nocontentsentry: - - The requested controller action could not be found. - -.. php:exception:: PrivateActionException - :nocontentsentry: - - Accessing private/protected/_ prefixed actions. - -.. php:namespace:: Cake\Console\Exception - -.. php:exception:: ConsoleException - :nocontentsentry: - - A console library class encounter an error. - -.. php:namespace:: Cake\Database\Exception - -.. php:exception:: MissingConnectionException - :nocontentsentry: - - A model's connection is missing. - -.. php:exception:: MissingDriverException - :nocontentsentry: - - A database driver could not be found. - -.. php:exception:: MissingExtensionException - :nocontentsentry: - - A PHP extension is missing for the database driver. - -.. php:namespace:: Cake\ORM\Exception - -.. php:exception:: MissingTableException - :nocontentsentry: - - A model's table could not be found. - -.. php:exception:: MissingEntityException - :nocontentsentry: - - A model's entity could not be found. - -.. php:exception:: MissingBehaviorException - :nocontentsentry: - - A model's behavior could not be found. - -.. php:exception:: PersistenceFailedException - :nocontentsentry: - - An entity couldn't be saved/deleted while using :php:meth:`Cake\\ORM\\Table::saveOrFail()` or - :php:meth:`Cake\\ORM\\Table::deleteOrFail()`. - -.. php:namespace:: Cake\Datasource\Exception - -.. php:exception:: RecordNotFoundException - :nocontentsentry: - - The requested record could not be found. This will also set HTTP response - headers to 404. - -.. php:namespace:: Cake\Routing\Exception - -.. php:exception:: MissingControllerException - :nocontentsentry: - - The requested controller could not be found. - -.. php:exception:: MissingRouteException - :nocontentsentry: - - The requested URL cannot be reverse routed or cannot be parsed. - -.. php:namespace:: Cake\Core\Exception - -.. php:exception:: Exception - :nocontentsentry: - - Base exception class in CakePHP. All framework layer exceptions thrown by - CakePHP will extend this class. - -These exception classes all extend :php:exc:`Exception`. -By extending Exception, you can create your own 'framework' errors. - -.. php:method:: responseHeader($header = null, $value = null) - :nocontentsentry: - - See :php:func:`Cake\\Network\\Request::header()` - -All Http and Cake exceptions extend the Exception class, which has a method -to add headers to the response. For instance when throwing a 405 -MethodNotAllowedException the rfc2616 says:: - - "The response MUST include an Allow header containing a list of valid - methods for the requested resource." - - -Customizing PHP Error Handling -============================== - -By default PHP errors are rendered to console or HTML output, and also logged. -If necessary, you can swap out CakePHP's error handling logic with your own. - -Custom Error Logging --------------------- - -Error handlers use instances of ``Cake\Error\ErrorLoggingInterface`` to create -log messages and log them to the appropriate place. You can replace the error -logger using the ``Error.errorLogger`` configure value. An example error -logger:: - - namespace App\Error; - - use Cake\Error\ErrorLoggerInterface; - use Cake\Error\PhpError; - use Psr\Http\Message\ServerRequestInterface; - use Throwable; - - /** - * Log errors and unhandled exceptions to `Cake\Log\Log` - */ - class ErrorLogger implements ErrorLoggerInterface - { - /** - * @inheritDoc - */ - public function logError( - PhpError $error, - ?ServerRequestInterface $request, - bool $includeTrace = false - ): void { - // Log PHP Errors - } - - /** - * @inheritDoc - */ - public function logException( - ?ServerRequestInterface $request, - bool $includeTrace = false - ): void { - // Log exceptions. - } - } - -Custom Error Rendering ----------------------- - -CakePHP includes error renderers for both web and console environments. If -however, you would like to replace the logic that renders errors you can create -a class:: - - // src/Error/CustomErrorRenderer.php - namespace App\Error; - - use Cake\Error\ErrorRendererInterface; - use Cake\Error\PhpError; - - class CustomErrorRenderer implements ErrorRendererInterface - { - public function write(string $out): void - { - // output the rendered error to the appropriate output stream - } - - public function render(PhpError $error, bool $debug): string - { - // Convert the error into the output string. - } - } - -The constructor of your renderer will be passed an array of all the Error -configuration. You connect your custom error renderer to CakePHP via the -``Error.errorRenderer`` config value. When replacing error handling you will -need to account for both web and command line environments. - -.. meta:: - :title lang=en: Error & Exception Handling - :keywords lang=en: stack traces,error constants,error array,default displays,anonymous functions,error handlers,default error,error level,exception handler,php error,error handler,write error,core classes,exception handling,configuration error,application code,callback,custom error,exceptions,bitmasks,fatal error, http status codes diff --git a/en/development/rest.rst b/en/development/rest.rst deleted file mode 100644 index 0d7ea46662..0000000000 --- a/en/development/rest.rst +++ /dev/null @@ -1,151 +0,0 @@ -REST -#### - -REST is a foundational concept to the open web. CakePHP provides functionality -to build applications that expose REST APIs with low complexity abstractions and -interfaces. - -CakePHP provides methods for exposing your controller actions via HTTP methods, -and serializing view variables based on content-type negotiation. Content-Type -negotiation allows clients of your application to send requests with serialize -data and receive responses with serialized data via the ``Accept`` and -``Content-Type`` headers, or URL extensions. - -Getting Started -=============== - -To get started with adding a REST API to your application, we'll first need -a controller containing actions that we want to expose as an API. A basic -controller might look something like this:: - - // src/Controller/RecipesController.php - use Cake\View\JsonView; - - class RecipesController extends AppController - { - public function viewClasses(): array - { - return [JsonView::class]; - } - - public function index() - { - $recipes = $this->Recipes->find('all')->all(); - $this->set('recipes', $recipes); - $this->viewBuilder()->setOption('serialize', ['recipes']); - } - - public function view($id) - { - $recipe = $this->Recipes->get($id); - $this->set('recipe', $recipe); - $this->viewBuilder()->setOption('serialize', ['recipe']); - } - - public function add() - { - $this->request->allowMethod(['post', 'put']); - $recipe = $this->Recipes->newEntity($this->request->getData()); - if ($this->Recipes->save($recipe)) { - $message = 'Saved'; - } else { - $message = 'Error'; - } - $this->set([ - 'message' => $message, - 'recipe' => $recipe, - ]); - $this->viewBuilder()->setOption('serialize', ['recipe', 'message']); - } - - public function edit($id) - { - $this->request->allowMethod(['patch', 'post', 'put']); - $recipe = $this->Recipes->get($id); - $recipe = $this->Recipes->patchEntity($recipe, $this->request->getData()); - if ($this->Recipes->save($recipe)) { - $message = 'Saved'; - } else { - $message = 'Error'; - } - $this->set([ - 'message' => $message, - 'recipe' => $recipe, - ]); - $this->viewBuilder()->setOption('serialize', ['recipe', 'message']); - } - - public function delete($id) - { - $this->request->allowMethod(['delete']); - $recipe = $this->Recipes->get($id); - $message = 'Deleted'; - if (!$this->Recipes->delete($recipe)) { - $message = 'Error'; - } - $this->set('message', $message); - $this->viewBuilder()->setOption('serialize', ['message']); - } - } - -In our ``RecipesController``, we have several actions that define the logic -to create, edit, view and delete recipes. In each of our actions we're using -the ``serialize`` option to tell CakePHP which view variables should be -serialized when making API responses. We'll connect our controller to the -application URLs with :ref:`resource-routes`:: - - // in config/routes.php - $routes->scope('/', function (RouteBuilder $routes): void { - $routes->setExtensions(['json']); - $routes->resources('Recipes'); - }); - -These routes will enable URLs like ``/recipes.json`` to return a JSON encoded -response. Clients could also make a request to ``/recipes`` with the -``Content-Type: application/json`` header as well. - -Encoding Response Data -====================== - -In the above controller, we're defining a ``viewClasses()`` method. This method -defines which views your controller has available for content-negotitation. -We're including CakePHP's ``JsonView`` which enables JSON based responses. To -learn more about it and Xml based views see :doc:`/views/json-and-xml-views`. is -used by CakePHP to select a view class to render a REST response with. - -Next, we have several methods that expose basic logic to create, edit, view and -delete recipes. In each of our actions we're using the ``serialize`` option to -tell CakePHP which view variables should be serialized when making API -responses. - -If we wanted to modify the data before it is converted into JSON we should not -define the ``serialize`` option, and instead use template files. We would place -the REST templates for our RecipesController inside **templates/Recipes/json**. - -See the :ref:`controller-viewclasses` for more information on how CakePHP's -response negotiation functionality. - -Parsing Request Bodies -====================== - -Creating the logic for the edit action requires another step. Because our -resources are serialized as JSON it would be ergonomic if our requests also -contained the JSON representation. - -In our ``Application`` class ensure the following is present:: - - $middlewareQueue->add(new BodyParserMiddleware()); - -This middleware will use the ``content-type`` header to detect the format of -request data and parse enabled formats. By default only ``JSON`` parsing is -enabled by default. You can enable XML support by enabling the ``xml`` -constructor option. When a request is made with a ``Content-Type`` of -``application/json``, CakePHP will decode the request data and update the -request so that ``$request->getData()`` contains the parsed body. - -You can also wire in additional deserializers for alternate formats if you -need them, using :php:meth:`BodyParserMiddleware::addParser()`. - -.. meta:: - :title lang=en: REST - :keywords lang=en: application programmers,default routes,core functionality,result format,mashups,recipe database,request method,access,config,soap,recipes,logic,audience,cakephp,running,api diff --git a/en/development/routing.rst b/en/development/routing.rst deleted file mode 100644 index fbd222e49a..0000000000 --- a/en/development/routing.rst +++ /dev/null @@ -1,1728 +0,0 @@ -Routing -####### - -.. php:namespace:: Cake\Routing - -.. php:class:: RouterBuilder - -Routing provides you tools that map URLs to controller actions. By defining -routes, you can separate how your application is implemented from how its URLs -are structured. - -Routing in CakePHP also encompasses the idea of reverse routing, where an array -of parameters can be transformed into a URL string. By using reverse routing, -you can re-factor your application's URL structure without having to update all -your code. - -.. index:: routes.php - -Quick Tour -========== - -This section will teach you by example the most common uses of the CakePHP -Router. Typically you want to display something as a landing page, so you add -this to your **config/routes.php** file:: - - /** @var \Cake\Routing\RouteBuilder $routes */ - $routes->connect('/', ['controller' => 'Articles', 'action' => 'index']); - -This will execute the index method in the ``ArticlesController`` when the -homepage of your site is visited. Sometimes you need dynamic routes that will -accept multiple parameters, this would be the case, for example of a route for -viewing an article's content:: - - $routes->connect('/articles/*', ['controller' => 'Articles', 'action' => 'view']); - -The above route will accept any URL looking like ``/articles/15`` and invoke the -method ``view(15)`` in the ``ArticlesController``. This will not, though, -prevent people from trying to access URLs looking like ``/articles/foobar``. If -you wish, you can restrict some parameters to conform to a regular expression:: - - // Using fluent interface - $routes->connect( - '/articles/{id}', - ['controller' => 'Articles', 'action' => 'view'], - ) - ->setPatterns(['id' => '\d+']) - ->setPass(['id']); - - // Using options array - $routes->connect( - '/articles/{id}', - ['controller' => 'Articles', 'action' => 'view'], - ['id' => '\d+', 'pass' => ['id']] - ); - -The previous example changed the star matcher by a new placeholder ``{id}``. -Using placeholders allows us to validate parts of the URL, in this case we used -the ``\d+`` regular expression so that only digits are matched. Finally, we told -the Router to treat the ``id`` placeholder as a function argument to the -``view()`` function by specifying the ``pass`` option. More on using this -option later. - -The CakePHP Router can also reverse match routes. That means that from an -array containing matching parameters, it is capable of generating a URL string:: - - use Cake\Routing\Router; - - echo Router::url(['controller' => 'Articles', 'action' => 'view', 'id' => 15]); - // Will output - /articles/15 - -Routes can also be labelled with a unique name, this allows you to quickly -reference them when building links instead of specifying each of the routing -parameters:: - - // In routes.php - $routes->connect( - '/upgrade', - ['controller' => 'Subscriptions', 'action' => 'create'], - ['_name' => 'upgrade'] - ); - - use Cake\Routing\Router; - - echo Router::url(['_name' => 'upgrade']); - // Will output - /upgrade - -To help keep your routing code DRY, the Router has the concept of 'scopes'. -A scope defines a common path segment, and optionally route defaults. Any routes -connected inside a scope will inherit the path/defaults from their wrapping -scopes:: - - $routes->scope('/blog', ['plugin' => 'Blog'], function (RouteBuilder $routes) { - $routes->connect('/', ['controller' => 'Articles']); - }); - -The above route would match ``/blog/`` and send it to -``Blog\Controller\ArticlesController::index()``. - -The application skeleton comes with a few routes to get you started. Once you've -added your own routes, you can remove the default routes if you don't need them. - -.. index:: {controller}, {action}, {plugin} -.. index:: greedy star, trailing star -.. _connecting-routes: -.. _routes-configuration: - -Connecting Routes -================= - -To keep your code :term:`DRY` you should use 'routing scopes'. Routing -scopes not only let you keep your code DRY, they also help Router optimize its -operation. This method defaults to the ``/`` scope. To create a scope and connect -some routes we'll use the ``scope()`` method:: - - // In config/routes.php - use Cake\Routing\RouteBuilder; - use Cake\Routing\Route\DashedRoute; - - $routes->scope('/', function (RouteBuilder $routes) { - // Connect the generic fallback routes. - $routes->fallbacks(DashedRoute::class); - }); - -The ``connect()`` method takes up to three parameters: the URL template you wish -to match, the default values for your route elements, and the options for the -route. Options frequently include regular expression rules to help the router -match elements in the URL. - -The basic format for a route definition is:: - - $routes->connect( - '/url/template', - ['targetKey' => 'targetValue'], - ['option' => 'matchingRegex'] - ); - -The first parameter is used to tell the router what sort of URL you're trying to -control. The URL is a normal slash delimited string, but can also contain -a wildcard (\*) or :ref:`route-elements`. Using a wildcard tells the router -that you are willing to accept any additional arguments supplied. Routes without -a \* only match the exact template pattern supplied. - -Once you've specified a URL, you use the last two parameters of ``connect()`` to -tell CakePHP what to do with a request once it has been matched. The second -parameter defines the route 'target'. This can be defined either as an array, or -as a destination string. A few examples of route targets are:: - - // Array target to an application controller - $routes->connect( - '/users/view/*', - ['controller' => 'Users', 'action' => 'view'] - ); - $routes->connect('/users/view/*', 'Users::view'); - - // Array target to a prefixed plugin controller - $routes->connect( - '/admin/cms/articles', - ['prefix' => 'Admin', 'plugin' => 'Cms', 'controller' => 'Articles', 'action' => 'index'] - ); - $routes->connect('/admin/cms/articles', 'Cms.Admin/Articles::index'); - -The first route we connect matches URLs starting with ``/users/view`` and maps -those requests to the ``UsersController->view()``. The trailing ``/*`` tells the -router to pass any additional segments as method arguments. For example, -``/users/view/123`` would map to ``UsersController->view(123)``. - -The above example also illustrates string targets. String targets provide -a compact way to define a route's destination. String targets have the following -syntax:: - - [Plugin].[Prefix]/[Controller]::[action] - -Some example string targets are:: - - // Application controller - 'Articles::view' - - // Application controller with prefix - Admin/Articles::view - - // Plugin controller - Cms.Articles::edit - - // Prefixed plugin controller - Vendor/Cms.Management/Admin/Articles::view - -Earlier we used the greedy star (``/*``) to capture additional path segments, -there is also the trailing star (``/**``). Using a trailing double star, -will capture the remainder of a URL as a single passed argument. This is useful -when you want to use an argument that included a ``/`` in it:: - - $routes->connect( - '/pages/**', - ['controller' => 'Pages', 'action' => 'show'] - ); - -The incoming URL of ``/pages/the-example-/-and-proof`` would result in a single -passed argument of ``the-example-/-and-proof``. - -The second parameter of ``connect()`` can define any parameters that -compose the default route parameters:: - - $routes->connect( - '/government', - ['controller' => 'Pages', 'action' => 'display', 5] - ); - -This example uses the second parameter of ``connect()`` to -define default parameters. If you built an application that features products for -different categories of customers, you might consider creating a route. This -allows you to link to ``/government`` rather than ``/pages/display/5``. - -A common use for routing is to rename controllers and their actions. Instead of -accessing our users controller at ``/users/some-action/5``, we'd like to be able -to access it through ``/cooks/some-action/5``. The following route takes care of -that:: - - $routes->connect( - '/cooks/{action}/*', ['controller' => 'Users'] - ); - -This is telling the Router that any URL beginning with ``/cooks/`` should be -sent to the ``UsersController``. The action called will depend on the value of -the ``{action}`` parameter. By using :ref:`route-elements`, you can create -variable routes, that accept user input or variables. The above route also uses -the greedy star. The greedy star indicates that this route should accept any -additional positional arguments given. These arguments will be made available in -the :ref:`passed-arguments` array. - -When generating URLs, routes are used too. Using -``['controller' => 'Users', 'action' => 'some-action', 5]`` as -a URL will output ``/cooks/some-action/5`` if the above route is the -first match found. - -The routes we've connected so far will match any HTTP verb. If you are building -a REST API you'll often want to map HTTP actions to different controller methods. -The ``RouteBuilder`` provides helper methods that make defining routes for -specific HTTP verbs simpler:: - - // Create a route that only responds to GET requests. - $routes->get( - '/cooks/{id}', - ['controller' => 'Users', 'action' => 'view'], - 'users:view' - ); - - // Create a route that only responds to PUT requests - $routes->put( - '/cooks/{id}', - ['controller' => 'Users', 'action' => 'update'], - 'users:update' - ); - -The above routes map the same URL to different controller actions based on the -HTTP verb used. GET requests will go to the 'view' action, while PUT requests -will go to the 'update' action. There are HTTP helper methods for: - -* GET -* POST -* PUT -* PATCH -* DELETE -* OPTIONS -* HEAD - -All of these methods return the route instance allowing you to leverage the -:ref:`fluent setters ` to further configure your route. - -.. _route-elements: - -Route Elements --------------- - -You can specify your own route elements and doing so gives you the -power to define places in the URL where parameters for controller -actions should lie. When a request is made, the values for these -route elements are found in ``$this->request->getParam()`` in the controller. -When you define a custom route element, you can optionally specify a regular -expression - this tells CakePHP how to know if the URL is correctly formed or -not. If you choose to not provide a regular expression, any non ``/`` character -will be treated as part of the parameter:: - - $routes->connect( - '/{controller}/{id}', - ['action' => 'view'] - )->setPatterns(['id' => '[0-9]+']); - - $routes->connect( - '/{controller}/{id}', - ['action' => 'view'], - ['id' => '[0-9]+'] - ); - -The above example illustrates how to create a quick way to view -models from any controller by crafting a URL that looks like -``/controller-name/{id}``. The URL provided to ``connect()`` specifies two -route elements: ``{controller}`` and ``{id}``. The ``{controller}`` element -is a CakePHP default route element, so the router knows how to match and -identify controller names in URLs. The ``{id}`` element is a custom -route element, and must be further clarified by specifying a -matching regular expression in the third parameter of ``connect()``. - -CakePHP does not automatically produce lowercased and dashed URLs when using the -``{controller}`` parameter. If you need this, the above example could be -rewritten like so:: - - use Cake\Routing\Route\DashedRoute; - - // Create a builder with a different route class. - $routes->scope('/', function (RouteBuilder $routes) { - $routes->setRouteClass(DashedRoute::class); - $routes->connect('/{controller}/{id}', ['action' => 'view']) - ->setPatterns(['id' => '[0-9]+']); - - $routes->connect( - '/{controller}/{id}', - ['action' => 'view'], - ['id' => '[0-9]+'] - ); - }); - -The ``DashedRoute`` class will make sure that the ``{controller}`` and -``{plugin}`` parameters are correctly lowercased and dashed. - -.. note:: - - Patterns used for route elements must not contain any capturing - groups. If they do, Router will not function correctly. - -Once this route has been defined, requesting ``/apples/5`` would call the ``view()`` -method of the ApplesController. Inside the ``view()`` method, you would need to -access the passed ID at ``$this->request->getParam('id')``. - -If you have a single controller in your application and you do not want the -controller name to appear in the URL, you can map all URLs to actions in your -controller. For example, to map all URLs to actions of the ``home`` controller, -e.g have URLs like ``/demo`` instead of ``/home/demo``, you can do the -following:: - - $routes->connect('/{action}', ['controller' => 'Home']); - -If you would like to provide a case insensitive URL, you can use regular -expression inline modifiers:: - - $routes->connect( - '/{userShortcut}', - ['controller' => 'Teachers', 'action' => 'profile', 1], - )->setPatterns(['userShortcut' => '(?i:principal)']); - -One more example, and you'll be a routing pro:: - - $routes->connect( - '/{controller}/{year}/{month}/{day}', - ['action' => 'index'] - )->setPatterns([ - 'year' => '[12][0-9]{3}', - 'month' => '0[1-9]|1[012]', - 'day' => '0[1-9]|[12][0-9]|3[01]' - ]); - -This is rather involved, but shows how powerful routes can be. The URL supplied -has four route elements. The first is familiar to us: it's a default route -element that tells CakePHP to expect a controller name. - -Next, we specify some default values. Regardless of the controller, -we want the ``index()`` action to be called. - -Finally, we specify some regular expressions that will match years, -months and days in numerical form. Note that parenthesis (capturing groups) -are not supported in the regular expressions. You can still specify -alternates, as above, but not grouped with parenthesis. - -Once defined, this route will match ``/articles/2007/02/01``, -``/articles/2004/11/16``, handing the requests to -the ``index()`` actions of their respective controllers, with the date -parameters in ``$this->request->getParam()``. - -Reserved Route Elements ------------------------ - -There are several route elements that have special meaning in -CakePHP, and should not be used unless you want the special meaning - -* ``controller`` Used to name the controller for a route. -* ``action`` Used to name the controller action for a route. -* ``plugin`` Used to name the plugin a controller is located in. -* ``prefix`` Used for :ref:`prefix-routing` -* ``_ext`` Used for :ref:`File extentions routing `. -* ``_base`` Set to ``false`` to remove the base path from the generated URL. If - your application is not in the root directory, this can be used to generate - URLs that are 'cake relative'. -* ``_scheme`` Set to create links on different schemes like `webcal` or `ftp`. - Defaults to the current scheme. -* ``_host`` Set the host to use for the link. Defaults to the current host. -* ``_port`` Set the port if you need to create links on non-standard ports. -* ``_full`` If ``true`` the value of ``App.fullBaseUrl`` mentioned in - :ref:`general-configuration` will be prepended to generated URLs. -* ``#`` Allows you to set URL hash fragments. -* ``_https`` Set to ``true`` to convert the generated URL to https or ``false`` - to force http. Prior to 4.5.0 use ``_ssl``. -* ``_method`` Define the HTTP verb/method to use. Useful when working with - :ref:`resource-routes`. -* ``_name`` Name of route. If you have setup named routes, you can use this key - to specify it. - -.. _route-fluent-methods: - -Configuring Route Options -------------------------- - -There are a number of route options that can be set on each route. After -connecting a route you can use its fluent builder methods to further configure -the route. These methods replace many of the keys in the ``$options`` parameter -of ``connect()``:: - - $routes->connect( - '/{lang}/articles/{slug}', - ['controller' => 'Articles', 'action' => 'view'], - ) - // Allow GET and POST requests. - ->setMethods(['GET', 'POST']) - - // Only match on the blog subdomain. - ->setHost('blog.example.com') - - // Set the route elements that should be converted to passed arguments - ->setPass(['slug']) - - // Set the matching patterns for route elements - ->setPatterns([ - 'slug' => '[a-z0-9-_]+', - 'lang' => 'en|fr|es', - ]) - - // Also allow JSON file extensions - ->setExtensions(['json']) - - // Set lang to be a persistent parameter - ->setPersist(['lang']); - -Passing Parameters to Action ----------------------------- - -When connecting routes using :ref:`route-elements` you may want to have routed -elements be passed arguments instead. The ``pass`` option indicates which route -elements should also be made available as arguments passed into the controller -functions:: - - // src/Controller/BlogsController.php - public function view($articleId = null, $slug = null) - { - // Some code here... - } - - // routes.php - $routes->scope('/', function (RouteBuilder $routes) { - $routes->connect( - '/blog/{id}-{slug}', // For example, /blog/3-CakePHP_Rocks - ['controller' => 'Blogs', 'action' => 'view'] - ) - // Define the route elements in the route template - // to prepend as function arguments. Order matters as this - // will pass the `$id` and `$slug` elements as the first and - // second parameters. Any additional passed parameters in your - // route will be added after the setPass() arguments. - ->setPass(['id', 'slug']) - // Define a pattern that `id` must match. - ->setPatterns([ - 'id' => '[0-9]+', - ]); - }); - -Now thanks to the reverse routing capabilities, you can pass in the URL array -like below and CakePHP will know how to form the URL as defined in the routes:: - - // view.php - // This will return a link to /blog/3-CakePHP_Rocks - echo $this->Html->link('CakePHP Rocks', [ - 'controller' => 'Blog', - 'action' => 'view', - 'id' => 3, - 'slug' => 'CakePHP_Rocks' - ]); - - // You can also used numerically indexed parameters. - echo $this->Html->link('CakePHP Rocks', [ - 'controller' => 'Blog', - 'action' => 'view', - 3, - 'CakePHP_Rocks' - ]); - -.. _path-routing: - -Using Path Routing ------------------- - -We talked about string targets above. The same also works for URL generation using -``Router::pathUrl()``:: - - echo Router::pathUrl('Articles::index'); - // outputs: /articles - - echo Router::pathUrl('MyBackend.Admin/Articles::view', [3]); - // outputs: /admin/my-backend/articles/view/3 - -.. tip:: - - IDE support for Path Routing autocomplete can be enabled with `CakePHP IdeHelper Plugin `_. - -.. _named-routes: - -Using Named Routes ------------------- - -Sometimes you'll find typing out all the URL parameters for a route too verbose, -or you'd like to take advantage of the performance improvements that named -routes have. When connecting routes you can specify a ``_name`` option, this -option can be used in reverse routing to identify the route you want to use:: - - // Connect a route with a name. - $routes->connect( - '/login', - ['controller' => 'Users', 'action' => 'login'], - ['_name' => 'login'] - ); - - // Name a verb specific route - $routes->post( - '/logout', - ['controller' => 'Users', 'action' => 'logout'], - 'logout' - ); - - // Generate a URL using a named route. - $url = Router::url(['_name' => 'logout']); - - // Generate a URL using a named route, - // with some query string args. - $url = Router::url(['_name' => 'login', 'username' => 'jimmy']); - -If your route template contains any route elements like ``{controller}`` you'll -need to supply those as part of the options to ``Router::url()``. - -.. note:: - - Route names must be unique across your entire application. The same - ``_name`` cannot be used twice, even if the names occur inside a different - routing scope. - -When building named routes, you will probably want to stick to some conventions -for the route names. CakePHP makes building up route names easier by allowing -you to define name prefixes in each scope:: - - $routes->scope('/api', ['_namePrefix' => 'api:'], function (RouteBuilder $routes) { - // This route's name will be `api:ping` - $routes->get('/ping', ['controller' => 'Pings'], 'ping'); - }); - // Generate a URL for the ping route - Router::url(['_name' => 'api:ping']); - - // Use namePrefix with plugin() - $routes->plugin('Contacts', ['_namePrefix' => 'contacts:'], function (RouteBuilder $routes) { - // Connect routes. - }); - - // Or with prefix() - $routes->prefix('Admin', ['_namePrefix' => 'admin:'], function (RouteBuilder $routes) { - // Connect routes. - }); - -You can also use the ``_namePrefix`` option inside nested scopes and it works as -you'd expect:: - - $routes->plugin('Contacts', ['_namePrefix' => 'contacts:'], function (RouteBuilder $routes) { - $routes->scope('/api', ['_namePrefix' => 'api:'], function (RouteBuilder $routes) { - // This route's name will be `contacts:api:ping` - $routes->get('/ping', ['controller' => 'Pings'], 'ping'); - }); - }); - - // Generate a URL for the ping route - Router::url(['_name' => 'contacts:api:ping']); - -Routes connected in named scopes will only have names added if the route is also -named. Nameless routes will not have the ``_namePrefix`` applied to them. - -.. index:: admin routing, prefix routing -.. _prefix-routing: - -Prefix Routing --------------- - -.. php:staticmethod:: prefix($name, $callback) - -Many applications require an administration section where -privileged users can make changes. This is often done through a -special URL such as ``/admin/users/edit/5``. In CakePHP, prefix routing -can be enabled by using the ``prefix`` scope method:: - - use Cake\Routing\Route\DashedRoute; - - $routes->prefix('Admin', function (RouteBuilder $routes) { - // All routes here will be prefixed with `/admin`, and - // have the `'prefix' => 'Admin'` route element added that - // will be required when generating URLs for these routes - $routes->fallbacks(DashedRoute::class); - }); - -Prefixes are mapped to sub-namespaces in your application's ``Controller`` -namespace. By having prefixes as separate controllers you can create smaller and -simpler controllers. Behavior that is common to the prefixed and non-prefixed -controllers can be encapsulated using inheritance, -:doc:`/controllers/components`, or traits. Using our users example, accessing -the URL ``/admin/users/edit/5`` would call the ``edit()`` method of our -**src/Controller/Admin/UsersController.php** passing 5 as the first parameter. -The view file used would be **templates/Admin/Users/edit.php** - -You can map the URL /admin to your ``index()`` action of pages controller using -following route:: - - $routes->prefix('Admin', function (RouteBuilder $routes) { - // Because you are in the admin scope, - // you do not need to include the /admin prefix - // or the Admin route element. - $routes->connect('/', ['controller' => 'Pages', 'action' => 'index']); - }); - -When creating prefix routes, you can set additional route parameters using -the ``$options`` argument:: - - $routes->prefix('Admin', ['param' => 'value'], function (RouteBuilder $routes) { - // Routes connected here are prefixed with '/admin' and - // have the 'param' routing key set. - $routes->connect('/{controller}'); - }); - -Note the additional route parameters will be added to all the connected routes defined -inside the prefix block. You will need to use all the parameters in the url array to -build the route later, if you don't use them you'll get a ``MissingRouteException``. - -Multi word prefixes are by default converted using dasherize inflection, ie ``MyPrefix`` -would be mapped to ``my-prefix`` in the URL. Make sure to set a path for such prefixes -if you want to use a different format like for example underscoring:: - - $routes->prefix('MyPrefix', ['path' => '/my_prefix'], function (RouteBuilder $routes) { - // Routes connected here are prefixed with '/my_prefix' - $routes->connect('/{controller}'); - }); - -You can define prefixes inside plugin scopes as well:: - - $routes->plugin('DebugKit', function (RouteBuilder $routes) { - $routes->prefix('Admin', function (RouteBuilder $routes) { - $routes->connect('/{controller}'); - }); - }); - -The above would create a route template like ``/debug-kit/admin/{controller}``. -The connected route would have the ``plugin`` and ``prefix`` route elements set. - -When defining prefixes, you can nest multiple prefixes if necessary:: - - $routes->prefix('Manager', function (RouteBuilder $routes) { - $routes->prefix('Admin', function (RouteBuilder $routes) { - $routes->connect('/{controller}/{action}'); - }); - }); - -The above would create a route template like ``/manager/admin/{controller}/{action}``. -The connected route would have the ``prefix`` route element set to -``Manager/Admin``. - -The current prefix will be available from the controller methods through -``$this->request->getParam('prefix')`` - -When using prefix routes it's important to set the ``prefix`` option, and to -use the same CamelCased format that is used in the ``prefix()`` method. Here's -how to build this link using the HTML helper:: - - // Go into a prefixed route. - echo $this->Html->link( - 'Manage articles', - ['prefix' => 'Manager/Admin', 'controller' => 'Articles', 'action' => 'add'] - ); - - // Leave a prefix - echo $this->Html->link( - 'View Post', - ['prefix' => false, 'controller' => 'Articles', 'action' => 'view', 5] - ); - -.. index:: plugin routing - -Creating Links to Prefix Routes -------------------------------- - -You can create links that point to a prefix, by adding the prefix key to your -URL array:: - - echo $this->Html->link( - 'New admin todo', - ['prefix' => 'Admin', 'controller' => 'TodoItems', 'action' => 'create'] - ); - -When using nesting, you need to chain them together:: - - echo $this->Html->link( - 'New todo', - ['prefix' => 'Admin/MyPrefix', 'controller' => 'TodoItems', 'action' => 'create'] - ); - -This would link to a controller with the namespace ``App\Controller\Admin\MyPrefix`` and the file path -``src/Controller/Admin/MyPrefix/TodoItemsController.php``. - -.. note:: - - The prefix is always CamelCased here, even if the routing result is dashed. - The route itself will do the inflection if necessary. - -Plugin Routing --------------- - -.. php:staticmethod:: plugin($name, $options = [], $callback) - -Routes for :doc:`/plugins` should be created using the ``plugin()`` -method. This method creates a new routing scope for the plugin's routes:: - - $routes->plugin('DebugKit', function (RouteBuilder $routes) { - // Routes connected here are prefixed with '/debug-kit' and - // have the plugin route element set to 'DebugKit'. - $routes->connect('/{controller}'); - }); - -When creating plugin scopes, you can customize the path element used with the -``path`` option:: - - $routes->plugin('DebugKit', ['path' => '/debugger'], function (RouteBuilder $routes) { - // Routes connected here are prefixed with '/debugger' and - // have the plugin route element set to 'DebugKit'. - $routes->connect('/{controller}'); - }); - -When using scopes you can nest plugin scopes within prefix scopes:: - - $routes->prefix('Admin', function (RouteBuilder $routes) { - $routes->plugin('DebugKit', function (RouteBuilder $routes) { - $routes->connect('/{controller}'); - }); - }); - -The above would create a route that looks like ``/admin/debug-kit/{controller}``. -It would have the ``prefix``, and ``plugin`` route elements set. The -:ref:`plugin-routes` section has more information on building plugin routes. - -Creating Links to Plugin Routes -------------------------------- - -You can create links that point to a plugin, by adding the plugin key to your -URL array:: - - echo $this->Html->link( - 'New todo', - ['plugin' => 'Todo', 'controller' => 'TodoItems', 'action' => 'create'] - ); - -Conversely if the active request is a plugin request and you want to create -a link that has no plugin you can do the following:: - - echo $this->Html->link( - 'New todo', - ['plugin' => null, 'controller' => 'Users', 'action' => 'profile'] - ); - -By setting ``'plugin' => null`` you tell the Router that you want to -create a link that is not part of a plugin. - -SEO-Friendly Routing --------------------- - -Some developers prefer to use dashes in URLs, as it's perceived to give -better search engine rankings. The ``DashedRoute`` class can be used in your -application with the ability to route plugin, controller, and camelized action -names to a dashed URL. - -For example, if we had a ``ToDo`` plugin, with a ``TodoItems`` controller, and a -``showItems()`` action, it could be accessed at ``/to-do/todo-items/show-items`` -with the following router connection:: - - use Cake\Routing\Route\DashedRoute; - - $routes->plugin('ToDo', ['path' => 'to-do'], function (RouteBuilder $routes) { - $routes->fallbacks(DashedRoute::class); - }); - -Matching Specific HTTP Methods ------------------------------- - -Routes can match specific HTTP methods using the HTTP verb helper methods:: - - $routes->scope('/', function (RouteBuilder $routes) { - // This route only matches on POST requests. - $routes->post( - '/reviews/start', - ['controller' => 'Reviews', 'action' => 'start'] - ); - - // Match multiple verbs - $routes->connect( - '/reviews/start', - [ - 'controller' => 'Reviews', - 'action' => 'start', - ] - )->setMethods(['POST', 'PUT']); - }); - -You can match multiple HTTP methods by using an array. Because the ``_method`` -parameter is a routing key, it participates in both URL parsing and URL -generation. To generate URLs for method specific routes you'll need to include -the ``_method`` key when generating the URL:: - - $url = Router::url([ - 'controller' => 'Reviews', - 'action' => 'start', - '_method' => 'POST', - ]); - -Matching Specific Hostnames ---------------------------- - -Routes can use the ``_host`` option to only match specific hosts. You can use -the ``*.`` wildcard to match any subdomain:: - - $routes->scope('/', function (RouteBuilder $routes) { - // This route only matches on http://images.example.com - $routes->connect( - '/images/default-logo.png', - ['controller' => 'Images', 'action' => 'default'] - )->setHost('images.example.com'); - - // This route only matches on http://*.example.com - $routes->connect( - '/images/old-logo.png', - ['controller' => 'Images', 'action' => 'oldLogo'] - )->setHost('*.example.com'); - }); - -The ``_host`` option is also used in URL generation. If your ``_host`` option -specifies an exact domain, that domain will be included in the generated URL. -However, if you use a wildcard, then you will need to provide the ``_host`` -parameter when generating URLs:: - - // If you have this route - $routes->connect( - '/images/old-logo.png', - ['controller' => 'Images', 'action' => 'oldLogo'] - )->setHost('images.example.com'); - - // You need this to generate a url - echo Router::url([ - 'controller' => 'Images', - 'action' => 'oldLogo', - '_host' => 'images.example.com', - ]); - -.. index:: file extensions -.. _file-extensions: - -Routing File Extensions ------------------------ - -.. php:staticmethod:: extensions(string|array|null $extensions, $merge = true) - -To handle different file extensions in your URLs, you can define the extensions -using the :php:meth:`Cake\\Routing\\RouteBuilder::setExtensions()` method:: - - $routes->scope('/', function (RouteBuilder $routes) { - $routes->setExtensions(['json', 'xml']); - }); - -This will enable the named extensions for all routes that are being connected in -that scope **after** the ``setExtensions()`` call, including those that are being -connected in nested scopes. - -.. note:: - - Setting the extensions should be the first thing you do in a scope, as the - extensions will only be applied to routes connected **after** the extensions - are set. - - Also be aware that re-opened scopes will **not** inherit extensions defined in - previously opened scopes. - -By using extensions, you tell the router to remove any matching file extensions -from the URL, and then parse what remains. If you want to create a URL such as -/page/title-of-page.html you would create your route using:: - - $routes->scope('/page', function (RouteBuilder $routes) { - $routes->setExtensions(['json', 'xml', 'html']); - $routes->connect( - '/{title}', - ['controller' => 'Pages', 'action' => 'view'] - )->setPass(['title']); - }); - -Then to create links which map back to the routes simply use:: - - $this->Html->link( - 'Link title', - ['controller' => 'Pages', 'action' => 'view', 'title' => 'super-article', '_ext' => 'html'] - ); - -.. _route-scoped-middleware: - -Route Scoped Middleware -======================= - -While Middleware can be applied to your entire application, applying middleware -to specific routing scopes offers more flexibility, as you can apply middleware -only where it is needed allowing your middleware to not concern itself with -how/where it is being applied. - -.. note:: - - Applied scoped middleware will be run by :ref:`RoutingMiddleware `, - normally at the end of your application's middleware queue. - -Before middleware can be applied to a scope, it needs to be -registered into the route collection:: - - // in config/routes.php - use Cake\Http\Middleware\CsrfProtectionMiddleware; - use Cake\Http\Middleware\EncryptedCookieMiddleware; - - $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware()); - $routes->registerMiddleware('cookies', new EncryptedCookieMiddleware()); - -Once registered, scoped middleware can be applied to specific -scopes:: - - $routes->scope('/cms', function (RouteBuilder $routes) { - // Enable CSRF & cookies middleware - $routes->applyMiddleware('csrf', 'cookies'); - $routes->get('/articles/{action}/*', ['controller' => 'Articles']); - }); - -In situations where you have nested scopes, inner scopes will inherit the -middleware applied in the containing scope:: - - $routes->scope('/api', function (RouteBuilder $routes) { - $routes->applyMiddleware('ratelimit', 'auth.api'); - $routes->scope('/v1', function (RouteBuilder $routes) { - $routes->applyMiddleware('v1compat'); - // Define routes here. - }); - }); - -In the above example, the routes defined in ``/v1`` will have 'ratelimit', -'auth.api', and 'v1compat' middleware applied. If you re-open a scope, the -middleware applied to routes in each scope will be isolated:: - - $routes->scope('/blog', function (RouteBuilder $routes) { - $routes->applyMiddleware('auth'); - // Connect the authenticated actions for the blog here. - }); - $routes->scope('/blog', function (RouteBuilder $routes) { - // Connect the public actions for the blog here. - }); - -In the above example, the two uses of the ``/blog`` scope do not share -middleware. However, both of these scopes will inherit middleware defined in -their enclosing scopes. - -Grouping Middleware -------------------- - -To help keep your route code :abbr:`DRY (Do not Repeat Yourself)` middleware can -be combined into groups. Once combined groups can be applied like middleware -can:: - - $routes->registerMiddleware('cookie', new EncryptedCookieMiddleware()); - $routes->registerMiddleware('auth', new AuthenticationMiddleware()); - $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware()); - $routes->middlewareGroup('web', ['cookie', 'auth', 'csrf']); - - // Apply the group - $routes->applyMiddleware('web'); - -.. _resource-routes: - -RESTful Routing -=============== - -Router helps generate RESTful routes for your controllers. RESTful routes are -helpful when you are creating API endpoints for your application. If we wanted -to allow REST access to a recipe controller, we'd do something like this:: - - // In config/routes.php... - - $routes->scope('/', function (RouteBuilder $routes) { - $routes->setExtensions(['json']); - $routes->resources('Recipes'); - }); - -The first line sets up a number of default routes for REST -access where method specifies the desired result format, for example, xml, -json and rss. These routes are HTTP Request Method sensitive. - -=========== ===================== ============================== -HTTP format URL.format Controller action invoked -=========== ===================== ============================== -GET /recipes.format RecipesController::index() ------------ --------------------- ------------------------------ -GET /recipes/123.format RecipesController::view(123) ------------ --------------------- ------------------------------ -POST /recipes.format RecipesController::add() ------------ --------------------- ------------------------------ -PUT /recipes/123.format RecipesController::edit(123) ------------ --------------------- ------------------------------ -PATCH /recipes/123.format RecipesController::edit(123) ------------ --------------------- ------------------------------ -DELETE /recipes/123.format RecipesController::delete(123) -=========== ===================== ============================== - -.. note:: - - The default for pattern for resource IDs only matches integers or UUIDs. - If your IDs are different you will have to supply a regular expression pattern - via the ``id`` option, for example, ``$builder->resources('Recipes', ['id' => '.*'])``. - -The HTTP method being used is detected from a few different sources. -The sources in order of preference are: - -#. The ``_method`` POST variable -#. The ``X_HTTP_METHOD_OVERRIDE`` header. -#. The ``REQUEST_METHOD`` header - -The ``_method`` POST variable is helpful in using a browser as a -REST client (or anything else that can do POST). Just set -the value of ``_method`` to the name of the HTTP request method you -wish to emulate. - -Creating Nested Resource Routes -------------------------------- - -Once you have connected resources in a scope, you can connect routes for -sub-resources as well. Sub-resource routes will be prepended by the original -resource name and a id parameter. For example:: - - $routes->scope('/api', function (RouteBuilder $routes) { - $routes->resources('Articles', function (RouteBuilder $routes) { - $routes->resources('Comments'); - }); - }); - -Will generate resource routes for both ``articles`` and ``comments``. The -comments routes will look like:: - - /api/articles/{article_id}/comments - /api/articles/{article_id}/comments/{id} - -You can get the ``article_id`` in ``CommentsController`` by:: - - $this->request->getParam('article_id'); - -By default resource routes map to the same prefix as the containing scope. If -you have both nested and non-nested resource controllers you can use a different -controller in each context by using prefixes:: - - $routes->scope('/api', function (RouteBuilder $routes) { - $routes->resources('Articles', function (RouteBuilder $routes) { - $routes->resources('Comments', ['prefix' => 'Articles']); - }); - }); - -The above would map the 'Comments' resource to the -``App\Controller\Articles\CommentsController``. Having separate controllers lets -you keep your controller logic simpler. The prefixes created this way are -compatible with :ref:`prefix-routing`. - -.. note:: - - While you can nest resources as deeply as you require, it is not recommended - to nest more than 2 resources together. - -Limiting the Routes Created ---------------------------- - -By default CakePHP will connect 6 routes for each resource. If you'd like to -only connect specific resource routes you can use the ``only`` option:: - - $routes->resources('Articles', [ - 'only' => ['index', 'view'] - ]); - -Would create read only resource routes. The route names are ``create``, -``update``, ``view``, ``index``, and ``delete``. - -The default **route name and controller action used** are as follows: - -=========== ======================= -Route name Controller action used -=========== ======================= -create add ------------ ----------------------- -update edit ------------ ----------------------- -view view ------------ ----------------------- -index index ------------ ----------------------- -delete delete -=========== ======================= - - -Changing the Controller Actions Used ------------------------------------- - -You may need to change the controller action names that are used when connecting -routes. For example, if your ``edit()`` action is called ``put()`` you can -use the ``actions`` key to rename the actions used:: - - $routes->resources('Articles', [ - 'actions' => ['update' => 'put', 'create' => 'add'] - ]); - -The above would use ``put()`` for the ``edit()`` action, and ``add()`` -instead of ``create()``. - -Mapping Additional Resource Routes ----------------------------------- - -You can map additional resource methods using the ``map`` option:: - - $routes->resources('Articles', [ - 'map' => [ - 'deleteAll' => [ - 'action' => 'deleteAll', - 'method' => 'DELETE' - ] - ] - ]); - // This would connect /articles/deleteAll - -In addition to the default routes, this would also connect a route for -`/articles/delete-all`. By default the path segment will match the key name. You -can use the 'path' key inside the resource definition to customize the path -name:: - - $routes->resources('Articles', [ - 'map' => [ - 'updateAll' => [ - 'action' => 'updateAll', - 'method' => 'PUT', - 'path' => '/update-many', - ], - ], - ]); - // This would connect /articles/update-many - -If you define 'only' and 'map', make sure that your mapped methods are also in -the 'only' list. - -Prefixed Resource Routing -------------------------- - -Resource routes can be connected to controllers in routing prefixes by -connecting routes within a prefixed scope or by using the ``prefix`` option:: - - $routes->resources('Articles', [ - 'prefix' => 'Api', - ]); - -.. _custom-rest-routing: - -Custom Route Classes for Resource Routes ----------------------------------------- - -You can provide ``connectOptions`` key in the ``$options`` array for -``resources()`` to provide custom setting used by ``connect()``:: - - $routes->scope('/', function (RouteBuilder $routes) { - $routes->resources('Books', [ - 'connectOptions' => [ - 'routeClass' => 'ApiRoute', - ] - ]; - }); - -URL Inflection for Resource Routes ----------------------------------- - -By default, multi-worded controllers' URL fragments are the dashed -form of the controller's name. For example, ``BlogPostsController``'s URL fragment -would be **/blog-posts**. - -You can specify an alternative inflection type using the ``inflect`` option:: - - $routes->scope('/', function (RouteBuilder $routes) { - $routes->resources('BlogPosts', [ - 'inflect' => 'underscore' // Will use ``Inflector::underscore()`` - ]); - }); - -The above will generate URLs styled like: **/blog_posts**. - -Changing the Path Element -------------------------- - -By default resource routes use an inflected form of the resource name for the -URL segment. You can set a custom URL segment with the ``path`` option:: - - $routes->scope('/', function (RouteBuilder $routes) { - $routes->resources('BlogPosts', ['path' => 'posts']); - }); - -.. index:: passed arguments -.. _passed-arguments: - -Passed Arguments -================ - -Passed arguments are additional arguments or path segments that are -used when making a request. They are often used to pass parameters -to your controller methods. :: - - http://localhost/calendars/view/recent/mark - -In the above example, both ``recent`` and ``mark`` are passed arguments to -``CalendarsController::view()``. Passed arguments are given to your controllers -in three ways. First as arguments to the action method called, and secondly they -are available in ``$this->request->getParam('pass')`` as a numerically indexed -array. When using custom routes you can force particular parameters to go into -the passed arguments as well. - -If you were to visit the previously mentioned URL, and you -had a controller action that looked like:: - - class CalendarsController extends AppController - { - public function view($arg1, $arg2) - { - debug(func_get_args()); - } - } - -You would get the following output:: - - Array - ( - [0] => recent - [1] => mark - ) - -This same data is also available at ``$this->request->getParam('pass')`` in your -controllers, views, and helpers. The values in the pass array are numerically -indexed based on the order they appear in the called URL:: - - debug($this->request->getParam('pass')); - -Either of the above would output:: - - Array - ( - [0] => recent - [1] => mark - ) - -When generating URLs, using a :term:`routing array` you add passed -arguments as values without string keys in the array:: - - ['controller' => 'Articles', 'action' => 'view', 5] - -Since ``5`` has a numeric key, it is treated as a passed argument. - -Generating URLs -=============== - -.. php:staticmethod:: url($url = null, $full = false) -.. php:staticmethod:: reverse($params, $full = false) - -Generating URLs or Reverse routing is a feature in CakePHP that is used to -allow you to change your URL structure without having to modify all your code. - -If you create URLs using strings like:: - - $this->Html->link('View', '/articles/view/' . $id); - -And then later decide that ``/articles`` should really be called -'posts' instead, you would have to go through your entire -application renaming URLs. However, if you defined your link like:: - - //`link()` uses Router::url() internally and accepts a routing array - - $this->Html->link( - 'View', - ['controller' => 'Articles', 'action' => 'view', $id] - ); - -or:: - - //'Router::reverse()' operates on the request parameters array - //and will produce a url string, valid input for `link()` - - $requestParams = Router::getRequest()->getAttribute('params'); - $this->Html->link('View', Router::reverse($requestParams)); - -Then when you decided to change your URLs, you could do so by defining a -route. This would change both the incoming URL mapping, as well as the -generated URLs. - -The choice of technique is determined by how well you can predict the routing -array elements. - -Using ``Router::url()`` ------------------------ - -``Router::url()`` allows you to use :term:`routing arrays ` in -situations where the array elements required are fixed or easily deduced. - -It will provide reverse routing when the destination url is well defined:: - - $this->Html->link( - 'View', - ['controller' => 'Articles', 'action' => 'view', $id] - ); - -It is also useful when the destination is unknown but follows a well -defined pattern:: - - $this->Html->link( - 'View', - ['controller' => $controller, 'action' => 'view', $id] - ); - -Elements with numeric keys are treated as :ref:`passed-arguments`. - -When using routing arrays, you can define both query string parameters and -document fragments using special keys:: - - $routes->url([ - 'controller' => 'Articles', - 'action' => 'index', - '?' => ['page' => 1], - '#' => 'top' - ]); - - // Will generate a URL like. - /articles/index?page=1#top - -You can also use any of the special route elements when generating URLs: - -* ``_ext`` Used for :ref:`file-extensions` routing. -* ``_base`` Set to ``false`` to remove the base path from the generated URL. If - your application is not in the root directory, this can be used to generate - URLs that are 'cake relative'. -* ``_scheme`` Set to create links on different schemes like ``webcal`` or - ``ftp``. Defaults to the current scheme. -* ``_host`` Set the host to use for the link. Defaults to the current host. -* ``_port`` Set the port if you need to create links on non-standard ports. -* ``_method`` Define the HTTP verb the URL is for. -* ``_full`` If ``true`` the value of ``App.fullBaseUrl`` mentioned in - :ref:`general-configuration` will be prepended to generated URLs. -* ``_https`` Set to ``true`` to convert the generated URL to https or ``false`` - to force http. Prior to 4.5.0 use ``_ssl`` -* ``_name`` Name of route. If you have setup named routes, you can use this key - to specify it. - -Using ``Router::reverse()`` ---------------------------- - -``Router::reverse()`` allows you to use the :ref:`request-parameters` in cases -where the current URL with some modification is the basis for the destination -and the elements of the current URL are unpredictable. - -As an example, imagine a blog that allowed users to create **Articles** and -**Comments**, and to mark both as either *published* or *draft*. Both the index -page URLs might include the user id. The **Comments** URL might also include -an article id to identify what article the comment refers to. - -Here are urls for this scenario:: - - /articles/index/42 - /comments/index/42/18 - -When the author uses these pages, it would be convenient to include links -that allow the page to be displayed with all results, published only, -or draft only. - -To keep the code DRY, it would be best to include the links through -an element:: - - // element/filter_published.php - - $params = $this->getRequest()->getAttribute('params'); - - /* prepare url for Draft */ - $params = Hash::insert($params, '?.published', 0); - echo $this->Html->link(__('Draft'), Router::reverse($params)); - - /* Prepare url for Published */ - $params = Hash::insert($params, '?.published', 1); - echo $this->Html->link(__('Published'), Router::reverse($params)); - - /* Prepare url for All */ - $params = Hash::remove($params, '?.published'); - echo $this->Html->link(__('All'), Router::reverse($params)); - -The links generated by these method calls would include one or two pass -parameters depending on the structure of the current URL. And the code -would work for any future URL, for example, if you started using -pathPrefixes or if you added more pass parameters. - -Routing Arrays vs Request Parameters -------------------------------------- - -The significant difference between the two arrays and their use in these -reverse routing methods is in the way they include pass parameters. - -Routing arrays include pass parameters as un-keyed values in the array:: - - $url = [ - 'controller' => 'Articles', - 'action' => 'View', - $id, //a pass parameter - 'page' => 3, //a query argument - ]; - -Request parameters include pass parameters on the 'pass' key of the array:: - - $url = [ - 'controller' => 'Articles', - 'action' => 'View', - 'pass' => [$id], //the pass parameters - '?' => ['page' => 3], //the query arguments - ]; - -So it is possible, if you wish, to convert the request parameters into -a routing array or vice versa. - -.. _asset-routing: - -Generating Asset URLs -===================== - -The ``Asset`` class provides methods for generating URLs to your application's -css, javascript, images and other static asset files:: - - use Cake\Routing\Asset; - - // Generate a URL to APP/webroot/js/app.js - $js = Asset::scriptUrl('app.js'); - - // Generate a URL to APP/webroot/css/app.css - $css = Asset::cssUrl('app.css'); - - // Generate a URL to APP/webroot/image/logo.png - $img = Asset::imageUrl('logo.png'); - - // Generate a URL to APP/webroot/files/upload/photo.png - $file = Asset::url('files/upload/photo.png'); - -The above methods also accept an array of options as their second parameter: - -* ``fullBase`` Append the full URL with domain name. -* ``pathPrefix`` Path prefix for relative URLs. -* ``plugin`` You can provide ``false`` to prevent paths from being treated as - a plugin asset. -* ``timestamp`` Overrides the value of ``Asset.timestamp`` in Configure. Set to - ``false`` to skip timestamp generation. Set to ``true`` to apply timestamps - when debug is true. Set to ``'force'`` to always enable timestamping - regardless of debug value. - -:: - - // Generates http://example.org/img/logo.png - $img = Asset::url('logo.png', ['fullBase' => true]); - - // Generates /img/logo.png?1568563625 - // Where the timestamp is the last modified time of the file. - $img = Asset::url('logo.png', ['timestamp' => true]); - -To generate asset URLs for files in plugins use :term:`plugin syntax`:: - - // Generates `/debug_kit/img/cake.png` - $img = Asset::imageUrl('DebugKit.cake.png'); - -.. _redirect-routing: - -Redirect Routing -================ - -Redirect routing allows you to issue HTTP status 30x redirects for -incoming routes, and point them at different URLs. This is useful -when you want to inform client applications that a resource has moved -and you don't want to expose two URLs for the same content. - -Redirection routes are different from normal routes as they perform an actual -header redirection if a match is found. The redirection can occur to -a destination within your application or an outside location:: - - $routes->scope('/', function (RouteBuilder $routes) { - $routes->redirect( - '/home/*', - ['controller' => 'Articles', 'action' => 'view'], - ['persist' => true] - // Or ['persist'=>['id']] for default routing where the - // view action expects $id as an argument. - ); - }) - -Redirects ``/home/*`` to ``/articles/view`` and passes the parameters to -``/articles/view``. Using an array as the redirect destination allows -you to use other routes to define where a URL string should be -redirected to. You can redirect to external locations using -string URLs as the destination:: - - $routes->scope('/', function (RouteBuilder $routes) { - $routes->redirect('/articles/*', 'http://google.com', ['status' => 302]); - }); - -This would redirect ``/articles/*`` to ``http://google.com`` with a -HTTP status of 302. - -.. _entity-routing: - -Entity Routing -============== - -Entity routing allows you to use an entity, an array or object implement -``ArrayAccess`` as the source of routing parameters. This allows you to refactor -routes more easily, and generate URLs with less code. For example, if you start -off with a route that looks like:: - - $routes->get( - '/view/{id}', - ['controller' => 'Articles', 'action' => 'view'], - 'articles:view' - ); - -You can generate URLs to this route using:: - - // $article is an entity in the local scope. - Router::url(['_name' => 'articles:view', 'id' => $article->id]); - -Later on, you may want to expose the article slug in the URL for SEO purposes. -In order to do this you would need to update everywhere you generate a URL to -the ``articles:view`` route, which could take some time. If we use entity routes -we pass the entire article entity into URL generation allowing us to skip any -rework when URLs require more parameters:: - - use Cake\Routing\Route\EntityRoute; - - // Create entity routes for the rest of this scope. - $routes->setRouteClass(EntityRoute::class); - - // Create the route just like before. - $routes->get( - '/view/{id}/{slug}', - ['controller' => 'Articles', 'action' => 'view'], - 'articles:view' - ); - -Now we can generate URLs using the ``_entity`` key:: - - Router::url(['_name' => 'articles:view', '_entity' => $article]); - -This will extract both the ``id`` property and the ``slug`` property out of the -provided entity. - -.. _custom-route-classes: - -Custom Route Classes -==================== - -Custom route classes allow you to extend and change how individual routes parse -requests and handle reverse routing. Route classes have a few conventions: - -* Route classes are expected to be found in the ``Routing\Route`` namespace of - your application or plugin. -* Route classes should extend :php:class:`Cake\\Routing\\Route\\Route`. -* Route classes should implement one or both of ``match()`` and/or ``parse()``. - -The ``parse()`` method is used to parse an incoming URL. It should generate an -array of request parameters that can be resolved into a controller & action. -Return ``null`` from this method to indicate a match failure. - -The ``match()`` method is used to match an array of URL parameters and create a -string URL. If the URL parameters do not match the route ``false`` should be -returned. - -You can use a custom route class when making a route by using the ``routeClass`` -option:: - - $routes->connect( - '/{slug}', - ['controller' => 'Articles', 'action' => 'view'], - ['routeClass' => 'SlugRoute'] - ); - - // Or by setting the routeClass in your scope. - $routes->scope('/', function (RouteBuilder $routes) { - $routes->setRouteClass('SlugRoute'); - $routes->connect( - '/{slug}', - ['controller' => 'Articles', 'action' => 'view'] - ); - }); - -This route would create an instance of ``SlugRoute`` and allow you -to implement custom parameter handling. You can use plugin route classes using -standard :term:`plugin syntax`. - -Default Route Class -------------------- - -.. php:staticmethod:: setRouteClass($routeClass = null) - -If you want to use an alternate route class for your routes besides the -default ``Route``, you can do so by calling ``RouterBuilder::setRouteClass()`` -before setting up any routes and avoid having to specify the ``routeClass`` -option for each route. For example using:: - - use Cake\Routing\Route\DashedRoute; - - $routes->setRouteClass(DashedRoute::class); - -will cause all routes connected after this to use the ``DashedRoute`` route class. -Calling the method without an argument will return current default route class. - -Fallbacks Method ----------------- - -.. php:method:: fallbacks($routeClass = null) - -The fallbacks method is a simple shortcut for defining default routes. The -method uses the passed routing class for the defined rules or if no class is -provided the class returned by ``RouterBuilder::setRouteClass()`` is used. - -Calling fallbacks like so:: - - use Cake\Routing\Route\DashedRoute; - - $routes->fallbacks(DashedRoute::class); - -Is equivalent to the following explicit calls:: - - use Cake\Routing\Route\DashedRoute; - - $routes->connect('/{controller}', ['action' => 'index'], ['routeClass' => DashedRoute::class]); - $routes->connect('/{controller}/{action}/*', [], ['routeClass' => DashedRoute::class]); - -.. note:: - - Using the default route class (``Route``) with fallbacks, or any route - with ``{plugin}`` and/or ``{controller}`` route elements will result in - inconsistent URL case. - -.. warning:: - Fallback route templates are very generic and allow URLs to be generated - and parsed for controllers & actions that do not exist. Fallback URLs can - also introduce ambiguity and duplication in your URLs. - - As your application grows, it is recommended to move away from fallback URLs - and explicitly define the routes in your application. - -Creating Persistent URL Parameters -================================== - -You can hook into the URL generation process using URL filter functions. Filter -functions are called *before* the URLs are matched against the routes, this -allows you to prepare URLs before routing. - -Callback filter functions should expect the following parameters: - -- ``$params`` The URL parameter array being processed. -- ``$request`` The current request (``Cake\Http\ServerRequest`` instance). - -The URL filter function should *always* return the parameters even if unmodified. - -URL filters allow you to implement features like persistent parameters:: - - Router::addUrlFilter(function (array $params, ServerRequest $request) { - if ($request->getParam('lang') && !isset($params['lang'])) { - $params['lang'] = $request->getParam('lang'); - } - - return $params; - }); - -Filter functions are applied in the order they are connected. - -Another use case is changing a certain route on runtime (plugin routes for -example):: - - Router::addUrlFilter(function (array $params, ServerRequest $request) { - if (empty($params['plugin']) || $params['plugin'] !== 'MyPlugin' || empty($params['controller'])) { - return $params; - } - if ($params['controller'] === 'Languages' && $params['action'] === 'view') { - $params['controller'] = 'Locations'; - $params['action'] = 'index'; - $params['language'] = $params[0]; - unset($params[0]); - } - - return $params; - }); - -This will alter the following route:: - - Router::url(['plugin' => 'MyPlugin', 'controller' => 'Languages', 'action' => 'view', 'es']); - -into this:: - - Router::url(['plugin' => 'MyPlugin', 'controller' => 'Locations', 'action' => 'index', 'language' => 'es']); - -.. warning:: - If you are using the caching features of :ref:`routing-middleware` you must - define the URL filters in your application ``bootstrap()`` as filters are - not part of the cached data. - -.. meta:: - :title lang=en: Routing - :keywords lang=en: controller actions,default routes,mod rewrite,code index,string url,php class,incoming requests,dispatcher,url url,meth,maps,match,parameters,array,config,cakephp,apache,router diff --git a/en/development/sessions.rst b/en/development/sessions.rst deleted file mode 100644 index b15c464b34..0000000000 --- a/en/development/sessions.rst +++ /dev/null @@ -1,466 +0,0 @@ -Sessions -######## - -CakePHP provides a wrapper and suite of utility features on top of PHP's native -``session`` extension. Sessions allow you to identify unique users across -requests and store persistent data for specific users. Unlike Cookies, session -data is not available on the client side. Usage of ``$_SESSION`` is generally -avoided in CakePHP, and instead usage of the Session classes is preferred. - -.. _session-configuration: - -Session Configuration -===================== - -Session configuration is generally defined in **/config/app.php**. The available -options are: - -* ``Session.timeout`` - The number of *minutes* a session can remain 'idle'. If - no request is received for ``timeout`` minutes, CakePHP's session - handler will expire the session. You can set this option to ``0`` to disable - server side idle timeouts. - -* ``Session.defaults`` - Allows you to use the built-in default session - configurations as a base for your session configuration. See below for the - built-in defaults. - -* ``Session.handler`` - Allows you to define a custom session handler. The core - database and cache session handlers use this. See below for additional - information on Session handlers. - -* ``Session.ini`` - Allows you to set additional session ini settings for your - config. This combined with ``Session.handler`` replace the custom session - handling features of previous versions - -* ``Session.cookie`` - The name of the cookie to use. Defaults to value set for - ``session.name`` php.ini config. - -* ``Session.cookiePath`` - The url path for which session cookie is set. Maps to - the ``session.cookie_path`` php.ini config. Defaults to base path of app. - -CakePHP's defaults ``session.cookie_secure`` to ``true``, when your application -is on an SSL protocol. If your application serves from both SSL and non-SSL -protocols, then you might have problems with sessions being lost. If you need -access to the session on both SSL and non-SSL domains you will want to disable -this:: - - Configure::write('Session', [ - 'defaults' => 'php', - 'ini' => [ - 'session.cookie_secure' => false - ] - ]); - -CakePHP also sets the `SameSite `__ attribute to ``Lax`` -by default for session cookies, which helps protect against CSRF attacks. -You can change the default value by setting ``session.cookie_samesite`` php.ini config:: - - Configure::write('Session', [ - 'defaults' => 'php', - 'ini' => [ - 'session.cookie_samesite' => 'Strict', - ], - ]); - -The session cookie path defaults to app's base path. To change this you can use -the ``session.cookie_path`` ini value. For example if you want your session to -persist across all subdomains you can do:: - - Configure::write('Session', [ - 'defaults' => 'php', - 'ini' => [ - 'session.cookie_path' => '/', - 'session.cookie_domain' => '.yourdomain.com', - ], - ]); - -By default PHP sets the session cookie to expire as soon as the browser is -closed, regardless of the configured ``Session.timeout`` value. The cookie -timeout is controlled by the ``session.cookie_lifetime`` ini value and can be -configured using:: - - Configure::write('Session', [ - 'defaults' => 'php', - 'ini' => [ - // Invalidate the cookie after 30 minutes - 'session.cookie_lifetime' => 1800 - ] - ]); - -The difference between ``Session.timeout`` and the ``session.cookie_lifetime`` -value is that the latter relies on the client telling the truth about the -cookie. If you require stricter timeout checking, without relying on what the -client reports, you should use ``Session.timeout``. - -Please note that ``Session.timeout`` corresponds to the total time of -inactivity for a user (i.e. the time without visiting any page where the session -is used), and does not limit the total amount of minutes a user can stay active -on the site. - -Built-in Session Handlers & Configuration -========================================= - -CakePHP comes with several built-in session configurations. You can either use -these as the basis for your session configuration, or you can create a fully -custom solution. To use defaults, simply set the 'defaults' key to the name of -the default you want to use. You can then override any sub setting by declaring -it in your Session config:: - - Configure::write('Session', [ - 'defaults' => 'php' - ]); - -The above will use the built-in 'php' session configuration. You could augment -part or all of it by doing the following:: - - Configure::write('Session', [ - 'defaults' => 'php', - 'cookie' => 'my_app', - 'timeout' => 4320 // 3 days - ]); - -The above overrides the timeout and cookie name for the 'php' session -configuration. The built-in configurations are: - -* ``php`` - Saves sessions with the standard settings in your php.ini file. -* ``cake`` - Saves sessions as files inside ``tmp/sessions``. This is a - good option when on hosts that don't allow you to write outside your own home - dir. -* ``database`` - Use the built-in database sessions. See below for more - information. -* ``cache`` - Use the built-in cache sessions. See below for more information. - -Session Handlers ----------------- - -Session handlers can also be defined in the session config array. By defining -the 'handler.engine' config key, you can name the class name, or provide -a handler instance. The class/object must implement the -native PHP ``SessionHandlerInterface``. Implementing this interface will allow -``Session`` to automatically map the methods for the handler. Both the core -Cache and Database session handlers use this method for saving sessions. -Additional settings for the handler should be placed inside the handler array. -You can then read those values out from inside your handler:: - - 'Session' => [ - 'handler' => [ - 'engine' => 'DatabaseSession', - 'model' => 'CustomSessions', - ], - ] - -The above shows how you could setup the Database session handler with an -application model. When using class names as your handler.engine, CakePHP will -expect to find your class in the ``Http\Session`` namespace. For example, if -you had an ``AppSessionHandler`` class, the file should be -**src/Http/Session/AppSessionHandler.php**, and the class name should be -``App\Http\Session\AppSessionHandler``. You can also use session handlers -from inside plugins. By setting the engine to ``MyPlugin.PluginSessionHandler``. - -Database Sessions ------------------ - -If you need to use a database to store your session data, configure as follows:: - - 'Session' => [ - 'defaults' => 'database' - ] - -This configuration requires a database table, having this schema:: - - CREATE TABLE `sessions` ( - `id` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL, - `created` datetime DEFAULT CURRENT_TIMESTAMP, -- Optional - `modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- Optional - `data` blob DEFAULT NULL, -- for PostgreSQL use bytea instead of blob - `expires` int(10) unsigned DEFAULT NULL, - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8; - -You can find a copy of the schema for the sessions table in the `application skeleton `_ in **config/schema/sessions.sql**. - -You can also use your own ``Table`` class to handle the saving of the sessions:: - - 'Session' => [ - 'defaults' => 'database', - 'handler' => [ - 'engine' => 'DatabaseSession', - 'model' => 'CustomSessions', - ], - ] - -The above will tell Session to use the built-in 'database' defaults, and -specify that a Table called ``CustomSessions`` will be the delegate for saving -session information to the database. - -.. _sessions-cache-sessions: - -Cache Sessions --------------- - -The Cache class can be used to store sessions as well. This allows you to store -sessions in a cache like APCu, or Memcached. There are some caveats to -using cache sessions, in that if you exhaust the cache space, sessions will -start to expire as records are evicted. - -To use Cache based sessions you can configure you Session config like:: - - Configure::write('Session', [ - 'defaults' => 'cache', - 'handler' => [ - 'config' => 'session', - ], - ]); - -This will configure Session to use the ``CacheSession`` class as the -delegate for saving the sessions. You can use the 'config' key which cache -configuration to use. The default cache configuration is ``'default'``. - -Session Locking ---------------- - -The app skeleton comes preconfigured with a session config like this:: - - 'Session' => [ - 'defaults' => 'php', - ], - -This means CakePHP will handle sessions via what is configured in your ``php.ini``. -In most cases this will be the default configuration so PHP will save any newly -created session as a file in e.g. ``/var/lib/php/session`` - -But this also means any computationally heavy task like querying a large dataset -combined with an active session will **lock that session file** - therefore -blocking users to e.g. open a second tab of your app to do something else -in the meantime. - -To prevent this behavior you will have to change the way how sessions are being -handled in CakePHP by using a different session handler like :ref:`sessions-cache-sessions` -combined with the :ref:`Redis Engine ` or another cache engine. - -.. tip:: - - If you want to read more about Session Locking see `here `_ - -Setting ini directives -====================== - -The built-in defaults attempt to provide a common base for session -configuration. You may need to tweak specific ini flags as well. CakePHP -exposes the ability to customize the ini settings for both default -configurations, as well as custom ones. The ``ini`` key in the session settings, -allows you to specify individual configuration values. For example you can use -it to control settings like ``session.gc_divisor``:: - - Configure::write('Session', [ - 'defaults' => 'php', - 'ini' => [ - 'session.cookie_name' => 'MyCookie', - 'session.cookie_lifetime' => 1800, // Valid for 30 minutes - 'session.gc_divisor' => 1000, - 'session.cookie_httponly' => true - ] - ]); - -Creating a Custom Session Handler -================================= - -Creating a custom session handler is straightforward in CakePHP. In this -example we'll create a session handler that stores sessions both in the Cache -(APC) and the database. This gives us the best of fast IO of APC, -without having to worry about sessions evaporating when the cache fills up. - -First we'll need to create our custom class and put it in -**src/Http/Session/ComboSession.php**. The class should look -something like:: - - namespace App\Http\Session; - - use Cake\Cache\Cache; - use Cake\Core\Configure; - use Cake\Http\Session\DatabaseSession; - - class ComboSession extends DatabaseSession - { - protected $cacheKey; - - public function __construct() - { - $this->cacheKey = Configure::read('Session.handler.cache'); - parent::__construct(); - } - - // Read data from the session. - public function read($id): string - { - $result = Cache::read($id, $this->cacheKey); - if ($result) { - return $result; - } - - return parent::read($id); - } - - // Write data into the session. - public function write($id, $data): bool - { - Cache::write($id, $data, $this->cacheKey); - - return parent::write($id, $data); - } - - // Destroy a session. - public function destroy($id): bool - { - Cache::delete($id, $this->cacheKey); - - return parent::destroy($id); - } - - // Removes expired sessions. - public function gc($expires = null): bool - { - return parent::gc($expires); - } - } - -Our class extends the built-in ``DatabaseSession`` so we don't have to duplicate -all of its logic and behavior. We wrap each operation with -a :php:class:`Cake\\Cache\\Cache` operation. This lets us fetch sessions from -the fast cache, and not have to worry about what happens when we fill the cache. -In **config/app.php** make the session block look like:: - - 'Session' => [ - 'defaults' => 'database', - 'handler' => [ - 'engine' => 'ComboSession', - 'model' => 'Session', - 'cache' => 'apc', - ], - ], - // Make sure to add a apc cache config - 'Cache' => [ - 'apc' => ['engine' => 'Apc'] - ] - -Now our application will start using our custom session handler for reading and -writing session data. - -.. php:class:: Session - -.. _accessing-session-object: - -Accessing the Session Object -============================ - -You can access the session data any place you have access to a request object. -This means the session is accessible from: - -* Controllers -* Views -* Helpers -* Cells -* Components - -A basic example of session usage in controllers, views and cells would be:: - - $name = $this->request->getSession()->read('User.name'); - - // If you are accessing the session multiple times, - // you will probably want a local variable. - $session = $this->request->getSession(); - $name = $session->read('User.name'); - -In helpers, use ``$this->getView()->getRequest()`` to get the request object; -In components, use ``$this->getController()->getRequest()``. - -Reading & Writing Session Data -============================== - -.. php:method:: read($key, $default = null) - -You can read values from the session using :php:meth:`Hash::extract()` -compatible syntax:: - - $session->read('Config.language', 'en'); - -.. php:method:: readOrFail($key) - -The same as convenience wrapper around non-nullable return value:: - - $session->readOrFail('Config.language'); - -This is useful, when you know this key has to be set and you don't want to have to check -for the existence in code itself. - -.. php:method:: write($key, $value) - -``$key`` should be the dot separated path you wish to write ``$value`` to:: - - $session->write('Config.language', 'en'); - -You may also specify one or multiple hashes like so:: - - $session->write([ - 'Config.theme' => 'blue', - 'Config.language' => 'en', - ]); - -.. php:method:: delete($key) - -When you need to delete data from the session, you can use ``delete()``:: - - $session->delete('Some.value'); - -.. php:staticmethod:: consume($key) - -When you need to read and delete data from the session, you can use -``consume()``:: - - $session->consume('Some.value'); - -.. php:method:: check($key) - -If you want to see if data exists in the session, you can use ``check()``:: - - if ($session->check('Config.language')) { - // Config.language exists and is not null. - } - -Destroying the Session -====================== - -.. php:method:: destroy() - -Destroying the session is useful when users log out. To destroy a session, use -the ``destroy()`` method:: - - $session->destroy(); - -Destroying a session will remove all serverside data in the session, but will -**not** remove the session cookie. - -Rotating Session Identifiers -============================ - -.. php:method:: renew() - -While the ``Authentication Plugin`` automatically renews the session id when users login and -logout, you may need to rotate the session id's manually. To do this use the -``renew()`` method:: - - $session->renew(); - -Flash Messages -============== - -Flash messages are small messages displayed to end users once. They are often -used to present error messages, or confirm that actions took place successfully. - -To set and display flash messages you should use -:doc:`FlashComponent ` and -:doc:`FlashHelper ` - -.. meta:: - :title lang=en: Sessions - :keywords lang=en: session defaults,session classes,utility features,session timeout,session ids,persistent data,session key,session cookie,session data,last session,core database,security level,useragent,security reasons,session id,attr,countdown,regeneration,sessions,config diff --git a/en/development/testing.rst b/en/development/testing.rst deleted file mode 100644 index 4e11e505a3..0000000000 --- a/en/development/testing.rst +++ /dev/null @@ -1,2056 +0,0 @@ -Testing -####### - -CakePHP comes with comprehensive testing support built-in. CakePHP comes with -integration for `PHPUnit `_. In addition to the features -offered by PHPUnit, CakePHP offers some additional features to make testing -easier. This section will cover installing PHPUnit, and getting started with -Unit Testing, and how you can use the extensions that CakePHP offers. - -Installing PHPUnit -================== - -CakePHP uses PHPUnit as its underlying test framework. PHPUnit is the de-facto -standard for unit testing in PHP. It offers a deep and powerful set of features -for making sure your code does what you think it does. PHPUnit can be installed -through using either a `PHAR package `__ or -`Composer `_. - -Install PHPUnit with Composer ------------------------------ - -To install PHPUnit with Composer: - -.. code-block:: console - - $ php composer.phar require --dev phpunit/phpunit:"^10.1" - -This will add the dependency to the ``require-dev`` section of your -``composer.json``, and then install PHPUnit along with any dependencies. - -You can now run PHPUnit using: - -.. code-block:: console - - $ vendor/bin/phpunit - -Using the PHAR File -------------------- - -After you have downloaded the **phpunit.phar** file, you can use it to run your -tests: - -.. code-block:: console - - php phpunit.phar - -.. tip:: - - As a convenience you can make phpunit.phar available globally - on Unix or Linux with the following: - - .. code-block:: shell - - chmod +x phpunit.phar - sudo mv phpunit.phar /usr/local/bin/phpunit - phpunit --version - - Please refer to the PHPUnit documentation for instructions regarding - `Globally installing the PHPUnit PHAR on Windows `__. - -Test Database Setup -=================== - -Remember to have debug enabled in your **config/app_local.php** file before running -any tests. Before running any tests you should be sure to add a ``test`` -datasource configuration to **config/app_local.php**. This configuration is used by -CakePHP for fixture tables and data:: - - 'Datasources' => [ - 'test' => [ - 'datasource' => 'Cake\Database\Driver\Mysql', - 'persistent' => false, - 'host' => 'dbhost', - 'username' => 'dblogin', - 'password' => 'dbpassword', - 'database' => 'test_database', - ], - ], - -.. note:: - - It's a good idea to make the test database and your actual database - different databases. This will prevent embarrassing mistakes later. - -Checking the Test Setup -======================= - -After installing PHPUnit and setting up your ``test`` datasource configuration -you can make sure you're ready to write and run your own tests by running your -application's tests: - -.. code-block:: console - - # For phpunit.phar - $ php phpunit.phar - - # For Composer installed phpunit - $ vendor/bin/phpunit - -The above should run any tests you have, or let you know that no tests were run. -To run a specific test you can supply the path to the test as a parameter to -PHPUnit. For example, if you had a test case for ArticlesTable class you could -run it with: - -.. code-block:: console - - $ vendor/bin/phpunit tests/TestCase/Model/Table/ArticlesTableTest - -You should see a green bar with some additional information about the tests run, -and number passed. - -.. note:: - - If you are on a Windows system you probably won't see any colors. - -Test Case Conventions -===================== - -Like most things in CakePHP, test cases have some conventions. Concerning -tests: - -#. PHP files containing tests should be in your - ``tests/TestCase/[Type]`` directories. -#. The filenames of these files should end in **Test.php** instead - of just .php. -#. The classes containing tests should extend ``Cake\TestSuite\TestCase``, - ``Cake\TestSuite\IntegrationTestCase`` or ``\PHPUnit\Framework\TestCase``. -#. Like other classnames, the test case classnames should match the filename. - **RouterTest.php** should contain ``class RouterTest extends TestCase``. -#. The name of any method containing a test (i.e. containing an - assertion) should begin with ``test``, as in ``testPublished()``. - You can also use the ``@test`` annotation to mark methods as test methods. - -Creating Your First Test Case -============================= - -In the following example, we'll create a test case for a very simple helper -method. The helper we're going to test will be formatting progress bar HTML. -Our helper looks like:: - - namespace App\View\Helper; - - use Cake\View\Helper; - - class ProgressHelper extends Helper - { - public function bar($value) - { - $width = round($value / 100, 2) * 100; - - return sprintf( - '
      -
      -
      ', $width); - } - } - -This is a very simple example, but it will be useful to show how you can create -a simple test case. After creating and saving our helper, we'll create the test -case file in **tests/TestCase/View/Helper/ProgressHelperTest.php**. In that file -we'll start with the following:: - - namespace App\Test\TestCase\View\Helper; - - use App\View\Helper\ProgressHelper; - use Cake\TestSuite\TestCase; - use Cake\View\View; - - class ProgressHelperTest extends TestCase - { - public function setUp(): void - { - } - - public function testBar(): void - { - } - } - -We'll flesh out this skeleton in a minute. We've added two methods to start -with. First is ``setUp()``. This method is called before every *test* method -in a test case class. Setup methods should initialize the objects needed for the -test, and do any configuration needed. In our setup method we'll add the -following:: - - public function setUp(): void - { - parent::setUp(); - $View = new View(); - $this->Progress = new ProgressHelper($View); - } - -Calling the parent method is important in test cases, as ``TestCase::setUp()`` -does a number things like backing up the values in -:php:class:`~Cake\\Core\\Configure` and, storing the paths in -:php:class:`~Cake\\Core\\App`. - -Next, we'll fill out the test method. We'll use some assertions to ensure that -our code creates the output we expect:: - - public function testBar(): void - { - $result = $this->Progress->bar(90); - $this->assertStringContainsString('width: 90%', $result); - $this->assertStringContainsString('progress-bar', $result); - - $result = $this->Progress->bar(33.3333333); - $this->assertStringContainsString('width: 33%', $result); - } - -The above test is a simple one but shows the potential benefit of using test -cases. We use ``assertStringContainsString()`` to ensure that our helper is returning a -string that contains the content we expect. If the result did not contain the -expected content the test would fail, and we would know that our code is -incorrect. - -By using test cases you can describe the relationship between a set of -known inputs and their expected output. This helps you be more confident of the -code you're writing as you can ensure that the code you wrote fulfills the -expectations and assertions your tests make. Additionally because tests are -code, they can be re-run whenever you make a change. This helps prevent -the creation of new bugs. - -.. note:: - - EventManager is refreshed for each test method. This means that when running - multiple tests at once, you will lose your event listeners that were - registered in config/bootstrap.php as the bootstrap is only executed once. - -.. _running-tests: - -Running Tests -============= - -Once you have PHPUnit installed and some test cases written, you'll want to run -the test cases very frequently. It's a good idea to run tests before committing -any changes to help ensure you haven't broken anything. - -By using ``phpunit`` you can run your application tests. To run your -application's tests you can simply run: - -.. code-block:: console - - vendor/bin/phpunit - - php phpunit.phar - -If you have cloned the `CakePHP source from GitHub `__ -and wish to run CakePHP's unit-tests don't forget to execute the following ``Composer`` -command prior to running ``phpunit`` so that any dependencies are installed: - -.. code-block:: console - - composer install - -From your application's root directory. To run tests for a plugin that is part -of your application source, first ``cd`` into the plugin directory, then use -``phpunit`` command that matches how you installed phpunit: - -.. code-block:: console - - cd plugins - - ../vendor/bin/phpunit - - php ../phpunit.phar - -To run tests on a standalone plugin, you should first install the project in -a separate directory and install its dependencies: - -.. code-block:: console - - git clone git://github.com/cakephp/debug_kit.git - cd debug_kit - php ~/composer.phar install - php ~/phpunit.phar - -Filtering Test Cases --------------------- - -When you have larger test cases, you will often want to run a subset of the test -methods when you are trying to work on a single failing case. With the -CLI runner you can use an option to filter test methods: - -.. code-block:: console - - $ phpunit --filter testSave tests/TestCase/Model/Table/ArticlesTableTest - -The filter parameter is used as a case-sensitive regular expression for -filtering which test methods to run. - -Generating Code Coverage ------------------------- - -You can generate code coverage reports from the command line using PHPUnit's -built-in code coverage tools. PHPUnit will generate a set of static HTML files -containing the coverage results. You can generate coverage for a test case by -doing the following: - -.. code-block:: console - - $ phpunit --coverage-html webroot/coverage tests/TestCase/Model/Table/ArticlesTableTest - -This will put the coverage results in your application's webroot directory. You -should be able to view the results by going to -``http://localhost/your_app/coverage``. - -You can also use ``phpdbg`` to generate coverage instead of xdebug. -``phpdbg`` is generally faster at generating coverage: - -.. code-block:: console - - $ phpdbg -qrr phpunit --coverage-html webroot/coverage tests/TestCase/Model/Table/ArticlesTableTest - -Combining Test Suites for Plugins ---------------------------------- - -Often times your application will be composed of several plugins. In these -situations it can be pretty tedious to run tests for each plugin. You can make -running tests for each of the plugins that compose your application by adding -additional ```` sections to your application's **phpunit.xml.dist** -file: - -.. code-block:: xml - - - - tests/TestCase/ - - - - - plugins/Forum/tests/TestCase/ - - - -Any additional test suites added to the ```` element will -automatically be run when you use ``phpunit``. - -If you are using ```` to use fixtures from plugins that you have -installed with composer, the plugin's ``composer.json`` file should add the -fixture namespace to the autoload section. Example:: - - "autoload-dev": { - "psr-4": { - "PluginName\\Test\\Fixture\\": "tests/Fixture/" - } - }, - -Test Case Lifecycle Callbacks -============================= - -Test cases have a number of lifecycle callbacks you can use when doing testing: - -* ``setUp`` is called before every test method. Should be used to create the - objects that are going to be tested, and initialize any data for the test. - Always remember to call ``parent::setUp()`` -* ``tearDown`` is called after every test method. Should be used to cleanup after - the test is complete. Always remember to call ``parent::tearDown()``. -* ``setupBeforeClass`` is called once before test methods in a case are started. - This method must be *static*. -* ``tearDownAfterClass`` is called once after test methods in a case are started. - This method must be *static*. - -.. _test-fixtures: - -Fixtures -======== - -When testing code that depends on models and the database, one can use -**fixtures** as a way to create initial state for your application's tests. -By using fixture data you can reduce repetitive setup steps in your tests. -Fixtures are well suited to data that is common or shared amongst many or all of -your tests. Data that is only needed in a subset of tests should be created in -tests as needed. - -CakePHP uses the connection named ``test`` in your **config/app.php** -configuration file. If this connection is not usable, an exception will be -raised and you will not be able to use database fixtures. - -CakePHP performs the following during the course of a test run: - -#. Creates tables for each of the fixtures needed. -#. Populates tables with data. -#. Runs test methods. -#. Empties the fixture tables. - -The schema for fixtures is created at the beginning of a test run via migrations -or a SQL dump file. - -Test Connections ----------------- - -By default CakePHP will alias each connection in your application. Each -connection defined in your application's bootstrap that does not start with -``test_`` will have a ``test_`` prefixed alias created. Aliasing connections -ensures, you don't accidentally use the wrong connection in test cases. -Connection aliasing is transparent to the rest of your application. For example -if you use the 'default' connection, instead you will get the ``test`` -connection in test cases. If you use the 'replica' connection, the test suite -will attempt to use 'test_replica'. - -.. _fixture-phpunit-configuration: - -PHPUnit Configuration ---------------------- - -Before you can use fixtures you should double check that your ``phpunit.xml`` -contains the fixture extension: - -.. code-block:: xml - - - - - - - -The extension is included in your application and plugins generated by ``bake`` -by default. - -.. _creating-test-database-schema: - -Creating Schema in Tests ------------------------------ - -You can generate test database schema either via CakePHP's migrations, loading -a SQL dump file or using another external schema management tool. You should -create your schema in your application's ``tests/bootstrap.php`` file. - -Creating Schema with Migrations -------------------------------- - -If you use CakePHP's `migrations plugin `_ to manage your -application's schema, you can reuse those migrations to generate your test -database schema as well:: - - // in tests/bootstrap.php - use Migrations\TestSuite\Migrator; - - $migrator = new Migrator(); - - // Simple setup for with no plugins - $migrator->run(); - - // Run migrations for a plugin - $migrator->run(['plugin' => 'Contacts']); - - // Run the Documents migrations on the test_docs connection. - $migrator->run(['plugin' => 'Documents', 'connection' => 'test_docs']); - -If you need to run multiple sets of migrations, those can be run as follows:: - - $migrator->runMany([ - // Run app migrations on test connection. - ['connection' => 'test'], - // Run Contacts migrations on test connection. - ['plugin' => 'Contacts'], - // Run Documents migrations on test_docs connection. - ['plugin' => 'Documents', 'connection' => 'test_docs'] - ]); - -Using ``runMany()`` will ensure that plugins that share a database don't drop -tables as each set of migrations is run. - -The migrations plugin will only run unapplied migrations, and will reset -migrations if your current migration head differs from the applied migrations. - -You can also configure how migrations should be run in tests in your datasources -configuration. See the :doc:`migrations docs ` for more information. - -Creating Schema with Abstract Schema ------------------------------------- - -For plugins that need to define schema in tests, but don't need or want to have -dependencies on migrations, you can define schema as a structured array of -tables. This format is not recommended for application development as it can be -time-consuming to maintain. - -Each table can define ``columns``, ``constraints``, and ``indexes``. -An example table would be:: - - return [ - 'articles' => [ - 'columns' => [ - 'id' => [ - 'type' => 'integer', - ], - 'author_id' => [ - 'type' => 'integer', - 'null' => true, - ], - 'title' => [ - 'type' => 'string', - 'null' => true, - ], - 'body' => 'text', - 'published' => [ - 'type' => 'string', - 'length' => 1, - 'default' => 'N', - ], - ], - 'constraints' => [ - 'primary' => [ - 'type' => 'primary', - 'columns' => [ - 'id', - ], - ], - ], - ], - // More tables - ]; - -The options available to ``columns``, ``indexes`` and ``constraints`` match the -attributes that are available in CakePHP's schema reflection APIs. Tables are -created incrementally and you must take care to ensure that tables are created -before foreign key references are made. Once you have created your schema file -you can load it in your ``tests/bootstrap.php`` with:: - - $loader = new SchemaLoader(); - $loader->loadInternalFile($pathToSchemaFile); - -Creating Schema with SQL Dump Files ------------------------------------ - -To load a SQL dump file you can use the following:: - - // in tests/bootstrap.php - use Cake\TestSuite\Fixture\SchemaLoader; - - // Load one or more SQL files. - (new SchemaLoader())->loadSqlFiles('path/to/schema.sql', 'test'); - -At the beginning of each test run ``SchemaLoader`` will drop all tables in the -connection and rebuild tables based on the provided schema file. - -Creating Fixtures ------------------ - -Fixtures defines the records that will be inserted into the test database at the -beginning of each test. Let's create our first fixture, that will be -used to test our own Article model. Create a file named **ArticlesFixture.php** -in your **tests/Fixture** directory, with the following content:: - - namespace App\Test\Fixture; - - use Cake\TestSuite\Fixture\TestFixture; - - class ArticlesFixture extends TestFixture - { - // Optional. Set this property to load fixtures to a different test datasource - public $connection = 'test'; - - public $records = [ - [ - 'title' => 'First Article', - 'body' => 'First Article Body', - 'published' => '1', - 'created' => '2007-03-18 10:39:23', - 'modified' => '2007-03-18 10:41:31' - ], - [ - 'title' => 'Second Article', - 'body' => 'Second Article Body', - 'published' => '1', - 'created' => '2007-03-18 10:41:23', - 'modified' => '2007-03-18 10:43:31' - ], - [ - 'title' => 'Third Article', - 'body' => 'Third Article Body', - 'published' => '1', - 'created' => '2007-03-18 10:43:23', - 'modified' => '2007-03-18 10:45:31' - ] - ]; - } - -.. note:: - - It is recommended to not manually add values to auto incremental columns, - as it interferes with the sequence generation in PostgreSQL and SQLServer. - -The ``$connection`` property defines the datasource of which the fixture will -use. If your application uses multiple datasources, you should make the -fixtures match the model's datasources but prefixed with ``test_``. -For example if your model uses the ``mydb`` datasource, your fixture should use -the ``test_mydb`` datasource. If the ``test_mydb`` connection doesn't exist, -your models will use the default ``test`` datasource. Fixture datasources must -be prefixed with ``test`` to reduce the possibility of accidentally truncating -all your application's data when running tests. - -We can define a set of records that will be populated after the fixture table is -created. The format is fairly straight forward, ``$records`` is an array of -records. Each item in ``$records`` should be a single row. Inside each row, -should be an associative array of the columns and values for the row. Just keep -in mind that each record in the ``$records`` array must have the same keys as -rows are bulk inserted. - -As you evolve your schema your fixture records may accumulate unused or -unsupported fields. You can enable ``strictFields`` on a fixture to have errors -raised when a record contains fields that are not defined in the schema:: - - namespace App\Test\Fixture; - - use Cake\TestSuite\Fixture\TestFixture; - - class ArticlesFixture extends TestFixture - { - protected $strictFields = true; - - // rest of fixture - } - -The ``strictFields`` mode can be useful in catching typos or when you want to -enforce stricter maintenance of test data. - -.. versionadded:: 5.2.0 - ``TestFixture::$strictFields`` was added. - - -Dynamic Data ------------- - -To use functions or other dynamic data in your fixture records you can define -your records in the fixture's ``init()`` method:: - - namespace App\Test\Fixture; - - use Cake\TestSuite\Fixture\TestFixture; - - class ArticlesFixture extends TestFixture - { - public function init(): void - { - $this->records = [ - [ - 'title' => 'First Article', - 'body' => 'First Article Body', - 'published' => '1', - 'created' => date('Y-m-d H:i:s'), - 'modified' => date('Y-m-d H:i:s'), - ], - ]; - parent::init(); - } - } - -.. note:: - When overriding ``init()`` remember to always call ``parent::init()``. - -Loading Fixtures in your Test Cases ------------------------------------ - -After you've created your fixtures, you'll want to use them in your test cases. -In each test case you should load the fixtures you will need. You should load a -fixture for every model that will have a query run against it. To load fixtures -you define the ``$fixtures`` property in your model:: - - class ArticlesTest extends TestCase - { - protected $fixtures = ['app.Articles', 'app.Comments']; - } - -As of 4.1.0 you can use ``getFixtures()`` to define your fixture list with -a method:: - - public function getFixtures(): array - { - return [ - 'app.Articles', - 'app.Comments', - ]; - } - -The above will load the Article and Comment fixtures from the application's -Fixture directory. You can also load fixtures from CakePHP core, or plugins:: - - class ArticlesTest extends TestCase - { - protected $fixtures = [ - 'plugin.DebugKit.Articles', - 'plugin.MyVendorName/MyPlugin.Messages', - 'core.Comments', - ]; - } - -Using the ``core`` prefix will load fixtures from CakePHP, and using a plugin -name as the prefix, will load the fixture from the named plugin. - -You can load fixtures in subdirectories. Using multiple directories can make it -easier to organize your fixtures if you have a larger application. To load -fixtures in subdirectories, simply include the subdirectory name in the fixture -name:: - - class ArticlesTest extends CakeTestCase - { - protected $fixtures = ['app.Blog/Articles', 'app.Blog/Comments']; - } - -In the above example, both fixtures would be loaded from -``tests/Fixture/Blog/``. - -You can also directly include fixtures by FQCN:: - - public function getFixtures(): array - { - return [ - UsersFixture::class, - ArticlesFixture::class, - ]; - } - - - -.. _fixture-state-management: - -Fixture State Managers ----------------------- - -By default CakePHP resets fixture state at the end of each test by truncating -all the tables in the database. This operation can become expensive as your -application grows. By using ``TransactionStrategy`` each test method will be run -inside a transaction that is rolled back at the end of the test. This can yield -improved performance but requires your tests not heavily rely on static fixture -data, as auto-increment values are not reset before each test. - -The fixture state management strategy can be defined within the test case:: - - use Cake\TestSuite\TestCase; - use Cake\TestSuite\Fixture\FixtureStrategyInterface; - use Cake\TestSuite\Fixture\TransactionStrategy; - - class ArticlesTableTest extends TestCase - { - /** - * Create the fixtures strategy used for this test case. - * You can use a base class/trait to change multiple classes. - */ - protected function getFixtureStrategy(): FixtureStrategyInterface - { - return new TransactionStrategy(); - } - } - -To switch out the general default strategy, use Configure key ``TestSuite.fixtureStrategy`` in your ``app.php``:: - - 'TestSuite' => [ - 'fixtureStrategy' => \Cake\TestSuite\Fixture\TransactionStrategy::class, - ], - - -The recommended strategy for medium and large applications is the ``TransactionStrategy``, as using rollbacks to undo changes from tests is simpler to maintain, and reduces the chances of cross-contamination and side-effects between tests. - -Fixture Factories ------------------ - -As your application grows, so does the number and the size of your test -fixtures. You might find it difficult to maintain them and to keep track of -their content. The `fixture factories plugin -`_ proposes an -alternative for large sized applications. - -The plugin uses the `test suite light plugin `_ -in order to truncate all dirty tables before each test. - -The following command will help you bake your factories:: - - bin/cake bake fixture_factory -h - -Once your factories are -`tuned `_, -you are ready to create test fixtures in no time. - -Unnecessary interaction with the database will slow down your tests as well as -your application. You can create test fixtures without persisting them which can -be useful for testing methods without DB interaction:: - - $article = ArticleFactory::make()->getEntity(); - -In order to persist:: - - $article = ArticleFactory::make()->persist(); - -The factories help creating associated fixtures too. -Assuming that articles belongs to many authors, we can now, for example, -create 5 articles each with 2 authors:: - - $articles = ArticleFactory::make(5)->with('Authors', 2)->getEntities(); - -Note that the fixture factories do not require any fixture creation or -declaration. Still, they are fully compatible with the fixtures that come with -cakephp. You will find additional insights and documentation `here -`_. - -Loading Routes in Tests -======================= - -If you are testing mailers, controller components or other classes that require -routes and resolving URLs, you will need to load routes. During -the ``setUp()`` of a class or during individual test methods you can use -``loadRoutes()`` to ensure your application routes are loaded:: - - public function setUp(): void - { - parent::setUp(); - $this->loadRoutes(); - } - -This method will build an instance of your ``Application`` and call the -``routes()`` method on it. If your ``Application`` class requires specialized -constructor parameters you can provide those to ``loadRoutes($constructorArgs)``. - -Creating Routes in Tests ------------------------- - -Sometimes it may be be necessary to dynamically add routes in tests, for example -when developing plugins, or applications that are extensible. - -Just like loading existing application routes, this can be done during ``setup()`` -of a test method, and/or in the individual test methods themselves:: - - use Cake\Routing\Route\DashedRoute; - use Cake\Routing\RouteBuilder; - use Cake\Routing\Router; - use Cake\TestSuite\TestCase; - - class PluginHelperTest extends TestCase - { - protected RouteBuilder $routeBuilder; - - public function setUp(): void - { - parent::setUp(); - - $this->routeBuilder = Router::createRouteBuilder('/'); - $this->routeBuilder->scope('/', function (RouteBuilder $routes) { - $routes->setRouteClass(DashedRoute::class); - $routes->get( - '/test/view/{id}', - ['controller' => 'Tests', 'action' => 'view'] - ); - // ... - }); - - // ... - } - } - -This will create a new route builder instance that will merge connected routes -into the same route collection used by all other route builder instances that -may already exist, or are yet to be created in the environment. - -Loading Plugins in Tests ------------------------- - -If your application would dynamically load plugins, you can use -``loadPlugins()`` to load one or more plugins during tests:: - - public function testMethodUsingPluginResources() - { - $this->loadPlugins(['Company/Cms']); - // Test logic that requires Company/Cms to be loaded. - } - -Testing Table Classes -===================== - -Let's say we already have our Articles Table class defined in -**src/Model/Table/ArticlesTable.php**, and it looks like:: - - namespace App\Model\Table; - - use Cake\ORM\Table; - use Cake\ORM\Query\SelectQuery; - - class ArticlesTable extends Table - { - public function findPublished(SelectQuery $query): SelectQuery - { - $query->where([ - $this->getAlias() . '.published' => 1 - ]); - - return $query; - } - } - -We now want to set up a test that will test this table class. Let's now create -a file named **ArticlesTableTest.php** in your **tests/TestCase/Model/Table** directory, -with the following contents:: - - namespace App\Test\TestCase\Model\Table; - - use App\Model\Table\ArticlesTable; - use Cake\TestSuite\TestCase; - - class ArticlesTableTest extends TestCase - { - protected $fixtures = ['app.Articles']; - } - -In our test cases' variable ``$fixtures`` we define the set of fixtures that -we'll use. You should remember to include all the fixtures that will have -queries run against them. - -Creating a Test Method ----------------------- - -Let's now add a method to test the function ``published()`` in the Articles -table. Edit the file **tests/TestCase/Model/Table/ArticlesTableTest.php** so it -now looks like this:: - - namespace App\Test\TestCase\Model\Table; - - use App\Model\Table\ArticlesTable; - use Cake\TestSuite\TestCase; - - class ArticlesTableTest extends TestCase - { - protected $fixtures = ['app.Articles']; - - public function setUp(): void - { - parent::setUp(); - $this->Articles = $this->getTableLocator()->get('Articles'); - } - - public function testFindPublished(): void - { - $query = $this->Articles->find('published')->select(['id', 'title']); - $this->assertInstanceOf('Cake\ORM\Query\SelectQuery', $query); - $result = $query->enableHydration(false)->toArray(); - $expected = [ - ['id' => 1, 'title' => 'First Article'], - ['id' => 2, 'title' => 'Second Article'], - ['id' => 3, 'title' => 'Third Article'] - ]; - - $this->assertEquals($expected, $result); - } - } - -You can see we have added a method called ``testFindPublished()``. We start by -creating an instance of our ``ArticlesTable`` class, and then run our -``find('published')`` method. In ``$expected`` we set what we expect should be -the proper result (that we know since we have defined which records are -initially populated to the article table.) We test that the result equals our -expectation by using the ``assertEquals()`` method. See the :ref:`running-tests` -section for more information on how to run your test case. - -Using the fixture factories, the test would now look like this:: - - namespace App\Test\TestCase\Model\Table; - - use App\Test\Factory\ArticleFactory; - use Cake\TestSuite\TestCase; - - class ArticlesTableTest extends TestCase - { - public function testFindPublished(): void - { - // Persist 3 published articles - $articles = ArticleFactory::make(['published' => 1], 3)->persist(); - // Persist 2 unpublished articles - ArticleFactory::make(['published' => 0], 2)->persist(); - - $result = ArticleFactory::find('published')->find('list')->toArray(); - - $expected = [ - $articles[0]->id => $articles[0]->title, - $articles[1]->id => $articles[1]->title, - $articles[2]->id => $articles[2]->title, - ]; - - $this->assertEquals($expected, $result); - } - } - -No fixtures need to be loaded. The 5 articles created will exist only in this test. The -static method ``::find()`` will query the database without using the table ``ArticlesTable`` -and it's events. - -Mocking Model Methods ---------------------- - -There will be times you'll want to mock methods on models when testing them. You -should use ``getMockForModel`` to create testing mocks of table classes. It -avoids issues with reflected properties that normal mocks have:: - - public function testSendingEmails(): void - { - $model = $this->getMockForModel('EmailVerification', ['send']); - $model->expects($this->once()) - ->method('send') - ->will($this->returnValue(true)); - - $model->verifyEmail('test@example.com'); - } - -In your ``tearDown()`` method be sure to remove the mock with:: - - $this->getTableLocator()->clear(); - -.. _integration-testing: - -Controller Integration Testing -============================== - -While you can test controller classes in a similar fashion to Helpers, Models, -and Components, CakePHP offers a specialized ``IntegrationTestTrait`` trait. -Using this trait in your controller test cases allows you to -test controllers from a high level. - -If you are unfamiliar with integration testing, it is a testing approach that -allows you to test multiple units in concert. The integration testing -features in CakePHP simulate an HTTP request being handled by your application. -For example, testing your controller will also exercise any components, models -and helpers that would be involved in handling a given request. This gives you a -more high level test of your application and all its working parts. - -Say you have a typical ArticlesController, and its corresponding model. The -controller code looks like:: - - namespace App\Controller; - - use App\Controller\AppController; - - class ArticlesController extends AppController - { - public function index($short = null) - { - if ($this->request->is('post')) { - $article = $this->Articles->newEntity($this->request->getData()); - if ($this->Articles->save($article)) { - // Redirect as per PRG pattern - return $this->redirect(['action' => 'index']); - } - } - if (!empty($short)) { - $result = $this->Articles->find('all', fields: ['id', 'title'])->all(); - } else { - $result = $this->Articles->find()->all(); - } - - $this->set([ - 'title' => 'Articles', - 'articles' => $result - ]); - } - } - -Create a file named **ArticlesControllerTest.php** in your -**tests/TestCase/Controller** directory and put the following inside:: - - namespace App\Test\TestCase\Controller; - - use Cake\TestSuite\IntegrationTestTrait; - use Cake\TestSuite\TestCase; - - class ArticlesControllerTest extends TestCase - { - use IntegrationTestTrait; - - protected $fixtures = ['app.Articles']; - - public function testIndex(): void - { - $this->get('/articles'); - - $this->assertResponseOk(); - // More asserts. - } - - public function testIndexQueryData(): void - { - $this->get('/articles?page=1'); - - $this->assertResponseOk(); - // More asserts. - } - - public function testIndexShort(): void - { - $this->get('/articles/index/short'); - - $this->assertResponseOk(); - $this->assertResponseContains('Articles'); - // More asserts. - } - - public function testIndexPostData(): void - { - $data = [ - 'user_id' => 1, - 'published' => 1, - 'slug' => 'new-article', - 'title' => 'New Article', - 'body' => 'New Body' - ]; - $this->post('/articles', $data); - - $this->assertResponseSuccess(); - $articles = $this->getTableLocator()->get('Articles'); - $query = $articles->find()->where(['title' => $data['title']]); - $this->assertEquals(1, $query->count()); - } - } - -This example shows a few of the request sending methods and a few of the -assertions that ``IntegrationTestTrait`` provides. Before you can do any -assertions you'll need to dispatch a request. You can use one of the following -methods to send a request: - -* ``get()`` Sends a GET request. -* ``post()`` Sends a POST request. -* ``put()`` Sends a PUT request. -* ``delete()`` Sends a DELETE request. -* ``patch()`` Sends a PATCH request. -* ``options()`` Sends an OPTIONS request. -* ``head()`` Sends a HEAD request. - -All of the methods except ``get()`` and ``delete()`` accept a second parameter -that allows you to send a request body. After dispatching a request you can use -the various assertions provided by ``IntegrationTestTrait`` or PHPUnit to -ensure your request had the correct side-effects. - -Setting up the Request ----------------------- - -The ``IntegrationTestTrait`` trait comes with a number of helpers to -to configure the requests you will send to your application under test:: - - // Set cookies - $this->cookie('name', 'Uncle Bob'); - - // Set session data - $this->session(['Auth.User.id' => 1]); - - // Configure headers and merge with the existing request - $this->configRequest([ - 'headers' => ['Accept' => 'application/json'] - ]); - - // Replace the existing request. Added in 5.1.0 - $this->replaceRequest([ - 'headers' => ['Accept' => 'application/json'] - ]); - - -The state set by these helper methods is reset in the ``tearDown()`` method. - -.. versionadded:: 5.1.0 - ``replaceRequest()`` was added. - -Testing Actions Protected by CsrfProtectionMiddleware or FormProtectionComponent --------------------------------------------------------------------------------- - -When testing actions protected by either ``CsrfProtectionMiddleware`` or ``FormProtectionComponent`` you -can enable automatic token generation to ensure your tests won't fail due to -token mismatches:: - - public function testAdd(): void - { - $this->enableCsrfToken(); - $this->enableSecurityToken(); - $this->post('/posts/add', ['title' => 'Exciting news!']); - } - -It is also important to enable debug in tests that use tokens to prevent the -``FormProtectionComponent`` from thinking the debug token is being used in a non-debug -environment. When testing with other methods like ``requireSecure()`` you -can use ``configRequest()`` to set the correct environment variables:: - - // Fake out SSL connections. - $this->configRequest([ - 'environment' => ['HTTPS' => 'on'] - ]); - -If your action requires unlocked fields you can declare them with -``setUnlockedFields()``:: - - $this->setUnlockedFields(['dynamic_field']); - -Integration Testing PSR-7 Middleware ------------------------------------- - -Integration testing can also be used to test your entire PSR-7 application and -:doc:`/controllers/middleware`. By default ``IntegrationTestTrait`` will -auto-detect the presence of an ``App\Application`` class and automatically -enable integration testing of your Application. - -You can customize the application class name used, and the constructor -arguments, by using the ``configApplication()`` method:: - - public function setUp(): void - { - $this->configApplication('App\App', [CONFIG]); - } - -You should also take care to try and use :ref:`application-bootstrap` to load -any plugins containing events/routes. Doing so will ensure that your -events/routes are connected for each test case. Alternatively if you wish to -load plugins manually in a test you can use the ``loadPlugins()`` method. - -Testing with Encrypted Cookies ------------------------------- - -If you use the :ref:`encrypted-cookie-middleware` in your -application, there are helper methods for setting encrypted cookies in your -test cases:: - - // Set a cookie using AES and the default key. - $this->cookieEncrypted('my_cookie', 'Some secret values'); - - // Assume this action modifies the cookie. - $this->get('/articles/index'); - - $this->assertCookieEncrypted('An updated value', 'my_cookie'); - -Testing Flash Messages ----------------------- - -If you want to assert the presence of flash messages in the session and not the -rendered HTML, you can use ``enableRetainFlashMessages()`` in your tests to -retain flash messages in the session so you can write assertions:: - - // Enable retention of flash messages instead of consuming them. - $this->enableRetainFlashMessages(); - $this->get('/articles/delete/9999'); - - $this->assertSession('That article does not exist', 'Flash.flash.0.message'); - - // Assert a flash message in the 'flash' key. - $this->assertFlashMessage('Article deleted', 'flash'); - - // Assert the second flash message, also in the 'flash' key. - $this->assertFlashMessageAt(1, 'Article really deleted'); - - // Assert a flash message in the 'auth' key at the first position - $this->assertFlashMessageAt(0, 'You are not allowed to enter this dungeon!', 'auth'); - - // Assert a flash messages uses the error element - $this->assertFlashElement('Flash/error'); - - // Assert the second flash message element - $this->assertFlashElementAt(1, 'Flash/error'); - -Testing a JSON Responding Controller ------------------------------------- - -JSON is a friendly and common format to use when building a web service. -Testing the endpoints of your web service is very simple with CakePHP. Let us -begin with a simple example controller that responds in JSON:: - - use Cake\View\JsonView; - - class MarkersController extends AppController - { - public function viewClasses(): array - { - return [JsonView::class]; - } - - public function view($id) - { - $marker = $this->Markers->get($id); - $this->set('marker', $marker); - $this->viewBuilder()->setOption('serialize', ['marker']); - } - } - -Now we create the file **tests/TestCase/Controller/MarkersControllerTest.php** -and make sure our web service is returning the proper response:: - - class MarkersControllerTest extends IntegrationTestCase - { - use IntegrationTestTrait; - - public function testGet(): void - { - $this->configRequest([ - 'headers' => ['Accept' => 'application/json'] - ]); - $this->get('/markers/view/1.json'); - - // Check that the response was a 200 - $this->assertResponseOk(); - - $expected = [ - ['id' => 1, 'lng' => 66, 'lat' => 45], - ]; - $expected = json_encode($expected, JSON_PRETTY_PRINT); - $this->assertEquals($expected, (string)$this->_response->getBody()); - } - } - -We use the ``JSON_PRETTY_PRINT`` option as CakePHP's built in JsonView will use -that option when ``debug`` is enabled. - -Testing with file uploads -------------------------- - -Simulating file uploads is straightforward when you use the default -":ref:`uploaded files as objects `" mode. You can simply -create instances that implement -`\\Psr\\Http\\Message\\UploadedFileInterface `__ -(the default implementation currently used by CakePHP is -``\Laminas\Diactoros\UploadedFile``), and pass them in your test request data. -In the CLI environment such objects will by default pass validation checks that -test whether the file was uploaded via HTTP. The same is not true for array style -data as found in ``$_FILES``, it would fail that check. - -In order to simulate exactly how the uploaded file objects would be present on -a regular request, you not only need to pass them in the request data, but you also -need to pass them to the test request configuration via the ``files`` option. It's -not technically necessary though unless your code accesses uploaded files via the -:php:meth:`Cake\\Http\\ServerRequest::getUploadedFile()` or -:php:meth:`Cake\\Http\\ServerRequest::getUploadedFiles()` methods. - -Let's assume articles have a teaser image, and a ``Articles hasMany Attachments`` -association, the form would look like something like this accordingly, where one -image file, and multiple attachments/files would be accepted:: - - Form->create($article, ['type' => 'file']) ?> - Form->control('title') ?> - Form->control('teaser_image', ['type' => 'file']) ?> - Form->control('attachments.0.attachment', ['type' => 'file']) ?> - Form->control('attachments.0.description']) ?> - Form->control('attachments.1.attachment', ['type' => 'file']) ?> - Form->control('attachments.1.description']) ?> - Form->button('Submit') ?> - Form->end() ?> - -The test that would simulate the corresponding request could look like this:: - - public function testAddWithUploads(): void - { - $teaserImage = new \Laminas\Diactoros\UploadedFile( - '/path/to/test/file.jpg', // stream or path to file representing the temp file - 12345, // the filesize in bytes - \UPLOAD_ERR_OK, // the upload/error status - 'teaser.jpg', // the filename as sent by the client - 'image/jpeg' // the mimetype as sent by the client - ); - - $textAttachment = new \Laminas\Diactoros\UploadedFile( - '/path/to/test/file.txt', - 12345, - \UPLOAD_ERR_OK, - 'attachment.txt', - 'text/plain' - ); - - $pdfAttachment = new \Laminas\Diactoros\UploadedFile( - '/path/to/test/file.pdf', - 12345, - \UPLOAD_ERR_OK, - 'attachment.pdf', - 'application/pdf' - ); - - // This is the data accessible via `$this->request->getUploadedFile()` - // and `$this->request->getUploadedFiles()`. - $this->configRequest([ - 'files' => [ - 'teaser_image' => $teaserImage, - 'attachments' => [ - 0 => [ - 'attachment' => $textAttachment, - ], - 1 => [ - 'attachment' => $pdfAttachment, - ], - ], - ], - ]); - - // This is the data accessible via `$this->request->getData()`. - $postData = [ - 'title' => 'New Article', - 'teaser_image' => $teaserImage, - 'attachments' => [ - 0 => [ - 'attachment' => $textAttachment, - 'description' => 'Text attachment', - ], - 1 => [ - 'attachment' => $pdfAttachment, - 'description' => 'PDF attachment', - ], - ], - ]; - $this->post('/articles/add', $postData); - - $this->assertResponseOk(); - $this->assertFlashMessage('The article was saved successfully'); - $this->assertFileExists('/path/to/uploads/teaser.jpg'); - $this->assertFileExists('/path/to/uploads/attachment.txt'); - $this->assertFileExists('/path/to/uploads/attachment.pdf'); - } - -.. tip:: - - If you configure the test request with files, then it *must* match the - structure of your POST data (but only include the uploaded file objects)! - -Likewise you can simulate `upload errors `_ -or otherwise invalid files that do not pass validation:: - - public function testAddWithInvalidUploads(): void - { - $missingTeaserImageUpload = new \Laminas\Diactoros\UploadedFile( - '', - 0, - \UPLOAD_ERR_NO_FILE, - '', - '' - ); - - $uploadFailureAttachment = new \Laminas\Diactoros\UploadedFile( - '/path/to/test/file.txt', - 1234567890, - \UPLOAD_ERR_INI_SIZE, - 'attachment.txt', - 'text/plain' - ); - - $invalidTypeAttachment = new \Laminas\Diactoros\UploadedFile( - '/path/to/test/file.exe', - 12345, - \UPLOAD_ERR_OK, - 'attachment.exe', - 'application/vnd.microsoft.portable-executable' - ); - - $this->configRequest([ - 'files' => [ - 'teaser_image' => $missingTeaserImageUpload, - 'attachments' => [ - 0 => [ - 'file' => $uploadFailureAttachment, - ], - 1 => [ - 'file' => $invalidTypeAttachment, - ], - ], - ], - ]); - - $postData = [ - 'title' => 'New Article', - 'teaser_image' => $missingTeaserImageUpload, - 'attachments' => [ - 0 => [ - 'file' => $uploadFailureAttachment, - 'description' => 'Upload failure attachment', - ], - 1 => [ - 'file' => $invalidTypeAttachment, - 'description' => 'Invalid type attachment', - ], - ], - ]; - $this->post('/articles/add', $postData); - - $this->assertResponseOk(); - $this->assertFlashMessage('The article could not be saved'); - $this->assertResponseContains('A teaser image is required'); - $this->assertResponseContains('Max allowed filesize exceeded'); - $this->assertResponseContains('Unsupported file type'); - $this->assertFileNotExists('/path/to/uploads/teaser.jpg'); - $this->assertFileNotExists('/path/to/uploads/attachment.txt'); - $this->assertFileNotExists('/path/to/uploads/attachment.exe'); - } - -Disabling Error Handling Middleware in Tests --------------------------------------------- - -When debugging tests that are failing because your application is encountering -errors it can be helpful to temporarily disable the error handling middleware to -allow the underlying error to bubble up. You can use -``disableErrorHandlerMiddleware()`` to do this:: - - public function testGetMissing(): void - { - $this->disableErrorHandlerMiddleware(); - $this->get('/markers/not-there'); - $this->assertResponseCode(404); - } - -In the above example, the test would fail and the underlying exception message -and stack trace would be displayed instead of the rendered error page being -checked. - -Assertion methods ------------------ - -The ``IntegrationTestTrait`` trait provides a number of assertion methods that -make testing responses much simpler. Some examples are:: - - // Check for a 2xx response code - $this->assertResponseOk(); - - // Check for a 2xx/3xx response code - $this->assertResponseSuccess(); - - // Check for a 4xx response code - $this->assertResponseError(); - - // Check for a 5xx response code - $this->assertResponseFailure(); - - // Check for a specific response code, for example, 200 - $this->assertResponseCode(200); - - // Check the Location header - $this->assertRedirect(['controller' => 'Articles', 'action' => 'index']); - - // Check that no Location header has been set - $this->assertNoRedirect(); - - // Check a part of the Location header - $this->assertRedirectContains('/articles/edit/'); - - // Assert location header does not contain - $this->assertRedirectNotContains('/articles/edit/'); - - // Assert not empty response content - $this->assertResponseNotEmpty(); - - // Assert empty response content - $this->assertResponseEmpty(); - - // Assert response content - $this->assertResponseEquals('Yeah!'); - - // Assert response content doesn't equal - $this->assertResponseNotEquals('No!'); - - // Assert partial response content - $this->assertResponseContains('You won!'); - $this->assertResponseNotContains('You lost!'); - - // Assert file sent back - $this->assertFileResponse('/absolute/path/to/file.ext'); - - // Assert layout - $this->assertLayout('default'); - - // Assert which template was rendered (if any) - $this->assertTemplate('index'); - - // Assert data in the session - $this->assertSession(1, 'Auth.User.id'); - - // Assert response header. - $this->assertHeader('Content-Type', 'application/json'); - $this->assertHeaderContains('Content-Type', 'html'); - - // Assert content-type header doesn't contain xml - $this->assertHeaderNotContains('Content-Type', 'xml'); - - // Assert view variables - $user = $this->viewVariable('user'); - $this->assertEquals('jose', $user->username); - - // Assert cookie values in the response - $this->assertCookie('1', 'thingid'); - - // Assert a cookie is or is not present - $this->assertCookieIsSet('remember_me'); - $this->assertCookieNotSet('remember_me'); - - // Check the content type - $this->assertContentType('application/json'); - -In addition to the above assertion methods, you can also use all of the -assertions in `TestSuite -`_ and those -found in `PHPUnit -`__. - -Comparing test results to a file --------------------------------- - -For some types of test, it may be easier to compare the result of a test to the -contents of a file - for example, when testing the rendered output of a view. -The ``StringCompareTrait`` adds a simple assert method for this purpose. - -Usage involves using the trait, setting the comparison base path and calling -``assertSameAsFile``:: - - use Cake\TestSuite\StringCompareTrait; - use Cake\TestSuite\TestCase; - - class SomeTest extends TestCase - { - use StringCompareTrait; - - public function setUp(): void - { - $this->_compareBasePath = APP . 'tests' . DS . 'comparisons' . DS; - parent::setUp(); - } - - public function testExample(): void - { - $result = ...; - $this->assertSameAsFile('example.php', $result); - } - } - -The above example will compare ``$result`` to the contents of the file -``APP/tests/comparisons/example.php``. - -A mechanism is provided to write/update test files, by setting the environment -variable ``UPDATE_TEST_COMPARISON_FILES``, which will create and/or update test -comparison files as they are referenced: - -.. code-block:: console - - phpunit - ... - FAILURES! - Tests: 6, Assertions: 7, Failures: 1 - - UPDATE_TEST_COMPARISON_FILES=1 phpunit - ... - OK (6 tests, 7 assertions) - - git status - ... - # Changes not staged for commit: - # (use "git add ..." to update what will be committed) - # (use "git checkout -- ..." to discard changes in working directory) - # - # modified: tests/comparisons/example.php - - -Console Integration Testing -=========================== - -See :ref:`console-integration-testing` for how to test console commands. - -Mocking Injected Dependencies -============================= - -See :ref:`mocking-services-in-tests` for how to replace services injected with -the dependency injection container in your integration tests. - -Mocking HTTP Client Responses -============================= - -See :ref:`httpclient-testing` to know how to create mock responses to external APIs. - -Testing Views -============= - -Generally most applications will not directly test their HTML code. Doing so is -often results in fragile, difficult to maintain test suites that are prone to -breaking. When writing functional tests using :php:class:`IntegrationTestTrait` -you can inspect the rendered view content by setting the ``return`` option to -'view'. While it is possible to test view content using ``IntegrationTestTrait``, -a more robust and maintainable integration/view testing can be accomplished -using tools like `Selenium webdriver `_. - -Testing Components -================== - -Let's pretend we have a component called PagematronComponent in our application. -This component helps us set the pagination limit value across all the -controllers that use it. Here is our example component located in -**src/Controller/Component/PagematronComponent.php**:: - - class PagematronComponent extends Component - { - public $controller = null; - - public function setController($controller) - { - $this->controller = $controller; - // Make sure the controller is using pagination - if (!isset($this->controller->paginate)) { - $this->controller->paginate = []; - } - } - - public function startup(EventInterface $event) - { - $this->setController($event->getSubject()); - } - - public function adjust($length = 'short'): void - { - switch ($length) { - case 'long': - $this->controller->paginate['limit'] = 100; - break; - case 'medium': - $this->controller->paginate['limit'] = 50; - break; - default: - $this->controller->paginate['limit'] = 20; - break; - } - } - } - -Now we can write tests to ensure our paginate ``limit`` parameter is being set -correctly by the ``adjust()`` method in our component. We create the file -**tests/TestCase/Controller/Component/PagematronComponentTest.php**:: - - namespace App\Test\TestCase\Controller\Component; - - use App\Controller\Component\PagematronComponent; - use Cake\Controller\Controller; - use Cake\Controller\ComponentRegistry; - use Cake\Event\Event; - use Cake\Http\ServerRequest; - use Cake\Http\Response; - use Cake\TestSuite\TestCase; - - class PagematronComponentTest extends TestCase - { - protected $component; - protected $controller; - - public function setUp(): void - { - parent::setUp(); - // Setup our component and provide it a basic controller. - // If your component relies on Application features, use AppController. - $request = new ServerRequest(); - $response = new Response(); - $this->controller = new Controller($request); - $registry = new ComponentRegistry($this->controller); - - $this->component = new PagematronComponent($registry); - $event = new Event('Controller.startup', $this->controller); - $this->component->startup($event); - } - - public function testAdjust(): void - { - // Test our adjust method with different parameter settings - $this->component->adjust(); - $this->assertEquals(20, $this->controller->paginate['limit']); - - $this->component->adjust('medium'); - $this->assertEquals(50, $this->controller->paginate['limit']); - - $this->component->adjust('long'); - $this->assertEquals(100, $this->controller->paginate['limit']); - } - - public function tearDown(): void - { - parent::tearDown(); - // Clean up after we're done - unset($this->component, $this->controller); - } - } - -Testing Helpers -=============== - -Since a decent amount of logic resides in Helper classes, it's -important to make sure those classes are covered by test cases. - -First we create an example helper to test. The ``CurrencyRendererHelper`` will -help us display currencies in our views and for simplicity only has one method -``usd()``:: - - // src/View/Helper/CurrencyRendererHelper.php - namespace App\View\Helper; - - use Cake\View\Helper; - - class CurrencyRendererHelper extends Helper - { - public function usd($amount): string - { - return 'USD ' . number_format($amount, 2, '.', ','); - } - } - -Here we set the decimal places to 2, decimal separator to dot, thousands -separator to comma, and prefix the formatted number with 'USD' string. - -Now we create our tests:: - - // tests/TestCase/View/Helper/CurrencyRendererHelperTest.php - - namespace App\Test\TestCase\View\Helper; - - use App\View\Helper\CurrencyRendererHelper; - use Cake\TestSuite\TestCase; - use Cake\View\View; - - class CurrencyRendererHelperTest extends TestCase - { - public $helper = null; - - // Here we instantiate our helper - public function setUp(): void - { - parent::setUp(); - $View = new View(); - $this->helper = new CurrencyRendererHelper($View); - } - - // Testing the usd() function - public function testUsd(): void - { - $this->assertEquals('USD 5.30', $this->helper->usd(5.30)); - - // We should always have 2 decimal digits - $this->assertEquals('USD 1.00', $this->helper->usd(1)); - $this->assertEquals('USD 2.05', $this->helper->usd(2.05)); - - // Testing the thousands separator - $this->assertEquals( - 'USD 12,000.70', - $this->helper->usd(12000.70) - ); - } - } - -Here, we call ``usd()`` with different parameters and tell the test suite to -check if the returned values are equal to what is expected. - -Save this and execute the test. You should see a green bar and messaging -indicating 1 pass and 4 assertions. - -When you are testing a Helper which uses other helpers, be sure to mock the -View clases ``loadHelpers`` method. - -.. _testing-events: - -Testing Events -============== - -The :doc:`/core-libraries/events` is a great way to decouple your application -code, but sometimes when testing, you tend to test the results of events in the -test cases that execute those events. This is an additional form of coupling -that can be removed by using ``assertEventFired`` and ``assertEventFiredWith`` -instead. - -Expanding on the Orders example, say we have the following tables:: - - class OrdersTable extends Table - { - public function place($order): bool - { - if ($this->save($order)) { - // moved cart removal to CartsTable - $event = new Event('Model.Order.afterPlace', $this, [ - 'order' => $order - ]); - $this->getEventManager()->dispatch($event); - - return true; - } - - return false; - } - } - - class CartsTable extends Table - { - public function initialize() - { - // Models don't share the same event manager instance, - // so we need to use the global instance to listen to - // events from other models - \Cake\Event\EventManager::instance()->on( - 'Model.Order.afterPlace', - callable: [$this, 'removeFromCart'] - ); - } - - public function removeFromCart(EventInterface $event): void - { - $order = $event->getData('order'); - $this->delete($order->cart_id); - } - } - -.. note:: - To assert that events are fired, you must first enable - :ref:`tracking-events` on the event manager you wish to assert against. - -To test the ``OrdersTable`` above, we enable tracking in ``setUp()`` then assert -that the event was fired, and assert that the ``$order`` entity was passed in -the event data:: - - namespace App\Test\TestCase\Model\Table; - - use App\Model\Table\OrdersTable; - use Cake\Event\EventList; - use Cake\TestSuite\TestCase; - - class OrdersTableTest extends TestCase - { - protected $fixtures = ['app.Orders']; - - public function setUp(): void - { - parent::setUp(); - $this->Orders = $this->getTableLocator()->get('Orders'); - // enable event tracking - $this->Orders->getEventManager()->setEventList(new EventList()); - } - - public function testPlace(): void - { - $order = new Order([ - 'user_id' => 1, - 'item' => 'Cake', - 'quantity' => 42, - ]); - - $this->assertTrue($this->Orders->place($order)); - - $this->assertEventFired('Model.Order.afterPlace', $this->Orders->getEventManager()); - $this->assertEventFiredWith('Model.Order.afterPlace', 'order', $order, $this->Orders->getEventManager()); - } - } - -By default, the global ``EventManager`` is used for assertions, so testing -global events does not require passing the event manager:: - - $this->assertEventFired('My.Global.Event'); - $this->assertEventFiredWith('My.Global.Event', 'user', 1); - -Testing Email -============= - -See :ref:`email-testing` for information on testing email. - -Testing Logging -=============== - -See :ref:`log-testing` for information on testing log messages. - -Creating Test Suites -==================== - -If you want several of your tests to run at the same time, you can create a test -suite. A test suite is composed of several test cases. You can either create -test suites in your application's **phpunit.xml** file. A simple example -would be: - -.. code-block:: xml - - - - src/Model - src/Service/UserServiceTest.php - src/Model/Cloud/ImagesTest.php - - - -Creating Tests for Plugins -========================== - -Tests for plugins are created in their own directory inside the plugins -folder. :: - - /src - /plugins - /Blog - /tests - /TestCase - /Fixture - -They work just like normal tests but you have to remember to use the naming -conventions for plugins when importing classes. This is an example of a testcase -for the ``BlogPost`` model from the plugins chapter of this manual. A difference -from other tests is in the first line where 'Blog.BlogPost' is imported. You -also need to prefix your plugin fixtures with ``plugin.Blog.BlogPosts``:: - - namespace Blog\Test\TestCase\Model\Table; - - use Blog\Model\Table\BlogPostsTable; - use Cake\TestSuite\TestCase; - - class BlogPostsTableTest extends TestCase - { - // Plugin fixtures located in /plugins/Blog/tests/Fixture/ - protected $fixtures = ['plugin.Blog.BlogPosts']; - - public function testSomething(): void - { - // Test something. - } - } - -If you want to use plugin fixtures in the app tests you can -reference them using ``plugin.pluginName.fixtureName`` syntax in the -``$fixtures`` array. Additionally if you use vendor plugin name or fixture -directories you can use the following: ``plugin.vendorName/pluginName.folderName/fixtureName``. - -Before you can use fixtures you should ensure you have the :ref:`fixture -listener ` configured in your ``phpunit.xml`` -file. You should also ensure that your fixtures are loadable. Ensure the -following is present in your **composer.json** file:: - - "autoload-dev": { - "psr-4": { - "MyPlugin\\Test\\": "plugins/MyPlugin/tests/" - } - } - -.. note:: - - Remember to run ``composer.phar dumpautoload`` when adding new autoload - mappings. - -Generating Tests with Bake -========================== - -If you use :doc:`bake ` to -generate scaffolding, it will also generate test stubs. If you need to -re-generate test case skeletons, or if you want to generate test skeletons for -code you wrote, you can use ``bake``: - -.. code-block:: console - - bin/cake bake test - -```` should be one of: - -#. Entity -#. Table -#. Controller -#. Component -#. Behavior -#. Helper -#. Shell -#. Task -#. ShellHelper -#. Cell -#. Form -#. Mailer -#. Command - -While ```` should be the name of the object you want to bake a test -skeleton for. - -.. meta:: - :title lang=en: Testing - :keywords lang=en: phpunit,test database,database configuration,database setup,database test,public test,test framework,running one,test setup,de facto standard,pear,runners,array,databases,cakephp,php,integration diff --git a/en/elasticsearch.rst b/en/elasticsearch.rst deleted file mode 100644 index b39bf8cb8b..0000000000 --- a/en/elasticsearch.rst +++ /dev/null @@ -1,4 +0,0 @@ -ElasticSearch -############# - -This page has `moved `__. diff --git a/en/epub-contents.rst b/en/epub-contents.rst deleted file mode 100644 index 823b29183d..0000000000 --- a/en/epub-contents.rst +++ /dev/null @@ -1,64 +0,0 @@ -:orphan: - -Contents -######## - -.. toctree:: - :maxdepth: 3 - - intro - quickstart - appendices/migration-guides - tutorials-and-examples - contributing - - installation - development/configuration - development/routing - controllers/request-response - controllers - views - orm - - core-libraries/caching - bake - console-commands - development/debugging - deployment - core-libraries/email - development/errors - core-libraries/events - core-libraries/internationalization-and-localization - core-libraries/logging - core-libraries/form - controllers/pagination - plugins - development/rest - security - development/sessions - development/testing - core-libraries/validation - - core-libraries/app - core-libraries/collections - core-libraries/hash - core-libraries/httpclient - core-libraries/inflector - core-libraries/number - core-libraries/registry-objects - core-libraries/text - core-libraries/time - core-libraries/xml - - core-libraries/global-constants-and-functions - chronos - debug-kit - migrations - elasticsearch - appendices - -.. todolist:: - -.. meta:: - :title lang=en: Contents - :keywords lang=en: core libraries,ref search,commands,deployment,appendices,glossary,models diff --git a/en/index.rst b/en/index.rst deleted file mode 100644 index 093bbf9e9e..0000000000 --- a/en/index.rst +++ /dev/null @@ -1,57 +0,0 @@ -Welcome -####### - -CakePHP 5 is a web development framework running on PHP |phpversion| (min. PHP -|minphpversion|). Read :doc:`CakePHP at a Glance ` to get an -introduction to the fundamentals of CakePHP. - -The CakePHP book is an openly developed and community editable documentation -project. Notice the pencil icon button fixated against the right wall; it will -direct you to the GitHub online editor of the active page, allowing you to -contribute any additions, deletions, or corrections to the documentation. - -.. container:: offline-download - - **Read the Book Anywhere** - - .. image:: /_static/img/read-the-book.jpg - - Enjoy the CakePHP book almost anywhere. Available as both a PDF and - EPUB, you can now read it on more devices, as well as offline. - - - `PDF <../_downloads/en/CakePHPBook.pdf>`_ - - `EPUB <../_downloads/en/CakePHP.epub>`_ - - `Original Source `_ - -Getting Help -============ - -If you're stuck, there are a number of places :doc:`you can get help -`. - -First Steps -=========== - -Learning a new framework can be intimidating and exciting at the same time. To -help you along, we have created a cookbook packed with examples and recipes to -get the common tasks completed. If you are new, you should start off with the -:doc:`/quickstart` as it will give you a quick tour of what -CakePHP has to offer and how it works. - -After you've finished the Quickstart tutorial, you can brush up on the key -elements in a CakePHP application: - -* The :ref:`CakePHP request cycle ` -* The :doc:`conventions ` that CakePHP - uses. -* :doc:`Controllers ` handle requests and co-ordinate your models - and the responses your application creates. -* :doc:`Views ` are the presentation layer in your application. They - give you powerful tools to create HTML, JSON and the other outputs your - application needs. -* :doc:`Models ` are the key ingredient in any application. They handle - validation, and domain logic within your application. - -.. meta:: - :title lang=en: .. CakePHP book documentation master file, created by - :keywords lang=en: doc models,documentation master,presentation layer,documentation project,quickstart,original source,sphinx,liking,book,validity,conventions,validation,cakephp,accuracy,storage and retrieval,heart,blog,project hope diff --git a/en/installation.rst b/en/installation.rst deleted file mode 100644 index d7cdf766dd..0000000000 --- a/en/installation.rst +++ /dev/null @@ -1,669 +0,0 @@ -Installation -############ - -CakePHP has a few system requirements: - -- HTTP Server. For example: Apache. Having mod\_rewrite is preferred, but - by no means required. You can also use nginx, or Microsoft IIS if you prefer. -- Minimum PHP |minphpversion| (|phpversion| supported). -- mbstring PHP extension -- intl PHP extension -- SimpleXML PHP extension -- PDO PHP extension - -.. note:: - - In XAMPP, intl extension is included but you have to uncomment - ``extension=php_intl.dll`` (or ``extension=intl``) in **php.ini** and restart the server through - the XAMPP Control Panel. - - In WAMP, the intl extension is "activated" by default but not working. - To make it work you have to go to php folder (by default) - **C:\\wamp\\bin\\php\\php{version}**, copy all the files that looks like - **icu*.dll** and paste them into the apache bin directory - **C:\\wamp\\bin\\apache\\apache{version}\\bin**. Then restart all services - and it should be OK. - -While a database engine isn't required, we imagine that most applications will -utilize one. CakePHP supports a variety of database storage engines: - -- MySQL (5.7 or higher) -- MariaDB (10.1 or higher) -- PostgreSQL (9.6 or higher) -- Microsoft SQL Server (2012 or higher) -- SQLite 3 - -The Oracle database is supported through the -`Driver for Oracle Database `_ -community plugin. - -.. note:: - - All built-in drivers require PDO. You should make sure you have the correct - PDO extensions installed. - -Installing CakePHP -================== - -Before starting you should make sure that your PHP version is up to date: - -.. code-block:: console - - php -v - -You should have PHP |minphpversion| (CLI) or higher. -Your webserver's PHP version must also be of |minphpversion| or higher, and should be -the same version your command line interface (CLI) uses. - -Installing Composer -------------------- - -CakePHP uses `Composer `_, a dependency management tool, -as the officially supported method for installation. - -- Installing Composer on Linux and macOS - - #. Run the installer script as described in the - `official Composer documentation `_ - and follow the instructions to install Composer. - #. Execute the following command to move the composer.phar to a directory - that is in your path:: - - mv composer.phar /usr/local/bin/composer - -- Installing Composer on Windows - - For Windows systems, you can download Composer's Windows installer - `here `__. Further - instructions for Composer's Windows installer can be found within the - README `here `__. - -Create a CakePHP Project ------------------------- - -You can create a new CakePHP application using composer's ``create-project`` -command: - -.. code-block:: console - - composer create-project --prefer-dist cakephp/app:~5.0 my_app_name - -Once Composer finishes downloading the application skeleton and the core CakePHP -library, you should have a functioning CakePHP application installed via -Composer. Be sure to keep the composer.json and composer.lock files with the -rest of your source code. - -You can now visit the path to where you installed your CakePHP application and -see the default home page. To change the content of this page, edit -**templates/Pages/home.php**. - -Although composer is the recommended installation method, there are -pre-installed downloads available on -`Github `__. -Those downloads contain the app skeleton with all vendor packages installed. -Also it includes the ``composer.phar`` so you have everything you need for -further use. - -Keeping Up To Date with the Latest CakePHP Changes --------------------------------------------------- - -By default this is what your application **composer.json** looks like:: - - "require": { - "cakephp/cakephp": "5.0.*" - } - -Each time you run ``php composer.phar update`` you will receive patch -releases for this minor version. You can instead change this to ``^5.0`` to -also receive the latest stable minor releases of the ``5.x`` branch. - -Installation using DDEV ------------------------ - -Another quick way to install CakePHP is via `DDEV `_. -It is an open source tool for launching local web development environments. - -If you want to configure a new project, you just need:: - - mkdir my-cakephp-app - cd my-cakephp-app - ddev config --project-type=cakephp --docroot=webroot - ddev composer create --prefer-dist cakephp/app:~5.0 - ddev launch - -If you have an existing project:: - - git clone - cd - ddev config --project-type=cakephp --docroot=webroot - ddev composer install - ddev launch - -Please check `DDEV Docs `_ for details on how to install / update DDEV. - -.. note:: - - IMPORTANT: This is not a deployment script. It is aimed to help developers - to set up a development environment quickly. It is not intended for - production environments. - -Permissions -=========== - -CakePHP uses the **tmp** directory for a number of different operations. -Model descriptions, cached views, and session information are a few -examples. The **logs** directory is used to write log files by the default -``FileLog`` engine. - -As such, make sure the directories **logs**, **tmp** and all its subdirectories -in your CakePHP installation are writable by the web server user. Composer's -installation process makes **tmp** and its subfolders globally writeable to get -things up and running quickly but you can update the permissions for better -security and keep them writable only for the web server user. - -One common issue is that **logs** and **tmp** directories and subdirectories -must be writable both by the web server and the command line user. On a UNIX -system, if your web server user is different from your command line user, you -can run the following commands from your application directory just once in your -project to ensure that permissions will be setup properly: - -.. code-block:: console - - HTTPDUSER=`ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\ -f1` - setfacl -R -m u:${HTTPDUSER}:rwx tmp - setfacl -R -d -m u:${HTTPDUSER}:rwx tmp - setfacl -R -m u:${HTTPDUSER}:rwx logs - setfacl -R -d -m u:${HTTPDUSER}:rwx logs - -In order to use the CakePHP console tools, you need to ensure that -``bin/cake`` file is executable. On \*nix or macOS, you can -execute: - -.. code-block:: console - - chmod +x bin/cake - -On Windows, the **.bat** file should be executable already. If you are using -a Vagrant, or any other virtualized environment, any shared directories need to -be shared with execute permissions (Please refer to your virtualized -environment's documentation on how to do this). - -If, for whatever reason, you cannot change the permissions of the ``bin/cake`` -file, you can run the CakePHP console with: - -.. code-block:: console - - php bin/cake.php - -Development Server -================== - -A development installation is the fastest way to setup CakePHP. In this -example, we use CakePHP's console to run PHP's built-in web server which -will make your application available at **http://host:port**. From the app -directory, execute: - -.. code-block:: console - - bin/cake server - -By default, without any arguments provided, this will serve your application at -**http://localhost:8765/**. - -If there is conflict with **localhost** or port 8765, you can tell -the CakePHP console to run the web server on a specific host and/or port -utilizing the following arguments: - -.. code-block:: console - - bin/cake server -H 192.168.13.37 -p 5673 - -This will serve your application at **http://192.168.13.37:5673/**. - -That's it! Your CakePHP application is up and running without having to -configure a web server. - -.. note:: - - Try ``bin/cake server -H 0.0.0.0`` if the server is unreachable from other hosts. - -.. warning:: - - The development server should *never* be used in a production environment. - It is only intended as a basic development server. - -If you'd prefer to use a real web server, you should be able to move your CakePHP -install (including the hidden files) inside your web server's document root. You -should then be able to point your web-browser at the directory you moved the -files into and see your application in action. - -Production -========== - -A production installation is a more flexible way to setup CakePHP. Using this -method allows an entire domain to act as a single CakePHP application. This -example will help you install CakePHP anywhere on your filesystem and make it -available at http://www.example.com. Note that this installation may require the -rights to change the ``DocumentRoot`` on Apache webservers. - -After installing your application using one of the methods above into the -directory of your choosing - we'll assume you chose /cake_install - your -production setup will look like this on the file system:: - - cake_install/ - bin/ - config/ - logs/ - plugins/ - resources/ - src/ - templates/ - tests/ - tmp/ - vendor/ - webroot/ (this directory is set as DocumentRoot) - .gitignore - .htaccess - composer.json - index.php - phpunit.xml.dist - README.md - -Developers using Apache should set the ``DocumentRoot`` directive for the domain -to: - -.. code-block:: apacheconf - - DocumentRoot /cake_install/webroot - -If your web server is configured correctly, you should now find your CakePHP -application accessible at http://www.example.com. - -Fire It Up -========== - -Alright, let's see CakePHP in action. Depending on which setup you used, you -should point your browser to http://example.com/ or http://localhost:8765/. At -this point, you'll be presented with CakePHP's default home, and a message that -tells you the status of your current database connection. - -Congratulations! You are ready to :doc:`create your first CakePHP application -`. - -.. _url-rewriting: - -URL Rewriting -============= - -Apache ------- - -While CakePHP is built to work with mod\_rewrite out of the box–and usually -does–we've noticed that a few users struggle with getting everything to play -nicely on their systems. - -Here are a few things you might try to get it running correctly. First look at -your httpd.conf. (Make sure you are editing the system httpd.conf rather than a -user- or site-specific httpd.conf.) - -These files can vary between different distributions and Apache versions. You -may also take a look at https://cwiki.apache.org/confluence/display/httpd/DistrosDefaultLayout for -further information. - -#. Make sure that an .htaccess override is allowed and that AllowOverride is set - to All for the correct DocumentRoot. You should see something similar to: - - .. code-block:: apacheconf - - # Each directory to which Apache has access can be configured with respect - # to which services and features are allowed and/or disabled in that - # directory (and its subdirectories). - # - # First, we configure the "default" to be a very restrictive set of - # features. - - Options FollowSymLinks - AllowOverride All - # Order deny,allow - # Deny from all - - -#. Make sure you are loading mod\_rewrite correctly. You should see something - like: - - .. code-block:: apacheconf - - LoadModule rewrite_module libexec/apache2/mod_rewrite.so - - In many systems these will be commented out by default, so you may just need - to remove the leading # symbols. - - After you make changes, restart Apache to make sure the settings are active. - - Verify that your .htaccess files are actually in the right directories. Some - operating systems treat files that start with '.' as hidden and therefore - won't copy them. - -#. Make sure your copy of CakePHP comes from the downloads section of the site - or our Git repository, and has been unpacked correctly, by checking for - .htaccess files. - - CakePHP app directory (will be copied to the top directory of your - application by bake): - - .. code-block:: apacheconf - - - RewriteEngine on - RewriteRule ^$ webroot/ [L] - RewriteRule (.*) webroot/$1 [L] - - - CakePHP webroot directory (will be copied to your application's web root by - bake): - - .. code-block:: apacheconf - - - RewriteEngine On - RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule ^ index.php [L] - - - If your CakePHP site still has problems with mod\_rewrite, you might want to - try modifying settings for Virtual Hosts. On Ubuntu, edit the file - **/etc/apache2/sites-available/default** (location is - distribution-dependent). In this file, ensure that ``AllowOverride None`` is - changed to ``AllowOverride All``, so you have: - - .. code-block:: apacheconf - - - Options FollowSymLinks - AllowOverride All - - - Options FollowSymLinks - AllowOverride All - Order Allow,Deny - Allow from all - - - On macOS, another solution is to use the tool - `virtualhostx `_ to make a Virtual - Host to point to your folder. - - For many hosting services (GoDaddy, 1and1), your web server is being - served from a user directory that already uses mod\_rewrite. If you are - installing CakePHP into a user directory - (http://example.com/~username/cakephp/), or any other URL structure that - already utilizes mod\_rewrite, you'll need to add RewriteBase statements to - the .htaccess files CakePHP uses (.htaccess, webroot/.htaccess). - - This can be added to the same section with the RewriteEngine directive, so - for example, your webroot .htaccess file would look like: - - .. code-block:: apacheconf - - - RewriteEngine On - RewriteBase /path/to/app - RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule ^ index.php [L] - - - The details of those changes will depend on your setup, and can include - additional things that are not related to CakePHP. Please refer to Apache's - online documentation for more information. - -#. (Optional) To improve production setup, you should prevent invalid assets - from being parsed by CakePHP. Modify your webroot .htaccess to something - like: - - .. code-block:: apacheconf - - - RewriteEngine On - RewriteBase /path/to/app/ - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_URI} !^/(webroot/)?(img|css|js)/(.*)$ - RewriteRule ^ index.php [L] - - - The above will prevent incorrect assets from being sent to index.php - and instead display your web server's 404 page. - - Additionally you can create a matching HTML 404 page, or use the default - built-in CakePHP 404 by adding an ``ErrorDocument`` directive: - - .. code-block:: apacheconf - - ErrorDocument 404 /404-not-found - -nginx ------ - -nginx does not make use of .htaccess files like Apache, so it is necessary to -create those rewritten URLs in the site-available configuration. This is usually -found in ``/etc/nginx/sites-available/your_virtual_host_conf_file``. Depending -on your setup, you will have to modify this, but at the very least, you will -need PHP running as a FastCGI instance. -The following configuration redirects the request to ``webroot/index.php``: - -.. code-block:: nginx - - location / { - try_files $uri $uri/ /index.php?$args; - } - -A sample of the server directive is as follows: - -.. code-block:: nginx - - server { - listen 80; - listen [::]:80; - server_name www.example.com; - return 301 http://example.com$request_uri; - } - - server { - listen 80; - listen [::]:80; - server_name example.com; - - root /var/www/example.com/public/webroot; - index index.php; - - access_log /var/www/example.com/log/access.log; - error_log /var/www/example.com/log/error.log; - - location / { - try_files $uri $uri/ /index.php?$args; - } - - location ~ \.php$ { - try_files $uri =404; - include fastcgi_params; - fastcgi_pass 127.0.0.1:9000; - fastcgi_index index.php; - fastcgi_intercept_errors on; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - } - } - -.. note:: - Recent configurations of PHP-FPM are set to listen to the unix php-fpm - socket instead of TCP port 9000 on address 127.0.0.1. If you get 502 bad - gateway errors from the above configuration, try update ``fastcgi_pass`` to - use the unix socket path (eg: fastcgi_pass - unix:/var/run/php/php7.1-fpm.sock;) instead of the TCP port. - -NGINX Unit ----------- - -`NGINX Unit `_ is dynamically configurable in runtime; -the following configuration relies on ``webroot/index.php``, also serving other -``.php`` scripts if present via ``cakephp_direct``: - -.. code-block:: json - - { - "listeners": { - "*:80": { - "pass": "routes/cakephp" - } - }, - - "routes": { - "cakephp": [ - { - "match": { - "uri": [ - "*.php", - "*.php/*" - ] - }, - - "action": { - "pass": "applications/cakephp_direct" - } - }, - { - "action": { - "share": "/path/to/cakephp/webroot/", - "fallback": { - "pass": "applications/cakephp_index" - } - } - } - ] - }, - - "applications": { - "cakephp_direct": { - "type": "php", - "root": "/path/to/cakephp/webroot/", - "user": "www-data" - }, - - "cakephp_index": { - "type": "php", - "root": "/path/to/cakephp/webroot/", - "user": "www-data", - "script": "index.php" - } - } - } - -To enable this config (assuming it's saved as ``cakephp.json``): - -.. code-block:: console - - # curl -X PUT --data-binary @cakephp.json --unix-socket \ - /path/to/control.unit.sock http://localhost/config - -IIS7 (Windows hosts) --------------------- - -IIS7 does not natively support .htaccess files. While there are -add-ons that can add this support, you can also import htaccess -rules into IIS to use CakePHP's native rewrites. To do this, follow -these steps: - -#. Use `Microsoft's Web Platform Installer `_ - to install the URL `Rewrite Module 2.0 `_ - or download it directly (`32-bit `_ / - `64-bit `_). -#. Create a new file called web.config in your CakePHP root folder. -#. Using Notepad or any XML-safe editor, copy the following - code into your new web.config file: - -.. code-block:: xml - - - - - - - - - - - - - - - - - - - - - - - -Once the web.config file is created with the correct IIS-friendly rewrite rules, -CakePHP's links, CSS, JavaScript, and rerouting should work correctly. - -Lighttpd --------- -Lighttpd does not make use of **.htaccess** files like Apache, so it is -necessary to add a ``url.rewrite-once`` configuration in **conf/lighttpd.conf**. -Ensure the following is present in your lighthttpd configuration: - -.. code-block:: php - - server.modules += ( - "mod_alias", - "mod_cgi", - "mod_rewrite" - ) - - # Directory Alias - alias.url = ( "/TestCake" => "C:/Users/Nicola/Documents/TestCake" ) - - # CGI Php - cgi.assign = ( ".php" => "c:/php/php-cgi.exe" ) - - # Rewrite Cake Php (on /TestCake path) - url.rewrite-once = ( - "^/TestCake/(css|files|img|js|stats)/(.*)$" => "/TestCake/webroot/$1/$2", - "^/TestCake/(.*)$" => "/TestCake/webroot/index.php/$1" - ) - -The above lines include PHP CGI configuration and example application -configuration for an application on the ``/TestCake`` path. - -I Can't Use URL Rewriting -------------------------- - -If you don't want or can't get mod\_rewrite (or some other compatible module) -running on your server, you will need to use CakePHP's built in pretty URLs. -In **config/app.php**, uncomment the line that looks like:: - - 'App' => [ - // ... - // 'baseUrl' => env('SCRIPT_NAME'), - ] - -Also remove these .htaccess files:: - - /.htaccess - webroot/.htaccess - -This will make your URLs look like -www.example.com/index.php/controllername/actionname/param rather than -www.example.com/controllername/actionname/param. - -.. _GitHub: https://github.com/cakephp/cakephp -.. _Composer: https://getcomposer.org - -.. meta:: - :title lang=en: Installation - :keywords lang=en: apache mod rewrite,microsoft sql server,tar bz2,tmp directory,database storage,archive copy,tar gz,source application,current releases,web servers,microsoft iis,copyright notices,database engine,bug fixes,lighthttpd,repository,enhancements,source code,cakephp,incorporate diff --git a/en/intro.rst b/en/intro.rst deleted file mode 100644 index 25291def5e..0000000000 --- a/en/intro.rst +++ /dev/null @@ -1,175 +0,0 @@ -CakePHP at a Glance -################### - -CakePHP is designed to make common web-development tasks simple, and easy. By -providing an all-in-one toolbox to get you started the various parts of CakePHP -work well together or separately. - -The goal of this overview is to introduce the general concepts in CakePHP, and -give you a quick overview of how those concepts are implemented in CakePHP. If -you are itching to get started on a project, you can :doc:`start with the -tutorial `, or :doc:`dive into the docs -`. - -Conventions Over Configuration -============================== - -CakePHP provides a basic organizational structure that covers class names, -filenames, database table names, and other conventions. While the conventions -take some time to learn, by following the conventions CakePHP provides you can -avoid needless configuration and make a uniform application structure that makes -working with various projects simple. The :doc:`conventions chapter -` covers the various conventions that CakePHP uses. - -The Model Layer -=============== - -The Model layer represents the part of your application that implements the -business logic. It is responsible for retrieving data and converting it into the -primary meaningful concepts in your application. This includes processing, -validating, associating or other tasks related to handling data. - -In the case of a social network, the Model layer would take care of -tasks such as saving the user data, saving friends' associations, storing -and retrieving user photos, finding suggestions for new friends, etc. -The model objects can be thought of as "Friend", "User", "Comment", or -"Photo". If we wanted to load some data from our ``users`` table we could do:: - - use Cake\ORM\Locator\LocatorAwareTrait; - - $users = $this->fetchTable('Users'); - $resultset = $users->find()->all(); - foreach ($resultset as $row) { - echo $row->username; - } - -You may notice that we didn't have to write any code before we could start -working with our data. By using conventions, CakePHP will use standard classes -for table and entity classes that have not yet been defined. - -If we wanted to make a new user and save it (with validation) we would do -something like:: - - use Cake\ORM\Locator\LocatorAwareTrait; - - $users = $this->fetchTable('Users'); - $user = $users->newEntity(['email' => 'mark@example.com']); - $users->save($user); - -The View Layer -============== - -The View layer renders a presentation of modeled data. Being separate from the -Model objects, it is responsible for using the information it has available -to produce any presentational interface your application might need. - -For example, the view could use model data to render an HTML view template containing it, -or a XML formatted result for others to consume:: - - // In a view template file, we'll render an 'element' for each user. - -
    • - element('user_info', ['user' => $user]) ?> -
    • - - -The View layer provides a number of extension points like :ref:`view-templates`, :ref:`view-elements` -and :doc:`/views/cells` to let you re-use your presentation logic. - -The View layer is not only limited to HTML or text representation of the data. -It can be used to deliver common data formats like JSON, XML, and through -a pluggable architecture any other format you may need, such as CSV. - -The Controller Layer -==================== - -The Controller layer handles requests from users. It is responsible for -rendering a response with the aid of both the Model and the View layers. - -A controller can be seen as a manager that ensures that all resources needed for -completing a task are delegated to the correct workers. It waits for petitions -from clients, checks their validity according to authentication or authorization -rules, delegates data fetching or processing to the model, selects the type of -presentational data that the clients are accepting, and finally delegates the -rendering process to the View layer. An example of a user registration -controller would be:: - - public function add() - { - $user = $this->Users->newEmptyEntity(); - if ($this->request->is('post')) { - $user = $this->Users->patchEntity($user, $this->request->getData()); - if ($this->Users->save($user, ['validate' => 'registration'])) { - $this->Flash->success(__('You are now registered.')); - } else { - $this->Flash->error(__('There were some problems.')); - } - } - $this->set('user', $user); - } - -You may notice that we never explicitly rendered a view. CakePHP's conventions -will take care of selecting the right view and rendering it with the view data -we prepared with ``set()``. - -.. _request-cycle: - -CakePHP Request Cycle -===================== - -Now that you are familiar with the different layers in CakePHP, lets review how -a request cycle works in CakePHP: - -.. figure:: /_static/img/typical-cake-request.png - :align: center - :alt: Flow diagram showing a typical CakePHP request - -The typical CakePHP request cycle starts with a user requesting a page or -resource in your application. At a high level each request goes through the -following steps: - -#. The webserver rewrite rules direct the request to **webroot/index.php**. -#. Your Application is loaded and bound to an ``HttpServer``. -#. Your application's middleware is initialized. -#. A request and response is dispatched through the PSR-7 Middleware that your - application uses. Typically this includes error trapping and routing. -#. If no response is returned from the middleware and the request contains - routing information, a controller & action are selected. -#. The controller's action is called and the controller interacts with the - required Models and Components. -#. The controller delegates response creation to the View to generate the output - resulting from the model data. -#. The view uses Helpers and Cells to generate the response body and headers. -#. The response is sent back out through the :doc:`/controllers/middleware`. -#. The ``HttpServer`` emits the response to the webserver. - -Just the Start -============== - -Hopefully this quick overview has piqued your interest. Some other great -features in CakePHP are: - -* A :doc:`caching ` framework that integrates with - Memcached, Redis and other backends. -* Powerful :doc:`code generation tools - ` so you can start immediately. -* :doc:`Integrated testing framework ` so you can ensure - your code works perfectly. - -The next obvious steps are to :doc:`download CakePHP `, read the -:doc:`tutorial and build something awesome -`. - -Additional Reading -================== - -.. toctree:: - :maxdepth: 1 - - /intro/where-to-get-help - /intro/conventions - /intro/cakephp-folder-structure - -.. meta:: - :title lang=en: Getting Started - :keywords lang=en: folder structure,table names,initial request,database table,organizational structure,rst,filenames,conventions,mvc,web page,sit diff --git a/en/intro/cakephp-folder-structure.rst b/en/intro/cakephp-folder-structure.rst deleted file mode 100644 index 9fec23c0be..0000000000 --- a/en/intro/cakephp-folder-structure.rst +++ /dev/null @@ -1,63 +0,0 @@ -CakePHP Folder Structure -######################## - -After you've downloaded the CakePHP application skeleton, there are a few top -level folders you should see: - -- The *bin* folder holds the Cake console executables. -- The *config* folder holds the :doc:`/development/configuration` files - CakePHP uses. Database connection details, bootstrapping, core configuration files - and more should be stored here. -- The *plugins* folder is where the :doc:`/plugins` your application uses are stored. -- The *logs* folder normally contains your log files, depending on your log - configuration. -- The *src* folder will be where your application’s source files will be placed. -- The *templates* folder has presentational files placed here: - elements, error pages, layouts, and view template files. -- The *resources* folder has sub folder for various types of resource files. - The *locales* sub folder stores language files for internationalization. -- The *tests* folder will be where you put the test cases for your application. -- The *tmp* folder is where CakePHP stores temporary data. The actual data it - stores depends on how you have CakePHP configured, but this folder - is usually used to store translation messages, model descriptions and sometimes - session information. -- The *vendor* folder is where CakePHP and other application dependencies will - be installed by `Composer `_. Editing these files is not - advised, as Composer will overwrite your changes next time you update. -- The *webroot* directory is the public document root of your application. It - contains all the files you want to be publicly reachable. - - Make sure that the *tmp* and *logs* folders exist and are writable, - otherwise the performance of your application will be severely - impacted. In debug mode, CakePHP will warn you, if these directories are not - writable. - -The src Folder -============== - -CakePHP's *src* folder is where you will do most of your application -development. Let's look a little closer at the folders inside -*src*. - -Command - Contains your application's console commands. See - :doc:`/console-commands/commands` to learn more. -Console - Contains the installation script executed by Composer. -Controller - Contains your application's :doc:`/controllers` and their components. -Middleware - Stores any :doc:`/controllers/middleware` for your application. -Model - Contains your application's tables, entities and behaviors. -View - Presentational classes are placed here: views, cells, helpers. - -.. note:: - - The folder ``Command`` is not present by default. - You can add it when you need it. - -.. meta:: - :title lang=en: CakePHP Folder Structure - :keywords lang=en: internal libraries,core configuration,model descriptions,external vendors,connection details,folder structure,party libraries,personal commitment,database connection,internationalization,configuration files,folders,application development,readme,lib,configured,logs,config,third party,cakephp diff --git a/en/intro/conventions.rst b/en/intro/conventions.rst deleted file mode 100644 index 7a98cf25c2..0000000000 --- a/en/intro/conventions.rst +++ /dev/null @@ -1,256 +0,0 @@ -CakePHP Conventions -################### - -We are big fans of convention over configuration. While it takes a bit of time -to learn CakePHP's conventions, you save time in the long run. By following -conventions, you get free functionality, and you liberate yourself from the -maintenance nightmare of tracking config files. Conventions also make for a very -uniform development experience, allowing other developers to jump in and help. - -Controller Conventions -====================== - -Controller class names are plural, CamelCased, and end in ``Controller``. -``UsersController`` and ``MenuLinksController`` are both examples of -conventional controller names. - -Public methods on Controllers are often exposed as 'actions' accessible through -a web browser. They are camelBacked. For example the ``/users/view-me`` maps to the ``viewMe()`` method -of the ``UsersController`` out of the box (if one uses default dashed inflection in routing). -Protected or private methods cannot be accessed with routing. - -URL Considerations for Controller Names -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -As you've just seen, single word controllers map to a simple lower case URL -path. For example, ``UsersController`` (which would be defined in the file name -**UsersController.php**) is accessed from ``http://example.com/users``. - -While you can route multiple word controllers in any way you like, the -convention is that your URLs are lowercase and dashed using the ``DashedRoute`` -class, therefore ``/menu-links/view-all`` is the correct form to access -the ``MenuLinksController::viewAll()`` action. - -When you create links using ``this->Html->link()``, you can use the following -conventions for the url array:: - - $this->Html->link('link-title', [ - 'prefix' => 'MyPrefix' // CamelCased - 'plugin' => 'MyPlugin', // CamelCased - 'controller' => 'ControllerName', // CamelCased - 'action' => 'actionName' // camelBacked - ] - -For more information on CakePHP URLs and parameter handling, see -:ref:`routes-configuration`. - -.. _file-and-classname-conventions: - -File and Class Name Conventions -=============================== - -In general, filenames match the class names, and follow the PSR-4 standard for -autoloading. The following are some examples of class names and their filenames: - -- The Controller class ``LatestArticlesController`` would be found in a file - named **LatestArticlesController.php** -- The Component class ``MyHandyComponent`` would be found in a file named - **MyHandyComponent.php** -- The Table class ``OptionValuesTable`` would be found in a file named - **OptionValuesTable.php**. -- The Entity class ``OptionValue`` would be found in a file named - **OptionValue.php**. -- The Behavior class ``EspeciallyFunkableBehavior`` would be found in a file - named **EspeciallyFunkableBehavior.php** -- The View class ``SuperSimpleView`` would be found in a file named - **SuperSimpleView.php** -- The Helper class ``BestEverHelper`` would be found in a file named - **BestEverHelper.php** - -Each file would be located in the appropriate folder/namespace in your app -folder. - -.. _model-and-database-conventions: - -Database Conventions -==================== - -Table names corresponding to CakePHP models are plural and underscored. For -example ``users``, ``menu_links``, and ``user_favorite_pages`` -respectively. Table name whose name contains multiple words should only -pluralize the last word, for example, ``menu_links``. - -Column names with two or more words are underscored, for example, ``first_name``. - -Foreign keys in hasMany, belongsTo/hasOne relationships are recognized by -default as the (singular) name of the related table followed by ``_id``. So if -Users hasMany Articles, the ``articles`` table will refer to the ``users`` -table via a ``user_id`` foreign key. For a table like ``menu_links`` -whose name contains multiple words, the foreign key would be -``menu_link_id``. - -Join (or "junction") tables are used in BelongsToMany relationships between -models. These should be named for the tables they connect. The names should be -pluralized and sorted alphabetically: ``articles_tags``, not ``tags_articles`` -or ``article_tags``. *The bake command will not work if this convention is not -followed.* If the junction table holds any data other than the linking foreign -keys, you should create a concrete entity/table class for the table. - -In addition to using an auto-incrementing integer as primary keys, you can also -use UUID columns. CakePHP will create UUID values automatically using -(:php:meth:`Cake\\Utility\\Text::uuid()`) whenever you save new records using -the ``Table::save()`` method. - -Model Conventions -================= - -Table class names are plural, CamelCased and end in ``Table``. ``UsersTable``, -``MenuLinksTable``, and ``UserFavoritePagesTable`` are all examples of -table class names matching the ``users``, ``menu_links`` and -``user_favorite_pages`` tables respectively. - -Entity class names are singular CamelCased and have no suffix. ``User``, -``MenuLink``, and ``UserFavoritePage`` are all examples of entity names -matching the ``users``, ``menu_links`` and ``user_favorite_pages`` -tables respectively. - -Enum class names should use a ``{Entity}{Column}`` convention, and enum cases -should use CamelCased names. - - -View Conventions -================ - -View template files are named after the controller functions they display, in an -underscored form. The ``viewAll()`` function of the ``ArticlesController`` class -will look for a view template in **templates/Articles/view_all.php**. - -The basic pattern is -**templates/Controller/underscored_function_name.php**. - -.. note:: - - By default CakePHP uses English inflections. If you have database - tables/columns that use another language, you will need to add inflection - rules (from singular to plural and vice-versa). You can use - :php:class:`Cake\\Utility\\Inflector` to define your custom inflection - rules. See the documentation about :doc:`/core-libraries/inflector` for more - information. - -Plugins Conventions -=================== - -It is useful to prefix a CakePHP plugin with "cakephp-" in the package name. -This makes the name semantically related on the framework it depends on. - -Do **not** use the CakePHP namespace (cakephp) as vendor name as this is -reserved to CakePHP owned plugins. The convention is to use lowercase letters -and dashes as separator:: - - // Bad - cakephp/foo-bar - - // Good - your-name/cakephp-foo-bar - -See `awesome list recommendations -`__ -for details. - -Summarized -========== - -By naming the pieces of your application using CakePHP conventions, you gain -functionality without the hassle and maintenance tethers of configuration. -Here's a final example that ties the conventions together: - -- Database table: "articles", "menu_links" -- Table class: ``ArticlesTable``, found at **src/Model/Table/ArticlesTable.php** -- Entity class: ``Article``, found at **src/Model/Entity/Article.php** -- Controller class: ``ArticlesController``, found at - **src/Controller/ArticlesController.php** -- View template, found at **templates/Articles/index.php** - -Using these conventions, CakePHP knows that a request to -``http://example.com/articles`` maps to a call on the ``index()`` method of the -``ArticlesController``, where the ``Articles`` model is automatically available. -None of these relationships have been configured by any means other than by -creating classes and files that you'd need to create anyway. - -+------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Example | articles | menu_links | | -+------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Database | articles | menu_links | Table names corresponding to CakePHP | -| Table | | | models are plural and underscored. | -+------------+-----------------------------+-------------------------+------------------------------------------------------+ -| File | ArticlesController.php | MenuLinksController.php | | -+------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Table | ArticlesTable.php | MenuLinksTable.php | Table class names are plural, | -| | | | CamelCased and end in Table | -+------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Entity | Article.php | MenuLink.php | Entity class names are singular, | -| | | | CamelCased: Article and MenuLink | -+------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Class | ArticlesController | MenuLinksController | | -+------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Controller | ArticlesController | MenuLinksController | Plural, CamelCased, end in Controller | -+------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Templates | Articles/index.php | MenuLinks/index.php | View template files are named after | -| | Articles/add.php | MenuLinks/add.php | the controller functions they | -| | Articles/get_list.php | MenuLinks/get_list.php | display, in an underscored form | -+------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Behavior | ArticlesBehavior.php | MenuLinksBehavior.php | | -+------------+-----------------------------+-------------------------+------------------------------------------------------+ -| View | ArticlesView.php | MenuLinksView.php | | -+------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Helper | ArticlesHelper.php | MenuLinksHelper.php | | -+------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Component | ArticlesComponent.php | MenuLinksComponent.php | | -+------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Plugin | Bad: cakephp/articles | cakephp/menu-links | Useful to prefix a CakePHP plugin with "cakephp-" | -| | Good: you/cakephp-articles | you/cakephp-menu-links | in the package name. Do not use the CakePHP | -| | | | namespace (cakephp) as vendor name as this is | -| | | | reserved to CakePHP owned plugins. The convention | -| | | | is to use lowercase letters and dashes as separator. | -| | | | | -+------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Each file would be located in the appropriate folder/namespace in your app folder. | -+------------+-----------------------------+-------------------------+------------------------------------------------------+ - - -Database Convention Summary -=========================== -+-----------------+--------------------------------------------------------------+ -| Foreign keys | Relationships are recognized by default as the | -| | (singular) name of the related table followed by ``_id``. | -| hasMany | Users hasMany Articles, ``articles`` table will refer | -| belongsTo/ | to the ``users`` table via a ``user_id`` foreign key. | -| hasOne | | -| BelongsToMany | | -| | | -+-----------------+--------------------------------------------------------------+ -| Multiple Words | ``menu_links`` whose name contains multiple words, | -| | the foreign key would be ``menu_link_id``. | -+-----------------+--------------------------------------------------------------+ -| Auto Increment | In addition to using an auto-incrementing integer as | -| | primary keys, you can also use UUID columns. | -| | CakePHP will create UUID values automatically | -| | using (:php:meth:`Cake\\Utility\\Text::uuid()`) | -| | whenever you save new records using the | -| | ``Table::save()`` method. | -+-----------------+--------------------------------------------------------------+ -| Join tables | Should be named after the model tables they will join | -| | or the bake command won't work, arranged in alphabetical | -| | order (``articles_tags`` rather than ``tags_articles``). | -| | Additional columns on the junction table you should create | -| | a separate entity/table class for that table. | -+-----------------+--------------------------------------------------------------+ - -Now that you've been introduced to CakePHP's fundamentals, you might try a run -through the :doc:`/tutorials-and-examples/cms/installation` to see how things fit -together. - - -.. meta:: - :title lang=en: CakePHP Conventions - :keywords lang=en: web development experience,maintenance nightmare,index method,legacy systems,method names,php class,uniform system,config files,tenets,articles,conventions,conventional controller,best practices,maps,visibility,news articles,functionality,logic,cakephp,developers diff --git a/en/intro/where-to-get-help.rst b/en/intro/where-to-get-help.rst deleted file mode 100644 index 85af585493..0000000000 --- a/en/intro/where-to-get-help.rst +++ /dev/null @@ -1,129 +0,0 @@ -Where to Get Help -################# - -The Official CakePHP website -============================ - -`https://cakephp.org `_ - -The Official CakePHP website is always a great place to visit. It features links -to oft-used developer tools, screencasts, donation opportunities, and downloads. - -The Cookbook -============ - -`https://book.cakephp.org `_ - -This manual should probably be the first place you go to get answers. As with -many other open source projects, we get new folks regularly. Try your best to -answer your questions on your own first. Answers may come slower, but will -remain longer – and you'll also be lightening our support load. Both the manual -and the API have an online component. - -The Bakery -========== - -`https://bakery.cakephp.org `_ - -The CakePHP Bakery is a clearing house for all things regarding CakePHP. Check -it out for tutorials, case studies, and code examples. Once you're acquainted -with CakePHP, log on and share your knowledge with the community and gain -instant fame and fortune. - -The API -======= - -`https://api.cakephp.org/ `_ - -Straight to the point and straight from the core developers, the CakePHP API -(Application Programming Interface) is the most comprehensive documentation -around for all the nitty gritty details of the internal workings of the -framework. It's a straight forward code reference, so bring your propeller hat. - -The Test Cases -============== - -If you ever feel the information provided in the API is not sufficient, check -out the code of the test cases provided with CakePHP. They can serve as -practical examples for function and data member usage for a class. :: - - tests/TestCase/ - -Slack -===== - -`CakePHP Slack Support Channel `_ - -If you're stumped, give us a holler in the CakePHP Slack support channel. -We'd love to hear from you, whether you need some help, want to -find users in your area, or would like to donate your brand new sports car. - -Discord -======= - -`CakePHP Discord `_ - -You can also join us on Discord. - -.. _cakephp-official-communities: - -Official CakePHP Forum -====================== -`CakePHP Official Forum `_ - -Our official forum where you can ask for help, suggest ideas and have a talk -about CakePHP. It's a perfect place for quickly finding answers and help others. -Join the CakePHP family by signing up. - -Stackoverflow -============= - -`https://stackoverflow.com/ `_ - -Tag your questions with ``cakephp`` and the specific version you are using to -enable existing users of stackoverflow to find your questions. - -Where to get Help in your Language -================================== - -Danish ------- - -- `Danish CakePHP Slack Channel `_ - -French ------- - -- `French CakePHP Slack Channel `_ - -German ------- - -- `German CakePHP Slack Channel `_ -- `German CakePHP Facebook Group `_ - -Dutch ------ - -- `Dutch CakePHP Slack Channel `_ - -Japanese --------- - -- `Japanese CakePHP Slack Channel `_ -- `Japanese CakePHP Facebook Group `_ - -Portuguese ----------- - -- `Portuguese CakePHP Slack Channel `_ - -Spanish -------- - -- `Spanish CakePHP Slack Channel `_ - -.. meta:: - :title lang=en: Where to Get Help - :description lang=en: Where to get help with CakePHP: The official CakePHP website, The Cookbook, The Bakery, The API, in the test cases, Slack, Discord, CakePHP Questions. - :keywords lang=en: cakephp,cakephp help,help with cakephp,where to get help,cakephp irc,cakephp questions,cakephp api,cakephp test cases,open source projects,channel irc,code reference,irc channel,developer tools,test case,bakery diff --git a/en/migrations.rst b/en/migrations.rst deleted file mode 100644 index 00218e316e..0000000000 --- a/en/migrations.rst +++ /dev/null @@ -1,4 +0,0 @@ -Migrations -########## - -This page has `moved `__. diff --git a/en/orm.rst b/en/orm.rst deleted file mode 100644 index 96019ceab6..0000000000 --- a/en/orm.rst +++ /dev/null @@ -1,123 +0,0 @@ -Database Access & ORM -##################### - -In CakePHP, working with data through the database is done with two primary object types: - -- **Repositories** or **table objects** provide access to collections of data. - They allow you to save new records, modify/delete existing ones, define - relations, and perform bulk operations. -- **Entities** represent individual records and allow you to define row/record - level behavior & functionality. - -These two classes are usually responsible for managing almost everything -that happens regarding your data, its validity, interactions and evolution -of the information workflow in your domain of work. - -CakePHP's built-in ORM specializes in relational databases, but can be extended -to support alternative datasources. - -The CakePHP ORM borrows ideas and concepts from both ActiveRecord and Datamapper -patterns. It aims to create a hybrid implementation that combines aspects of -both patterns to create a fast, simple to use ORM. - -Before we get started exploring the ORM, make sure you :ref:`configure your -database connections `. - -Quick Example -============= - -To get started you don't have to write any code. If you've followed the -:ref:`CakePHP conventions for your database tables -` you can just start using the ORM. For example -if we wanted to load some data from our ``articles`` table we would start off -creating our ``Articles`` table class. Create -**src/Model/Table/ArticlesTable.php** with the following code:: - - fetchTable('Articles')->find()->all(); - - foreach ($resultset as $row) { - echo $row->title; - } - } - -In other contexts, you can use the ``LocatorAwareTrait`` which add accessor methods for ORM tables:: - - use Cake\ORM\Locator\LocatorAwareTrait; - - public function someMethod() - { - $articles = $this->fetchTable('Articles'); - // more code. - } - -Within a static method you can use the :php:class:`~Cake\\Datasource\\FactoryLocator` -to get the table locator:: - - $articles = TableRegistry::getTableLocator()->get('Articles'); - -Table classes represent **collections** of **entities**. Next, lets create an -entity class for our Articles. Entity classes let you define accessor and -mutator methods, define custom logic for individual records and much more. We'll -start off by adding the following to **src/Model/Entity/Article.php** after the -``fetchTable('Articles'); - $resultset = $articles->find()->all(); - - foreach ($resultset as $row) { - // Each row is now an instance of our Article class. - echo $row->title; - } - -CakePHP uses naming conventions to link the Table and Entity class together. If -you need to customize which entity a table uses you can use the -``entityClass()`` method to set a specific classname. - -See the chapters on :doc:`/orm/table-objects` and :doc:`/orm/entities` for more -information on how to use table objects and entities in your application. - -More Information -================ - -.. toctree:: - :maxdepth: 2 - - orm/database-basics - orm/query-builder - orm/table-objects - orm/entities - orm/associations - orm/retrieving-data-and-resultsets - orm/validation - orm/saving-data - orm/deleting-data - orm/behaviors - orm/schema-system - console-commands/schema-cache diff --git a/en/orm/associations.rst b/en/orm/associations.rst deleted file mode 100644 index 9261c9240f..0000000000 --- a/en/orm/associations.rst +++ /dev/null @@ -1,765 +0,0 @@ -Associations - Linking Tables Together -###################################### - -Defining relations between different objects in your application should be -a natural process. For example, an article may have many comments, and belong to -an author. Authors may have many articles and comments. The four association -types in CakePHP are: hasOne, hasMany, belongsTo, and belongsToMany. - -============= ===================== ======================================= -Relationship Association Type Example -============= ===================== ======================================= -one to one hasOne A user has one profile. -------------- --------------------- --------------------------------------- -one to many hasMany A user can have multiple articles. -------------- --------------------- --------------------------------------- -many to one belongsTo Many articles belong to a user. -------------- --------------------- --------------------------------------- -many to many belongsToMany Tags belong to many articles. -============= ===================== ======================================= - -Associations are defined during the ``initialize()`` method of your table -object. Methods matching the association type allow you to define the -associations in your application. For example if we wanted to define a belongsTo -association in our ArticlesTable:: - - namespace App\Model\Table; - - use Cake\ORM\Table; - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->belongsTo('Authors'); - } - } - -The simplest form of any association setup takes the table alias you want to -associate with. By default all of the details of an association will use the -CakePHP conventions. If you want to customize how your associations are handled -you can modify them with setters:: - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->belongsTo('Authors', [ - 'className' => 'Publishing.Authors' - ]) - ->setForeignKey('author_id') - ->setProperty('author'); - } - } - -The property name will be the property key (of the associated entity) on the entity object, in this case:: - - $authorEntity = $articleEntity->author; - -You can also use arrays to customize your associations:: - - $this->belongsTo('Authors', [ - 'className' => 'Publishing.Authors', - 'foreignKey' => 'author_id', - 'propertyName' => 'author' - ]); - -However, arrays do not offer the typehinting and autocomplete benefits that the fluent interface does. - -The same table can be used multiple times to define different types of -associations. For example consider a case where you want to separate -approved comments and those that have not been moderated yet:: - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->hasMany('Comments') - ->setFinder('approved'); - - $this->hasMany('UnapprovedComments', [ - 'className' => 'Comments' - ]) - ->setFinder('unapproved') - ->setProperty('unapproved_comments'); - } - } - -As you can see, by specifying the ``className`` key, it is possible to use the -same table as different associations for the same table. You can even create -self-associated tables to create parent-child relationships:: - - class CategoriesTable extends Table - { - public function initialize(array $config): void - { - $this->hasMany('SubCategories', [ - 'className' => 'Categories', - ]); - - $this->belongsTo('ParentCategories', [ - 'className' => 'Categories', - ]); - } - } - -You can also setup associations in mass by making a single call to -``Table::addAssociations()`` which accepts an array containing a set of -table names indexed by association type as an argument:: - - class PostsTable extends Table - { - public function initialize(array $config): void - { - $this->addAssociations([ - 'belongsTo' => [ - 'Users' => ['className' => 'App\Model\Table\UsersTable'], - ], - 'hasMany' => ['Comments'], - 'belongsToMany' => ['Tags'], - ]); - } - } - -Each association type accepts multiple associations where the keys are the -aliases, and the values are association config data. If numeric keys are used -the values will be treated as association aliases. - -.. _has-one-associations: - -HasOne Associations -=================== - -Let's set up a Users table with a hasOne relationship to the Addresses table. - -First, your database tables need to be keyed correctly. For a hasOne -relationship to work, one table has to contain a foreign key that points to a -record in the other table. In this case, the Addresses table will contain a field -called 'user_id'. The basic pattern is: - -**hasOne:** the *other* model contains the foreign key. - -====================== ================== -Relation Schema -====================== ================== -Users hasOne Addresses addresses.user\_id ----------------------- ------------------ -Doctors hasOne Mentors mentors.doctor\_id -====================== ================== - -.. note:: - - It is not mandatory to follow CakePHP conventions, you can override the name - of any ``foreignKey`` in your associations definitions. Nevertheless, sticking - to conventions will make your code less repetitive, easier to read and to - maintain. - -Once you create the ``UsersTable`` and ``AddressesTable`` classes, you can make -the association with the following code:: - - class UsersTable extends Table - { - public function initialize(array $config): void - { - $this->hasOne('Addresses'); - } - } - -If you need more control, you can define your associations using the setters. -For example, you might want to limit the association to include only certain -records:: - - class UsersTable extends Table - { - public function initialize(array $config): void - { - $this->hasOne('Addresses') - ->setName('Addresses') - ->setFinder('primary') - ->setDependent(true); - } - } - -If you want to break different addresses into multiple associations, you can do something like:: - - class UsersTable extends Table - { - public function initialize(array $config): void - { - $this->hasOne('HomeAddresses', [ - 'className' => 'Addresses' - ]) - ->setProperty('home_address') - ->setConditions(['HomeAddresses.label' => 'Home']) - ->setDependent(true); - - $this->hasOne('WorkAddresses', [ - 'className' => 'Addresses' - ]) - ->setProperty('work_address') - ->setConditions(['WorkAddresses.label' => 'Work']) - ->setDependent(true); - } - } - -.. note:: - - If a column is shared by multiple hasOne associations, you must qualify it with the association alias. - In the above example, the 'label' column is qualified with the 'HomeAddresses' and 'WorkAddresses' aliases. - -Possible keys for hasOne association arrays include: - -- **className**: The class name of the other table. This is the same name used - when getting an instance of the table. In the 'Users hasOne Addresses' example, - it should be 'Addresses'. The default value is the name of the association. -- **foreignKey**: The name of the foreign key column in the other table. The - default value is the underscored, singular name of the current model, - suffixed with '\_id' such as 'user\_id' in the above example. -- **bindingKey**: The name of the column in the current table used to match the - ``foreignKey``. The default value is the primary key of the current table - such as 'id' of Users in the above example. -- **conditions**: An array of find() compatible conditions such as - ``['Addresses.primary' => true]`` -- **joinType**: The type of the join used in the SQL query. Accepted values are - 'LEFT' and 'INNER'. You can use 'INNER' to get results only where the - association is set. The default value is 'LEFT'. -- **dependent**: When the dependent key is set to ``true``, and an entity is - deleted, the associated model records are also deleted. In this case we set it - to ``true`` so that deleting a User will also delete her associated Address. -- **cascadeCallbacks**: When this and **dependent** are ``true``, cascaded - deletes will load and delete entities so that callbacks are properly - triggered. When ``false``, ``deleteAll()`` is used to remove associated data - and no callbacks are triggered. -- **propertyName**: The property name that should be filled with data from the - associated table into the source table results. By default this is the - underscored & singular name of the association so ``address`` in our example. -- **strategy**: The query strategy used to load matching record from the other table. - Accepted values are ``'join'`` and ``'select'``. Using ``'select'`` will generate a separate query - and can be useful when the other table is in different database. The default is ``'join'``. -- **finder**: The finder method to use when loading associated records. - -Once this association has been defined, find operations on the Users table can -contain the Address record if it exists:: - - // In a controller or table method. - $query = $users->find('all')->contain(['Addresses'])->all(); - foreach ($query as $user) { - echo $user->address->street; - } - -The above would emit SQL that is similar to: - -.. code-block:: sql - - SELECT * FROM users INNER JOIN addresses ON addresses.user_id = users.id; - -.. _belongs-to-associations: - -BelongsTo Associations -====================== - -Now that we have Address data access from the User table, let's define -a belongsTo association in the Addresses table in order to get access to related -User data. The belongsTo association is a natural complement to the hasOne and -hasMany associations - it allows us to see related data from the other -direction. - -When keying your database tables for a belongsTo relationship, follow this -convention: - -**belongsTo:** the *current* model contains the foreign key. - -========================= ================== -Relation Schema -========================= ================== -Addresses belongsTo Users addresses.user\_id -------------------------- ------------------ -Mentors belongsTo Doctors mentors.doctor\_id -========================= ================== - -.. tip:: - - If a table contains a foreign key, it belongs to the other table. - -We can define the belongsTo association in our Addresses table as follows:: - - class AddressesTable extends Table - { - public function initialize(array $config): void - { - $this->belongsTo('Users'); - } - } - -We can also define a more specific relationship using the setters:: - - class AddressesTable extends Table - { - public function initialize(array $config): void - { - $this->belongsTo('Users') - ->setForeignKey('user_id') - ->setJoinType('INNER'); - } - } - -Possible keys for belongsTo association arrays include: - -- **className**: The class name of the other table. This is the same name used - when getting an instance of the table. In the 'Addresses belongsTo Users' example, - it should be 'Users'. The default value is the name of the association. -- **foreignKey**: The name of the foreign key column in the current table. The - default value is the underscored, singular name of the other model, - suffixed with '\_id' such as 'user\_id' in the above example. -- **bindingKey**: The name of the column in the other table used to match the - ``foreignKey``. The default value is the primary key of the other table - such as 'id' of Users in the above example. -- **conditions**: An array of find() compatible conditions or SQL strings such - as ``['Users.active' => true]`` -- **joinType**: The type of the join used in the SQL query. Accepted values are - 'LEFT' and 'INNER'. You can use 'INNER' to get results only where the - association is set. The default value is 'LEFT'. -- **propertyName**: The property name that should be filled with data from the - associated table into the source table results. By default this is the - underscored & singular name of the association so ``user`` in our example. -- **strategy**: The query strategy used to load matching record from the other table. - Accepted values are ``'join'`` and ``'select'``. Using ``'select'`` will generate a separate query - and can be useful when the other table is in different database. The default is ``'join'``. -- **finder**: The finder method to use when loading associated records. - -Once this association has been defined, find operations on the Addresses table can -contain the User record if it exists:: - - // In a controller or table method. - $query = $addresses->find('all')->contain(['Users'])->all(); - foreach ($query as $address) { - echo $address->user->username; - } - -The above would output SQL similar to: - -.. code-block:: sql - - SELECT * FROM addresses LEFT JOIN users ON addresses.user_id = users.id; - -.. _has-many-associations: - -HasMany Associations -==================== - -An example of a hasMany association is "Articles hasMany Comments". Defining this -association will allow us to fetch an article's comments when the article is -loaded. - -When creating your database tables for a hasMany relationship, follow this -convention: - -**hasMany:** the *other* model contains the foreign key. - -========================== ==================== -Relation Schema -========================== ==================== -Articles hasMany Comments Comments.article\_id --------------------------- -------------------- -Products hasMany Options Options.product\_id --------------------------- -------------------- -Doctors hasMany Patients Patients.doctor\_id -========================== ==================== - -We can define the hasMany association in our Articles model as follows:: - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->hasMany('Comments'); - } - } - -We can also define a more specific relationship using the setters:: - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->hasMany('Comments') - ->setForeignKey('article_id') - ->setDependent(true); - } - } - -Sometimes you may want to configure composite keys in your associations:: - - // Within ArticlesTable::initialize() call - $this->hasMany('Comments') - ->setForeignKey([ - 'article_id', - 'article_hash', - ]); - -Relying on the example above, we have passed an array containing the desired -composite keys to ``setForeignKey()``. By default the ``bindingKey`` would be -automatically defined as ``id`` and ``hash`` respectively, but let's assume that -you need to specify different binding fields than the defaults. You can setup it -manually with ``setBindingKey()``:: - - // Within ArticlesTable::initialize() call - $this->hasMany('Comments') - ->setForeignKey([ - 'article_id', - 'article_hash', - ]) - ->setBindingKey([ - 'whatever_id', - 'whatever_hash', - ]); - -Like hasOne associations, ``foreignKey`` is in the other (Comments) -table and ``bindingKey`` is in the current (Articles) table. - -Possible keys for hasMany association arrays include: - -- **className**: The class name of the other table. This is the same name used - when getting an instance of the table. In the 'Articles hasMany Comments' example, - it should be 'Comments'. The default value is the name of the association. -- **foreignKey**: The name of the foreign key column in the other table. The - default value is the underscored, singular name of the current model, - suffixed with '\_id' such as 'article\_id' in the above example. -- **bindingKey**: The name of the column in the current table used to match the - ``foreignKey``. The default value is the primary key of the current table - such as 'id' of Articles in the above example. -- **conditions**: an array of find() compatible conditions or SQL - strings such as ``['Comments.visible' => true]``. It is recommended to - use the ``finder`` option instead. -- **sort**: an array of find() compatible order clauses or SQL - strings such as ``['Comments.created' => 'ASC']`` -- **dependent**: When dependent is set to ``true``, recursive model - deletion is possible. In this example, Comment records will be - deleted when their associated Article record has been deleted. -- **cascadeCallbacks**: When this and **dependent** are ``true``, cascaded - deletes will load and delete entities so that callbacks are properly - triggered. When ``false``, ``deleteAll()`` is used to remove associated data - and no callbacks are triggered. -- **propertyName**: The property name that should be filled with data from the - associated table into the source table results. By default this is the - underscored & plural name of the association so ``comments`` in our example. -- **strategy**: Defines the query strategy to use. Defaults to 'select'. The - other valid value is 'subquery', which replaces the ``IN`` list with an - equivalent subquery. -- **saveStrategy**: Either 'append' or 'replace'. Defaults to 'append'. When 'append' the current - records are appended to any records in the database. When 'replace' associated - records not in the current set will be removed. If the foreign key is a nullable - column or if ``dependent`` is true records will be orphaned. -- **finder**: The finder method to use when loading associated records. See the - :ref:`association-finder` section for more information. - -Once this association has been defined, find operations on the Articles table -can contain the Comment records if they exist:: - - // In a controller or table method. - $query = $articles->find('all')->contain(['Comments'])->all(); - foreach ($query as $article) { - echo $article->comments[0]->text; - } - -The above would output SQL similar to: - -.. code-block:: sql - - SELECT * FROM articles; - SELECT * FROM comments WHERE article_id IN (1, 2, 3, 4, 5); - -When the subquery strategy is used, SQL similar to the following will be -generated: - -.. code-block:: sql - - SELECT * FROM articles; - SELECT * FROM comments WHERE article_id IN (SELECT id FROM articles); - -You may want to cache the counts for your hasMany associations. This is useful -when you often need to show the number of associated records, but don't want to -load all the records just to count them. For example, the comment count on any -given article is often cached to make generating lists of articles more -efficient. You can use the :doc:`CounterCacheBehavior -` to cache counts of associated records. - -You should make sure that your database tables do not contain columns that match -association property names. If for example you have counter fields that conflict -with association properties, you must either rename the association property, or -the column name. - -.. _belongs-to-many-associations: - -BelongsToMany Associations -========================== - -An example of a BelongsToMany association is "Article BelongsToMany Tags", where -the tags from one article are shared with other articles. BelongsToMany is -often referred to as "has and belongs to many", and is a classic "many to many" -association. - -The main difference between hasMany and BelongsToMany is that the link between -the models in a BelongsToMany association is not exclusive. For example, we are -joining our Articles table with a Tags table. Using 'funny' as a Tag for my -Article, doesn't "use up" the tag. I can also use it on the next article -I write. - -Three database tables are required for a BelongsToMany association. In the -example above we would need tables for ``articles``, ``tags`` and -``articles_tags``. The ``articles_tags`` table contains the data that links -tags and articles together. The joining table is named after the two tables -involved, separated with an underscore by convention. In its simplest form, this -table consists of ``article_id`` and ``tag_id`` and a multi-column -``PRIMARY KEY`` index spanning both columns. - -**belongsToMany** requires a separate join table that includes both *model* -names. - -============================== ================================================================ -Relationship Join Table Fields -============================== ================================================================ -Articles belongsToMany Tags articles_tags.id, articles_tags.tag_id, articles_tags.article_id ------------------------------- ---------------------------------------------------------------- -Patients belongsToMany Doctors doctors_patients.id, doctors_patients.doctor_id, - doctors_patients.patient_id. -============================== ================================================================ - -We can define the belongsToMany association in both our models as follows:: - - // In src/Model/Table/ArticlesTable.php - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->belongsToMany('Tags'); - } - } - - // In src/Model/Table/TagsTable.php - class TagsTable extends Table - { - public function initialize(array $config): void - { - $this->belongsToMany('Articles'); - } - } - -We can also define a more specific relationship using configuration:: - - // In src/Model/Table/TagsTable.php - class TagsTable extends Table - { - public function initialize(array $config): void - { - $this->belongsToMany('Articles', [ - 'joinTable' => 'articles_tags', - ]); - } - } - -Possible keys for belongsToMany association arrays include: - -- **className**: The class name of the other table. This is the same name used - when getting an instance of the table. In the 'Articles belongsToMany Tags' - example, it should be 'Tags'. The default value is the name of the association. -- **joinTable**: The name of the join table used in this - association (if the current table doesn't adhere to the naming - convention for belongsToMany join tables). By default this table - name will be used to load the Table instance for the join table. -- **foreignKey**: The name of the foreign key that references the current model - found on the join table, or list in case of composite foreign keys. - This is especially handy if you need to define multiple - belongsToMany relationships. The default value for this key is the - underscored, singular name of the current model, suffixed with '\_id'. -- **bindingKey**: The name of the column in the current table, that will be used - for matching the ``foreignKey``. Defaults to the primary key. -- **targetForeignKey**: The name of the foreign key that references the target - model found on the join model, or list in case of composite foreign keys. - The default value for this key is the underscored, singular name of - the target model, suffixed with '\_id'. -- **conditions**: An array of ``find()`` compatible conditions. If you have - conditions on an associated table, you should use a 'through' model, and - define the necessary belongsTo associations on it. It is recommended to - use the ``finder`` option instead. -- **sort**: an array of find() compatible order clauses. -- **dependent**: When the dependent key is set to ``false``, and an entity is - deleted, the data of the join table will not be deleted. -- **through**: Allows you to provide either the alias of the Table instance you - want used on the join table, or the instance itself. This makes customizing - the join table keys possible, and allows you to customize the behavior of the - pivot table. -- **cascadeCallbacks**: When this is ``true``, cascaded deletes will load and - delete entities so that callbacks are properly triggered on join table - records. When ``false``, ``deleteAll()`` is used to remove associated data and - no callbacks are triggered. This defaults to ``false`` to help reduce - overhead. -- **propertyName**: The property name that should be filled with data from the - associated table into the source table results. By default this is the - underscored & plural name of the association, so ``tags`` in our example. -- **strategy**: Defines the query strategy to use. Defaults to 'select'. The - other valid value is 'subquery', which replaces the ``IN`` list with an - equivalent subquery. -- **saveStrategy**: Either 'append' or 'replace'. Defaults to 'replace'. - Indicates the mode to be used for saving associated entities. The former will - only create new links between both side of the relation and the latter will - do a wipe and replace to create the links between the passed entities when - saving. -- **finder**: The finder method to use when loading associated records. See the - :ref:`association-finder` section for more information. - -Once this association has been defined, find operations on the Articles table can -contain the Tag records if they exist:: - - // In a controller or table method. - $query = $articles->find('all')->contain(['Tags'])->all(); - foreach ($query as $article) { - echo $article->tags[0]->text; - } - -The above would output SQL similar to: - -.. code-block:: sql - - SELECT * FROM articles; - SELECT * FROM tags - INNER JOIN articles_tags ON ( - tags.id = article_tags.tag_id - AND article_id IN (1, 2, 3, 4, 5) - ); - -When the subquery strategy is used, SQL similar to the following will be -generated: - -.. code-block:: sql - - SELECT * FROM articles; - SELECT * FROM tags - INNER JOIN articles_tags ON ( - tags.id = article_tags.tag_id - AND article_id IN (SELECT id FROM articles) - ); - -.. _using-the-through-option: - -Using the 'through' Option --------------------------- - -If you plan on adding extra information to the join/pivot table, or if you need -to use join columns outside of the conventions, you will need to define the -``through`` option. The ``through`` option provides you full control over how -the belongsToMany association will be created. - -It is sometimes desirable to store additional data with a many to many -association. Consider the following:: - - Student BelongsToMany Course - Course BelongsToMany Student - -A Student can take many Courses and a Course can be taken by many Students. This -is a simple many to many association. The following table would suffice:: - - id | student_id | course_id - -Now what if we want to store the number of days that were attended by the -student on the course and their final grade? The table we'd want would be:: - - id | student_id | course_id | days_attended | grade - -The way to implement our requirement is to use a **join model**, otherwise known -as a **hasMany through** association. That is, the association is a model -itself. So, we can create a new model CoursesMemberships. Take a look at the -following models:: - - class StudentsTable extends Table - { - public function initialize(array $config): void - { - $this->belongsToMany('Courses', [ - 'through' => 'CoursesMemberships', - ]); - } - } - - class CoursesTable extends Table - { - public function initialize(array $config): void - { - $this->belongsToMany('Students', [ - 'through' => 'CoursesMemberships', - ]); - } - } - - class CoursesMembershipsTable extends Table - { - public function initialize(array $config): void - { - $this->belongsTo('Students'); - $this->belongsTo('Courses'); - } - } - -The CoursesMemberships join table uniquely identifies a given Student's -participation on a Course in addition to extra meta-information. - -When using a query object with a BelongsToMany relationship with a ``through`` -model, add contain and matching conditions for the association target table into -your query object. The ``through`` table can then be referenced in other conditions -such as a where condition by designating the through table name before the field -you are filtering on:: - - $query = $this->find( - 'list', - valueField: 'studentFirstName', order: 'students.id' - ) - ->contain(['Courses']) - ->matching('Courses') - ->where(['CoursesMemberships.grade' => 'B']); - -.. _association-finder: - -Using Association Finders -------------------------- - -By default associations will load records based on the foreign key columns. If -you want to define additional conditions for associations, you can use -a ``finder``. When an association is loaded the ORM will use your :ref:`custom -finder ` to load, update, or delete associated records. -Using finders lets you encapsulate your queries and make them more reusable. -There are some limitations when using finders to load data in associations that -are loaded using joins (belongsTo/hasOne). Only the following aspects of the -query will be applied to the root query: - -- Where conditions. -- Additional joins. -- Contained associations. - -Other aspects of the query, such as selected columns, order, group by, having -and other sub-statements, will not be applied to the root query. Associations -that are *not* loaded through joins (hasMany/belongsToMany), do not have the -above restrictions and can also use result formatters or map/reduce functions. - -Association Conventions -======================= - -By default, associations should be configured and referenced using the CamelCase style. -This enables property chains to related tables in the following way:: - - $this->MyTableOne->MyTableTwo->find()->...; - -Association properties on entities do not use CamelCase conventions though. Instead for a hasOne/belongsTo relation like "User belongsTo Roles", you would get a `role` property instead of `Role` or `Roles`:: - - // A single entity (or null if not available) - $role = $user->role; - -Whereas for the other direction "Roles hasMany Users" it would be:: - - // Collection of user entities (or null if not available) - $users = $role->users; - -Loading Associations -==================== - -Once you've defined your associations you can :ref:`eager load associations -` when fetching results. diff --git a/en/orm/behaviors.rst b/en/orm/behaviors.rst deleted file mode 100644 index c3c3aa8ea5..0000000000 --- a/en/orm/behaviors.rst +++ /dev/null @@ -1,327 +0,0 @@ -Behaviors -######### - -Behaviors are a way to organize and enable horizontal re-use of Model layer -logic. Conceptually they are similar to traits. However, behaviors are -implemented as separate classes. This allows them to hook into the -life-cycle callbacks that models emit, while providing trait-like features. - -Behaviors provide a convenient way to package up behavior that is common across -many models. For example, CakePHP includes a ``TimestampBehavior``. Many -models will want timestamp fields, and the logic to manage these fields is -not specific to any one model. It is these kinds of scenarios that behaviors are -a perfect fit for. - -Using Behaviors -=============== - -.. include:: ./table-objects.rst - :start-after: start-behaviors - :end-before: end-behaviors - -Core Behaviors -============== - -.. toctree:: - :maxdepth: 1 - - /orm/behaviors/counter-cache - /orm/behaviors/timestamp - /orm/behaviors/translate - /orm/behaviors/tree - -Creating a Behavior -=================== - -In the following examples we will create a very simple ``SluggableBehavior``. -This behavior will allow us to populate a slug field with the results of -``Text::slug()`` based on another field. - -Before we create our behavior we should understand the conventions for -behaviors: - -- Behavior files are located in **src/Model/Behavior**, or - ``MyPlugin\Model\Behavior``. -- Behavior classes should be in the ``App\Model\Behavior`` namespace, or - ``MyPlugin\Model\Behavior`` namespace. -- Behavior class names end in ``Behavior``. -- Behaviors extend ``Cake\ORM\Behavior``. - -To create our sluggable behavior. Put the following into -**src/Model/Behavior/SluggableBehavior.php**:: - - namespace App\Model\Behavior; - - use Cake\ORM\Behavior; - - class SluggableBehavior extends Behavior - { - } - -Similar to tables, behaviors also have an ``initialize()`` hook where you can -put your behavior's initialization code, if required:: - - public function initialize(array $config): void - { - // Some initialization code here - } - -We can now add this behavior to one of our table classes. In this example we'll -use an ``ArticlesTable``, as articles often have slug properties for creating -friendly URLs:: - - namespace App\Model\Table; - - use Cake\ORM\Table; - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->addBehavior('Sluggable'); - } - } - -Our new behavior doesn't do much of anything right now. Next, we'll add a mixin -method and an event listener so that when we save entities we can automatically -slug a field. - -Defining Mixin Methods ----------------------- - -Any public method defined on a behavior will be added as a 'mixin' method on the -table object it is attached to. If you attach two behaviors that provide the -same methods an exception will be raised. If a behavior provides the same method -as a table class, the behavior method will not be callable from the table. -Behavior mixin methods will receive the exact same arguments that are provided -to the table. For example, if our SluggableBehavior defined the following -method:: - - public function slug($value) - { - return Text::slug($value, $this->_config['replacement']); - } - -It could be invoked using:: - - $slug = $articles->slug('My article name'); - -Limiting or Renaming Exposed Mixin Methods -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When creating behaviors, there may be situations where you don't want to expose -public methods as mixin methods. In these cases you can use the -``implementedMethods`` configuration key to rename or exclude mixin methods. For -example if we wanted to prefix our slug() method we could do the following:: - - protected $_defaultConfig = [ - 'implementedMethods' => [ - 'superSlug' => 'slug', - ] - ]; - -Applying this configuration will make ``slug()`` not callable, however it will -add a ``superSlug()`` mixin method to the table. Notably if our behavior -implemented other public methods they would **not** be available as mixin -methods with the above configuration. - -Since the exposed methods are decided by configuration you can also -rename/remove mixin methods when adding a behavior to a table. For example:: - - // In a table's initialize() method. - $this->addBehavior('Sluggable', [ - 'implementedMethods' => [ - 'superSlug' => 'slug', - ] - ]); - -Defining Event Listeners ------------------------- - -Now that our behavior has a mixin method to slug fields, we can implement -a callback listener to automatically slug a field when entities are saved. We'll -also modify our slug method to accept an entity instead of just a plain value. Our -behavior should now look like:: - - namespace App\Model\Behavior; - - use ArrayObject; - use Cake\Datasource\EntityInterface; - use Cake\Event\EventInterface; - use Cake\ORM\Behavior; - use Cake\ORM\Entity; - use Cake\ORM\Query\SelectQuery; - use Cake\Utility\Text; - - class SluggableBehavior extends Behavior - { - protected array $_defaultConfig = [ - 'field' => 'title', - 'slug' => 'slug', - 'replacement' => '-', - ]; - - public function slug(EntityInterface $entity) - { - $config = $this->getConfig(); - $value = $entity->get($config['field']); - $entity->set($config['slug'], Text::slug($value, $config['replacement'])); - } - - public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) - { - $this->slug($entity); - } - - } - -The above code shows a few interesting features of behaviors: - -- Behaviors can define callback methods by defining methods that follow the - :ref:`table-callbacks` conventions. -- Behaviors can define a default configuration property. This property is merged - with the overrides when a behavior is attached to the table. - -To prevent the save from continuing, simply stop event propagation in your callback:: - - public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) - { - if (...) { - $event->stopPropagation(); - $event->setResult(false); - - return; - } - $this->slug($entity); - } - -Alternatively, you can return false from the callback. This has the same effect as stopping event propagation. - -Defining Finders ----------------- - -Now that we are able to save articles with slug values, we should implement -a finder method so we can fetch articles by their slug. Behavior finder -methods, use the same conventions as :ref:`custom-find-methods` do. Our -``find('slug')`` method would look like:: - - public function findSlug(SelectQuery $query, string $slug): SelectQuery - { - return $query->where(['slug' => $slug]); - } - -Once our behavior has the above method we can call it:: - - $article = $articles->find('slug', slug: $value)->first(); - -Limiting or Renaming Exposed Finder Methods -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When creating behaviors, there may be situations where you don't want to expose -finder methods, or you need to rename finders to avoid duplicated methods. In -these cases you can use the ``implementedFinders`` configuration key to rename -or exclude finder methods. For example if we wanted to rename our ``find(slug)`` -method we could do the following:: - - protected array $_defaultConfig = [ - 'implementedFinders' => [ - 'slugged' => 'findSlug', - ] - ]; - -Applying this configuration will make ``find('slug')`` trigger an error. However -it will make ``find('slugged')`` available. Notably if our behavior implemented -other finder methods they would **not** be available, as they are not included -in the configuration. - -Since the exposed methods are decided by configuration you can also -rename/remove finder methods when adding a behavior to a table. For example:: - - // In a table's initialize() method. - $this->addBehavior('Sluggable', [ - 'implementedFinders' => [ - 'slugged' => 'findSlug', - ] - ]); - -Transforming Request Data into Entity Properties -================================================ - -Behaviors can define logic for how the custom fields they provide are -marshalled by implementing the ``Cake\ORM\PropertyMarshalInterface``. This -interface requires a single method to be implemented:: - - public function buildMarshalMap($marshaller, $map, $options) - { - return [ - 'custom_behavior_field' => function ($value, $entity) { - // Transform the value as necessary - return $value . '123'; - } - ]; - } - -The ``TranslateBehavior`` has a non-trivial implementation of this interface -that you might want to refer to. - -Removing Loaded Behaviors -========================= - -To remove a behavior from your table you can call the ``removeBehavior()`` method:: - - // Remove the loaded behavior - $this->removeBehavior('Sluggable'); - -Accessing Loaded Behaviors -========================== - -Once you've attached behaviors to your Table instance you can introspect the -loaded behaviors, or access specific behaviors using the ``BehaviorRegistry``:: - - // See which behaviors are loaded - $table->behaviors()->loaded(); - - // Check if a specific behavior is loaded. - // Remember to omit plugin prefixes. - $table->behaviors()->has('CounterCache'); - - // Get a loaded behavior - // Remember to omit plugin prefixes - $table->behaviors()->get('CounterCache'); - -Re-configuring Loaded Behaviors -------------------------------- - -To modify the configuration of an already loaded behavior you can combine the -``BehaviorRegistry::get`` command with ``config`` command provided by the -``InstanceConfigTrait`` trait. - -For example, if a parent class, such as ``AppTable``, loaded the ``Timestamp`` -behavior you could do the following to add, modify or remove the configurations -for the behavior. In this case, we will add an event we want Timestamp to -respond to:: - - namespace App\Model\Table; - - use App\Model\Table\AppTable; // similar to AppController - - class UsersTable extends AppTable - { - public function initialize(array $options): void - { - parent::initialize($options); - - // For example, if our parent calls $this->addBehavior('Timestamp') - // and we want to add an additional event - if ($this->behaviors()->has('Timestamp')) { - $this->behaviors()->get('Timestamp')->setConfig([ - 'events' => [ - 'Users.login' => [ - 'last_login' => 'always' - ], - ], - ]); - } - } - } - diff --git a/en/orm/behaviors/counter-cache.rst b/en/orm/behaviors/counter-cache.rst deleted file mode 100644 index fd633ee6a3..0000000000 --- a/en/orm/behaviors/counter-cache.rst +++ /dev/null @@ -1,189 +0,0 @@ -CounterCache -############ - -.. php:namespace:: Cake\ORM\Behavior - -.. php:class:: CounterCacheBehavior - -Often times web applications need to display counts of related objects. For -example, when showing a list of articles you may want to display how many -comments it has. Or when showing a user you might want to show how many -friends/followers she has. The CounterCache behavior is intended for these -situations. CounterCache will update a field in the associated models assigned -in the options when it is invoked. The fields should exist in the database and -be of the type INT. - -Basic Usage -=========== - -You enable the CounterCache behavior like any other behavior, but it won't do -anything until you configure some relations and the field counts that should be -stored on each of them. Using our example below, we could cache the comment -count for each article with the following:: - - class CommentsTable extends Table - { - public function initialize(array $config): void - { - $this->addBehavior('CounterCache', [ - 'Articles' => ['comment_count'] - ]); - } - } - - -.. note:: - - The column ``comment_count`` should exist in the ``articles`` table. - -The CounterCache configuration should be a map of relation names and the -specific configuration for that relation. - -As you see you need to add the behavior on the "other side" of the association -where you actually want the field to be updated. In this example the behavior -is added to the ``CommentsTable`` even though it updates the ``comment_count`` -field in the ``ArticlesTable``. - -The counter's value will be updated each time an entity is saved or deleted. -The counter **will not** be updated when you - -- save the entity without changing data or -- use ``updateAll()`` or -- use ``deleteAll()`` or -- execute SQL you have written - -Advanced Usage -============== - -If you need to keep a cached counter for less than all of the related records, -you can supply additional conditions or finder methods to generate a -counter value:: - - // Use a specific find method. - // In this case find(published) - $this->addBehavior('CounterCache', [ - 'Articles' => [ - 'comment_count' => [ - 'finder' => 'published', - ], - ], - ]); - -If you don't have a custom finder method you can provide an array of conditions -to find records instead:: - - $this->addBehavior('CounterCache', [ - 'Articles' => [ - 'comment_count' => [ - 'conditions' => ['Comments.spam' => false], - ], - ], - ]); - -If you want CounterCache to update multiple fields, for example both showing a -conditional count and a basic count you can add these fields in the array:: - - $this->addBehavior('CounterCache', [ - 'Articles' => ['comment_count', - 'published_comment_count' => [ - 'finder' => 'published', - ], - ], - ]); - -If you want to calculate the CounterCache field value on your own, you can set -the ``ignoreDirty`` option to ``true``. -This will prevent the field from being recalculated if you've set it dirty -before:: - - $this->addBehavior('CounterCache', [ - 'Articles' => [ - 'comment_count' => [ - 'ignoreDirty' => true, - ], - ], - ]); - -Lastly, if a custom finder and conditions are not suitable you can provide -a callback function. Your function must return the count value to be stored:: - - $this->addBehavior('CounterCache', [ - 'Articles' => [ - 'rating_avg' => function ($event, $entity, $table, $original) { - return 4.5; - } - ], - ]); - -Your function can return ``false`` to skip updating the counter column, or -a ``SelectQuery`` object that produced the count value. If you return a ``SelectQuery`` -object, your query will be used as a subquery in the update statement. The -``$table`` parameter refers to the table object holding the behavior (not the -target relation) for convenience. The callback is invoked at least once with -``$original`` set to ``false``. If the entity-update changes the association -then the callback is invoked a *second* time with ``true``, the return value -then updates the counter of the *previously* associated item. - -.. note:: - - The CounterCache behavior works for ``belongsTo`` associations only. For - example for "Comments belongsTo Articles", you need to add the CounterCache - behavior to the ``CommentsTable`` in order to generate ``comment_count`` for - Articles table. - -.. versionchanged:: 5.1.2 - - As of CakePHP 5.1.2, the counter cache values are updated using a single - query using sub-queries, instead of separate queries, to fetch the count and - update a record. If required you can disable the use of sub-queries by - setting `useSubQuery` key to `false` in the config - `['Articles' => ['comment_count' => ['useSubQuery' => false]]` - -Belongs to many Usage -===================== - -It is possible to use the CounterCache behavior in a ``belongsToMany`` association. -First, you need to add the ``through`` and ``cascadeCallbacks`` options to the -``belongsToMany`` association:: - - 'through' => 'CommentsArticles', - 'cascadeCallbacks' => true - -Also see :ref:`using-the-through-option` how to configure a custom join table. - -The ``CommentsArticles`` is the name of the junction table classname. -If you don't have it you should create it with the bake CLI tool. - -In this ``src/Model/Table/CommentsArticlesTable.php`` you then need to add the behavior -with the same code as described above.:: - - $this->addBehavior('CounterCache', [ - 'Articles' => ['comments_count'], - ]); - -Finally clear all caches with ``bin/cake cache clear_all`` and try it out. - -Manually updating counter caches -================================ - -.. php:method:: updateCounterCache(?string $assocName = null, int $limit = 100, ?int $page = null): void - -The ``updateCounterCache()`` method allows you to update the counter cache values -for all records of one or all configured associations in batches. This can be useful, -for example, to update the counter cache after importing data directly into the database.:: - - // Update the counter cache for all configured associations - $table->updateCounterCache(); - - // Update the counter cache for a specific association, 200 records per batch - $table->updateCounterCache('Articles', 200); - - // Update only the first page of records - $table->updateCounterCache('Articles', page: 1); - -.. versionadded:: 5.2.0 - -.. note:: - - This methods won't update the counter cache values for fields which are - configured to use a closure to get the count value. diff --git a/en/orm/behaviors/timestamp.rst b/en/orm/behaviors/timestamp.rst deleted file mode 100644 index 7321cd2817..0000000000 --- a/en/orm/behaviors/timestamp.rst +++ /dev/null @@ -1,87 +0,0 @@ -Timestamp -######### - -.. php:namespace:: Cake\ORM\Behavior - -.. php:class:: TimestampBehavior - -The timestamp behavior allows your table objects to update one or more -timestamps on each model event. This is primarily used to populate data into -``created`` and ``modified`` fields. However, with some additional -configuration, you can update any timestamp/datetime column on any event a table -publishes. - -Basic Usage -=========== - -You enable the timestamp behavior like any other behavior:: - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->addBehavior('Timestamp'); - } - } - -The default configuration will do the following: - -- When a new entity is saved the ``created`` and ``modified`` fields will be set - to the current time. -- When an entity is updated, the ``modified`` field is set to the current time. - -Using and Configuring the Behavior -================================== - -If you need to modify fields with different names, or want to update additional -timestamp fields on custom events you can use some additional configuration:: - - class OrdersTable extends Table - { - public function initialize(array $config): void - { - $this->addBehavior('Timestamp', [ - 'events' => [ - 'Model.beforeSave' => [ - 'created_at' => 'new', - 'updated_at' => 'always', - ], - 'Orders.completed' => [ - 'completed_at' => 'always' - ] - ] - ]); - } - } - -As you can see above, in addition to the standard ``Model.beforeSave`` event, we -are also updating the ``completed_at`` column when orders are completed. - -Updating Timestamps on Entities -=============================== - -Sometimes you'll want to update just the timestamps on an entity without -changing any other properties. This is sometimes referred to as 'touching' -a record. In CakePHP you can use the ``touch()`` method to do exactly this:: - - // Touch based on the Model.beforeSave event. - $articles->touch($article); - - // Touch based on a specific event. - $orders->touch($order, 'Orders.completed'); - -After you have saved the entity, the field is updated. - -Touching records can be useful when you want to signal that a parent resource -has changed when a child resource is created/updated. For example: updating an -article when a new comment is added. - -Saving Updates Without Modifying Timestamps -=========================================== - -To disable the automatic modification of the ``updated`` timestamp column when -saving an entity you can mark the attribute as 'dirty':: - - // Mark the modified column as dirty making - // the current value be set on update. - $order->setDirty('modified', true); diff --git a/en/orm/behaviors/translate.rst b/en/orm/behaviors/translate.rst deleted file mode 100644 index 9a0f8f90a2..0000000000 --- a/en/orm/behaviors/translate.rst +++ /dev/null @@ -1,525 +0,0 @@ -Translate -######### - -.. php:namespace:: Cake\ORM\Behavior - -.. php:class:: TranslateBehavior - -The Translate behavior allows you to create and retrieve translated copies -of your entities in multiple languages. - -.. warning:: - - The TranslateBehavior does not support composite primary keys at this point - in time. - -Translation Strategies -====================== - -The behavior offers two strategies for how the translations are stored. - -1. Shadow table Strategy: This strategy uses a separate "shadow table" for each - Table object to store translation of all translated fields of that table. - This is the default strategy. -2. Eav Strategy: This strategy uses a ``i18n`` table where it stores the - translation for each of the fields of any given Table object that it's bound to. - -Shadow Table Strategy -===================== - -Let's assume we have an ``articles`` table and we want it's ``title`` and ``body`` -fields to be translated. For that we create a shadow table ``articles_translations``: - -.. code-block:: sql - - CREATE TABLE `articles_translations` ( - `id` int(11) NOT NULL, - `locale` varchar(5) NOT NULL, - `title` varchar(255), - `body` text, - PRIMARY KEY (`id`,`locale`) - ); - -The shadow table needs ``id`` and ``locale`` columns which together -form the primary key and other columns with same name as primary table which -need to be translated. - -A note on language abbreviations: The Translate Behavior doesn't impose any -restrictions on the language identifier, the possible values are only restricted -by the ``locale`` column type/size. ``locale`` is defined as ``varchar(6)`` in -case you want to use abbreviations like ``es-419`` (Spanish for Latin America, -language abbreviation with area code `UN M.49 -`_). - -.. tip:: - - It's wise to use the same language abbreviations as required for - :doc:`Internationalization and Localization - `. Thus you are - consistent and switching the language works identical for both, the - ``Translate`` behavior and ``Internationalization and Localization``. - -So it's recommended to use either the two letter ISO code of the language like -``en``, ``fr``, ``de`` or the full locale name such as ``fr_FR``, ``es_AR``, -``da_DK`` which contains both the language and the country where it is spoken. - -Eav Strategy -============ - -In order to use the Eav strategy, you need to create a ``i18n`` table with the -correct schema. Currently the only way of loading the ``i18n`` table is by -manually running the following SQL script in your database: - -.. code-block:: sql - - CREATE TABLE i18n ( - id int NOT NULL auto_increment, - locale varchar(6) NOT NULL, - model varchar(255) NOT NULL, - foreign_key int(10) NOT NULL, - field varchar(255) NOT NULL, - content text, - PRIMARY KEY (id), - UNIQUE INDEX I18N_LOCALE_FIELD(locale, model, foreign_key, field), - INDEX I18N_FIELD(model, foreign_key, field) - ); - -The schema is also available as sql file in **/config/schema/i18n.sql**. - -Attaching the Translate Behavior to Your Tables -=============================================== - -Attaching the behavior can be done in the ``initialize()`` method in your Table -class:: - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - // By default ShadowTable will be used. - $this->addBehavior('Translate', ['fields' => ['title', 'body']]); - } - } - -For shadow table strategy specifying the ``fields`` key is optional as the -behavior can infer the fields from the shadow table columns. - -If you want to use the ``EavStrategy`` then you can configure the behavior -as:: - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->addBehavior('Translate', [ - 'strategyClass' => \Cake\ORM\Behavior\Translate\EavStrategy::class, - 'fields' => ['title', 'body'], - ]); - } - } - -For ``EavStrategy`` you are required to pass the ``fields`` key in the -configuration array. This list of fields is needed to tell the behavior what -columns will be able to store translations. - -By default the locale specified in ``App.defaultLocale`` config is used as default -locale for the ``TranslateBehavior``. You can override that by setting ``defaultLocale`` -config of the behavior:: - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->addBehavior('Translate', [ - 'defaultLocale' => 'en_GB', - ]); - } - } - -Quick tour -========== - -Regardless of the datastructure strategy you choose the behavior provides the -same API to manage translations. - -Now, select a language to be used for retrieving entities by changing -the application language, which will affect all translations:: - - // In the Articles controller. Change the locale to Spanish, for example - I18n::setLocale('es'); - -Then, get an existing entity:: - - $article = $this->Articles->get(12); - echo $article->title; // Echoes 'A title', not translated yet - -Next, translate your entity:: - - $article->title = 'Un Artículo'; - $this->Articles->save($article); - -You can try now getting your entity again:: - - $article = $this->Articles->get(12); - echo $article->title; // Echoes 'Un Artículo', yay piece of cake! - -Working with multiple translations can be done by using a special trait -in your Entity class:: - - use Cake\ORM\Behavior\Translate\TranslateTrait; - use Cake\ORM\Entity; - - class Article extends Entity - { - use TranslateTrait; - } - -Now you can find all translations for a single entity:: - - $article = $this->Articles->find('translations')->first(); - echo $article->translation('es')->title; // 'Un Artículo' - - echo $article->translation('en')->title; // 'An Article'; - -And save multiple translations at once:: - - $article->translation('es')->title = 'Otro Título'; - $article->translation('fr')->title = 'Un autre Titre'; - $this->Articles->save($article); - -If you want to go deeper on how it works or how to tune the -behavior for your needs, keep on reading the rest of this chapter. - - -Using a Separate Translations Table for Eav strategy ----------------------------------------------------- - -If you wish to use a table other than ``i18n`` for translating a particular -repository, you can specify the name of the table class name for your custom -table in the behavior's configuration. This is common when you have multiple -tables to translate and you want a cleaner separation of the data that is stored -for each different table:: - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->addBehavior('Translate', [ - 'fields' => ['title', 'body'], - 'translationTable' => 'ArticlesI18n', - ]); - } - } - -You need to make sure that any custom table you use has the columns ``field``, -``foreign_key``, ``locale`` and ``model``. - -Reading Translated Content -========================== - -As shown above you can use the ``setLocale()`` method to choose the active -translation for entities that are loaded:: - - // Load I18n core functions at the beginning of your Articles Controller: - use Cake\I18n\I18n; - - // Then you can change the language in your action: - I18n::setLocale('es'); - - // All entities in results will contain spanish translation - $results = $this->Articles->find()->all(); - -This method works with any finder in your tables. For example, you can -use TranslateBehavior with ``find('list')``:: - - I18n::setLocale('es'); - $data = $this->Articles->find('list')->toArray(); - - // Data will contain - [1 => 'Mi primer artículo', 2 => 'El segundo artículo', 15 => 'Otro articulo' ...] - - // Change the locale to french for a single find call - $data = $this->Articles->find('list', locale: 'fr')->toArray(); - -Retrieve All Translations For An Entity ---------------------------------------- - -When building interfaces for updating translated content, it is often helpful to -show one or more translation(s) at the same time. You can use the -``translations`` finder for this:: - - // Find the first article with all corresponding translations - $article = $this->Articles->find('translations')->first(); - -In the example above you will get a list of entities back that have a -``_translations`` property set. This property will contain a list of translation -data entities. For example the following properties would be accessible:: - - // Outputs 'en' - echo $article->_translations['en']->locale; - - // Outputs 'title' - echo $article->_translations['en']->field; - - // Outputs 'My awesome post!' - echo $article->_translations['en']->body; - -A more elegant way for dealing with this data is by adding a trait to the entity -class that is used for your table:: - - use Cake\ORM\Behavior\Translate\TranslateTrait; - use Cake\ORM\Entity; - - class Article extends Entity - { - use TranslateTrait; - } - -This trait contains a single method called ``translation``, which lets you -access or create new translation entities on the fly:: - - // Outputs 'title' - echo $article->translation('en')->title; - - // Adds a new translation data entity to the article - $article->translation('de')->title = 'Wunderbar'; - -Limiting the Translations to be Retrieved ------------------------------------------ - -You can limit the languages that are fetched from the database for a particular -set of records:: - - $results = $this->Articles->find('translations', locales: ['en', 'es']); - $article = $results->first(); - $spanishTranslation = $article->translation('es'); - $englishTranslation = $article->translation('en'); - -Preventing Retrieval of Empty Translations ------------------------------------------- - -Translation records can contain any string, if a record has been translated -and stored as an empty string ('') the translate behavior will take and use -this to overwrite the original field value. - -If this is undesired, you can ignore translations which are empty using the -``allowEmptyTranslations`` config key:: - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->addBehavior('Translate', [ - 'fields' => ['title', 'body'], - 'allowEmptyTranslations' => false - ]); - } - } - -The above would only load translated data that had content. - -Retrieving All Translations For Associations --------------------------------------------- - -It is also possible to find translations for any association in a single find -operation:: - - $article = $this->Articles->find('translations')->contain([ - 'Categories' => function ($query) { - return $query->find('translations'); - } - ])->first(); - - // Outputs 'Programación' - echo $article->categories[0]->translation('es')->name; - -This assumes that ``Categories`` has the TranslateBehavior attached to it. It -simply uses the query builder function for the ``contain`` clause to use the -``translations`` custom finder in the association. - -.. _retrieving-one-language-without-using-i18n-locale: - -Retrieving one language without using I18n::setLocale ------------------------------------------------------ - -calling ``I18n::setLocale('es');`` changes the default locale for all translated -finds, there may be times you wish to retrieve translated content without -modifying the application's state. For these scenarios use the behavior's -``setLocale()`` method:: - - I18n::setLocale('en'); // reset for illustration - - // specific locale. - $this->Articles->setLocale('es'); - - $article = $this->Articles->get(12); - echo $article->title; // Echoes 'Un Artículo', yay piece of cake! - -Note that this only changes the locale of the Articles table, it would not -affect the language of associated data. To affect associated data it's necessary -to call the method on each table, for example:: - - I18n::setLocale('en'); // reset for illustration - - $this->Articles->setLocale('es'); - $this->Articles->Categories->setLocale('es'); - - $data = $this->Articles->find('all', contain: ['Categories']); - -This example also assumes that ``Categories`` has the TranslateBehavior attached -to it. - -Querying Translated Fields --------------------------- - -TranslateBehavior does not substitute find conditions by default. You need to use -``translationField()`` method to compose find conditions on translated fields:: - - $this->Articles->setLocale('es'); - $query = $this->Articles->find()->where([ - $this->Articles->translationField('title') => 'Otro Título' - ]); - -Saving in Another Language -========================== - -The philosophy behind the TranslateBehavior is that you have an entity -representing the default language, and multiple translations that can override -certain fields in such entity. Keeping this in mind, you can intuitively save -translations for any given entity. For example, given the following setup:: - - // in src/Model/Table/ArticlesTable.php - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->addBehavior('Translate', ['fields' => ['title', 'body']]); - } - } - - // in src/Model/Entity/Article.php - class Article extends Entity - { - use TranslateTrait; - } - - // In the Articles Controller - $article = new Article([ - 'title' => 'My First Article', - 'body' => 'This is the content', - 'footnote' => 'Some afterwords' - ]); - - $this->Articles->save($article); - -So, after you save your first article, you can now save a translation for it, -there are a couple ways to do it. The first one is setting the language directly -into the entity:: - - $article->_locale = 'es'; - $article->title = 'Mi primer Artículo'; - - $this->Articles->save($article); - -After the entity has been saved, the translated field will be persisted as well, -one thing to note is that values from the default language that were not -overridden will be preserved:: - - // Outputs 'This is the content' - echo $article->body; - - // Outputs 'Mi primer Artículo' - echo $article->title; - -Once you override the value, the translation for that field will be saved and -can be retrieved as usual:: - - $article->body = 'El contendio'; - $this->Articles->save($article); - -The second way to use for saving entities in another language is to set the -default language directly to the table:: - - $article->title = 'Mi Primer Artículo'; - - $this->Articles->setLocale('es'); - $this->Articles->save($article); - -Setting the language directly in the table is useful when you need to both -retrieve and save entities for the same language or when you need to save -multiple entities at once. - -.. _saving-multiple-translations: - -Saving Multiple Translations -============================ - -It is a common requirement to be able to add or edit multiple translations to -any database record at the same time. This can be done using the -``TranslateTrait``:: - - use Cake\ORM\Behavior\Translate\TranslateTrait; - use Cake\ORM\Entity; - - class Article extends Entity - { - use TranslateTrait; - } - -Now, You can populate translations before saving them:: - - $translations = [ - 'fr' => ['title' => "Un article"], - 'es' => ['title' => 'Un artículo'], - ]; - - foreach ($translations as $lang => $data) { - $article->translation($lang)->set($data, ['guard' => false]); - } - - $this->Articles->save($article); - -And create form controls for your translated fields:: - - // In a view template. - Form->create($article); ?> -
      - French - Form->control('_translations.fr.title'); ?> - Form->control('_translations.fr.body'); ?> -
      -
      - Spanish - Form->control('_translations.es.title'); ?> - Form->control('_translations.es.body'); ?> -
      - -In your controller, you can marshal the data as normal:: - - $article = $this->Articles->newEntity($this->request->getData()); - $this->Articles->save($article); - -This will result in your article, the french and spanish translations all being -persisted. You'll need to remember to add ``_translations`` into the -``$_accessible`` fields of your entity as well. - -Validating Translated Entities ------------------------------- - -When attaching ``TranslateBehavior`` to a model, you can define the validator -that should be used when translation records are created/modified by the -behavior during ``newEntity()`` or ``patchEntity()``:: - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->addBehavior('Translate', [ - 'fields' => ['title'], - 'validator' => 'translated', - ]); - } - } - -The above will use the validator created by ``validationTranslated`` to -validated translated entities. diff --git a/en/orm/behaviors/tree.rst b/en/orm/behaviors/tree.rst deleted file mode 100644 index c8f321301c..0000000000 --- a/en/orm/behaviors/tree.rst +++ /dev/null @@ -1,338 +0,0 @@ -Tree -#### - -.. php:namespace:: Cake\ORM\Behavior - -.. php:class:: TreeBehavior - -It's fairly common to want to store hierarchical data in a database -table. Examples of such data might be categories with unlimited -subcategories, data related to a multilevel menu system or a -literal representation of hierarchy such as departments in a company. - -Relational databases are usually not well suited for storing and retrieving this -type of data, but there are a few known techniques that can make them effective -for working with multi-level information. - -The TreeBehavior helps you maintain a hierarchical data structure in the -database that can be queried without much overhead and helps reconstruct the -tree data for finding and displaying processes. - -Requirements -============ - -This behavior requires the following columns in your table: - -- ``parent_id`` (nullable) The column holding the ID of the parent row. This column should be indexed. -- ``lft`` (integer, signed) Used to maintain the tree structure. This column should be indexed. -- ``rght`` (integer, signed) Used to maintain the tree structure. - -You can configure the name of those fields should you need to customize them. -More information on the meaning of the fields and how they are used can be found -in this article describing the `MPTT logic `_ - -.. warning:: - - The TreeBehavior does not support composite primary keys at this point in - time. - -A Quick Tour -============ - -You enable the Tree behavior by adding it to the Table you want to store -hierarchical data in:: - - class CategoriesTable extends Table - { - public function initialize(array $config): void - { - $this->addBehavior('Tree'); - } - } - -Once added, you can let CakePHP build the internal structure if the table is -already holding some rows:: - - // In a controller - $categories = $this->getTableLocator()->get('Categories'); - $categories->recover(); - -You can verify it works by getting any row from the table and asking for the -count of descendants it has:: - - $node = $categories->get(1); - echo $categories->childCount($node); - -Getting direct descendents --------------------------- - -Getting a flat list of the descendants for a node can be done with:: - - $descendants = $categories->find('children', for: 1); - - foreach ($descendants as $category) { - echo $category->name . "\n"; - } - -If you need to pass conditions you do so as per normal:: - - $descendants = $categories - ->find('children', for: 1) - ->where(['name LIKE' => '%Foo%']) - ->all(); - - foreach ($descendants as $category) { - echo $category->name . "\n"; - } - -If you instead need a threaded list, where children for each node are nested -in a hierarchy, you can stack the 'threaded' finder:: - - $children = $categories - ->find('children', for: 1) - ->find('threaded') - ->toArray(); - - foreach ($children as $child) { - echo "{$child->name} has " . count($child->children) . " direct children"; - } - -While, if you're using custom ``parent_id`` you need to pass it in the -'threaded' finder option (i.e. ``parentField``) . - -.. note:: - For more information on 'threaded' finder options see :ref:`Finding Threaded Data logic ` - -Getting formatted tree lists ----------------------------- - -Traversing threaded results usually requires recursive functions in, but if you -only require a result set containing a single field from each level so you can -display a list, in an HTML select for example, it is better to use the -``treeList`` finder:: - - $list = $categories->find('treeList')->toArray(); - - // In a CakePHP template file: - echo $this->Form->control('categories', ['options' => $list]); - - // Or you can output it in plain text, for example in a CLI script - foreach ($list as $categoryName) { - echo $categoryName . "\n"; - } - -The output will be similar to:: - - My Categories - _Fun - __Sport - ___Surfing - ___Skating - _Trips - __National - __International - -The ``treeList`` finder takes a number of options: - -* ``keyPath``: A dot separated path to fetch the field to use for the array key, - or a closure to return the key out of the provided row. -* ``valuePath``: A dot separated path to fetch the field to use for the array - value, or a closure to return the value out of the provided row. -* ``spacer``: A string to be used as prefix for denoting the depth in the tree - for each item - -An example of all options in use is:: - - $query = $categories->find('treeList', - keyPath: 'url', - valuePath: 'id', - spacer: ' ' - ); - -An example using closure:: - - $query = $categories->find('treeList', - valuePath: function($entity){ - return $entity->url . ' ' . $entity->id - } - ); - -Finding a path or branch in the tree ------------------------------------- - -One very common task is to find the tree path from a particular node to the root -of the tree. This is useful, for example, for adding the breadcrumbs list for -a menu structure:: - - $nodeId = 5; - $crumbs = $categories->find('path', for: $nodeId)->all(); - - foreach ($crumbs as $crumb) { - echo $crumb->name . ' > '; - } - -Trees constructed with the TreeBehavior cannot be sorted by any column other -than ``lft``, this is because the internal representation of the tree depends on -this sorting. Luckily, you can reorder the nodes inside the same level without -having to change their parent:: - - $node = $categories->get(5); - - // Move the node so it shows up one position up when listing children. - $categories->moveUp($node); - - // Move the node to the top of the list inside the same level. - $categories->moveUp($node, true); - - // Move the node to the bottom. - $categories->moveDown($node, true); - -Configuration -============= - -If the default column names that are used by this behavior don't match your own -schema, you can provide aliases for them:: - - public function initialize(array $config): void - { - $this->addBehavior('Tree', [ - 'parent' => 'ancestor_id', // Use this instead of parent_id - 'left' => 'tree_left', // Use this instead of lft - 'right' => 'tree_right' // Use this instead of rght - ]); - } - -Node Level (Depth) ------------------- - -Knowing the depth of tree nodes can be useful when you want to retrieve nodes -only up to a certain level, for example, when generating menus. You can use the -``level`` option to specify the field that will save level of each node:: - - $this->addBehavior('Tree', [ - 'level' => 'level', // Defaults to null, i.e. no level saving - ]); - -If you don't want to cache the level using a db field you can use -``TreeBehavior::getLevel()`` method to get level of a node. - -Scoping and Multi Trees ------------------------ - -Sometimes you want to persist more than one tree structure inside the same -table, you can achieve that by using the 'scope' configuration. For example, in -a locations table you may want to create one tree per country:: - - class LocationsTable extends Table - { - public function initialize(array $config): void - { - $this->addBehavior('Tree', [ - 'scope' => ['country_name' => 'Brazil'] - ]); - } - } - -In the previous example, all tree operations will be scoped to only the rows -having the column ``country_name`` set to 'Brazil'. You can change the scoping -on the fly by using the 'config' function:: - - $this->behaviors()->Tree->setConfig('scope', ['country_name' => 'France']); - -Optionally, you can have a finer grain control of the scope by passing a closure -as the scope:: - - $this->behaviors()->Tree->setConfig('scope', function ($query) { - $country = $this->getConfigureContry(); // A made-up function - return $query->where(['country_name' => $country]); - }); - -Deletion Behavior ------------------ - -By enabling the ``cascadeCallbacks`` option, ``TreeBehavior`` will load all of -the entities that are going to be deleted. Once loaded, these entities will be -deleted individually using ``Table::delete()``. This enables ORM callbacks to be -fired when tree nodes are deleted:: - - $this->addBehavior('Tree', [ - 'cascadeCallbacks' => true, - ]); - -Recovering with custom sort field -================================= - -By default, ``recover()`` sorts the items using the primary key. This works great -if this is a numeric (auto increment) column, but can lead to weird results if you -use UUIDs. - -If you need custom sorting for the recovery, you can set a -custom order clause in your config:: - - $this->addBehavior('Tree', [ - 'recoverOrder' => ['country_name' => 'DESC'], - ]); - -Saving Hierarchical Data -======================== - -When using the Tree behavior, you usually don't need to worry about the -internal representation of the hierarchical structure. The positions where nodes -are placed in the tree are deduced from the ``parent_id`` column in each of your -entities:: - - $aCategory = $categoriesTable->get(10); - $aCategory->parent_id = 5; - $categoriesTable->save($aCategory); - -Providing inexistent parent ids when saving or attempting to create a loop in -the tree (making a node child of itself) will throw an exception. - -You can make a node into a root in the tree by setting the ``parent_id`` column to -null:: - - $aCategory = $categoriesTable->get(10); - $aCategory->parent_id = null; - $categoriesTable->save($aCategory); - -Children for the new root node will be preserved. - -Deleting Nodes -============== - -Deleting a node and all its sub-tree (any children it may have at any depth in -the tree) is trivial:: - - $aCategory = $categoriesTable->get(10); - $categoriesTable->delete($aCategory); - -The TreeBehavior will take care of all internal deleting operations for you. It -is also possible to only delete one node and re-assign all children to the -immediately superior parent node in the tree:: - - $aCategory = $categoriesTable->get(10); - $categoriesTable->removeFromTree($aCategory); - $categoriesTable->delete($aCategory); - -All children nodes will be kept and a new parent will be assigned to them. - -The deletion of a node is based off of the ``lft`` and ``rght`` values of the entity. This -is important to note when looping through the various children of a node for -conditional deletes:: - - $descendants = $teams->find('children', for: 1)->all(); - - foreach ($descendants as $descendant) { - $team = $teams->get($descendant->id); // search for the up-to-date entity object - if ($team->expired) { - $teams->delete($team); // deletion reorders the lft and rght of database entries - } - } - -TreeBehavior will reorder the ``lft`` and ``rght`` values of records in the -table when a node is deleted. - -In our example above, the ``lft`` and ``rght`` values of the entities inside -``$descendants`` will be inaccurate. You will need to reload existing entity -objects if you need an accurate shape of the tree. - diff --git a/en/orm/database-basics.rst b/en/orm/database-basics.rst deleted file mode 100644 index 6e2c7205c7..0000000000 --- a/en/orm/database-basics.rst +++ /dev/null @@ -1,1108 +0,0 @@ -Database Basics -############### - -The CakePHP database access layer abstracts and provides help with most aspects -of dealing with relational databases such as, keeping connections to the server, -building queries, preventing SQL injections, inspecting and altering schemas, -and with debugging and profiling queries sent to the database. - -Quick Tour -========== - -The functions described in this chapter illustrate what is possible to do with -the lower-level database access API. If instead you want to learn more about the -complete ORM, you can read the :doc:`/orm/query-builder` and -:doc:`/orm/table-objects` sections. - -The easiest way to create a database connection is using a ``DSN`` string:: - - use Cake\Datasource\ConnectionManager; - - $dsn = 'mysql://root:password@localhost/my_database'; - ConnectionManager::setConfig('default', ['url' => $dsn]); - -Once created, you can access the connection object to start using it:: - - $connection = ConnectionManager::get('default'); - -.. note:: - For supported databases, see :doc:`installation notes `. - -.. _running-select-statements: - -Running Select Statements -------------------------- - -Running raw SQL queries is a breeze:: - - use Cake\Datasource\ConnectionManager; - - $connection = ConnectionManager::get('default'); - $results = $connection->execute('SELECT * FROM articles')->fetchAll('assoc'); - -You can use prepared statements to insert parameters:: - - $results = $connection - ->execute('SELECT * FROM articles WHERE id = :id', ['id' => 1]) - ->fetchAll('assoc'); - -It is also possible to use complex data types as arguments:: - - use Cake\Datasource\ConnectionManager; - use DateTime; - - $connection = ConnectionManager::get('default'); - $results = $connection - ->execute( - 'SELECT * FROM articles WHERE created >= :created', - ['created' => new DateTime('1 day ago')], - ['created' => 'datetime'] - ) - ->fetchAll('assoc'); - -Instead of writing the SQL manually, you can use the query builder:: - - // Prior to 4.5 use $connection->query() instead. - $results = $connection - ->selectQuery('*', 'articles') - ->where(['created >' => new DateTime('1 day ago')], ['created' => 'datetime']) - ->order(['title' => 'DESC']) - ->execute() - ->fetchAll('assoc'); - -Running Insert Statements -------------------------- - -Inserting rows in the database is usually a matter of a couple lines:: - - use Cake\Datasource\ConnectionManager; - use DateTime; - - $connection = ConnectionManager::get('default'); - $connection->insert('articles', [ - 'title' => 'A New Article', - 'created' => new DateTime('now') - ], ['created' => 'datetime']); - -Running Update Statements -------------------------- - -Updating rows in the database is equally intuitive, the following example will -update the article with **id** 10:: - - use Cake\Datasource\ConnectionManager; - $connection = ConnectionManager::get('default'); - $connection->update('articles', ['title' => 'New title'], ['id' => 10]); - -Running Delete Statements -------------------------- - -Similarly, the ``delete()`` method is used to delete rows from the database, the -following example deletes the article with **id** 10:: - - use Cake\Datasource\ConnectionManager; - $connection = ConnectionManager::get('default'); - $connection->delete('articles', ['id' => 10]); - -.. _database-configuration: - -Configuration -============= - -By convention database connections are configured in **config/app.php**. The -connection information defined in this file is fed into -:php:class:`Cake\\Datasource\\ConnectionManager` creating the connection configuration -your application will be using. Sample connection information can be found in -**config/app.default.php**. A sample connection configuration would look -like:: - - 'Datasources' => [ - 'default' => [ - 'className' => 'Cake\Database\Connection', - 'driver' => 'Cake\Database\Driver\Mysql', - 'persistent' => false, - 'host' => 'localhost', - 'username' => 'my_app', - 'password' => 'secret', - 'database' => 'my_app', - 'encoding' => 'utf8mb4', - 'timezone' => 'UTC', - 'cacheMetadata' => true, - ], - ], - -The above will create a 'default' connection, with the provided parameters. You -can define as many connections as you want in your configuration file. You can -also define additional connections at runtime using -:php:meth:`Cake\\Datasource\\ConnectionManager::setConfig()`. An example of that -would be:: - - use Cake\Datasource\ConnectionManager; - - ConnectionManager::setConfig('default', [ - 'className' => 'Cake\Database\Connection', - 'driver' => 'Cake\Database\Driver\Mysql', - 'persistent' => false, - 'host' => 'localhost', - 'username' => 'my_app', - 'password' => 'secret', - 'database' => 'my_app', - 'encoding' => 'utf8mb4', - 'timezone' => 'UTC', - 'cacheMetadata' => true, - ]); - -Configuration options can also be provided as a :term:`DSN` string. This is -useful when working with environment variables or :term:`PaaS` providers:: - - ConnectionManager::setConfig('default', [ - 'url' => 'mysql://my_app:sekret@localhost/my_app?encoding=utf8&timezone=UTC&cacheMetadata=true', - ]); - -When using a DSN string you can define any additional parameters/options as -query string arguments. - -By default, all Table objects will use the ``default`` connection. To -use a non-default connection, see :ref:`configuring-table-connections`. - -There are a number of keys supported in database configuration. A full list is -as follows: - -className - The fully namespaced class name of the class that represents the connection to a database server. - This class is responsible for loading the database driver, providing SQL - transaction mechanisms and preparing SQL statements among other things. -driver - The class name of the driver used to implement all specificities for - a database engine. This can either be a short classname using :term:`plugin syntax`, - a fully namespaced name, or a constructed driver instance. - Examples of short classnames are Mysql, Sqlite, Postgres, and Sqlserver. -persistent - Whether or not to use a persistent connection to the database. This option - is not supported by SqlServer. An exception is thrown if you attempt to set - ``persistent`` to ``true`` with SqlServer. -host - The database server's hostname (or IP address). -username - The username for the account. -password - The password for the account. -database - The name of the database for this connection to use. Avoid using ``.`` in - your database name. Because of how it complicates identifier quoting CakePHP - does not support ``.`` in database names. The path to your SQLite database - should be an absolute path (for example, ``ROOT . DS . 'my_app.db'``) to avoid - incorrect paths caused by relative paths. -port (*optional*) - The TCP port or Unix socket used to connect to the server. -encoding - Indicates the character set to use when sending SQL statements to - the server. This defaults to the database's default encoding for - all databases other than DB2. -timezone - Server timezone to set. -schema - Used in PostgreSQL database setups to specify which schema to use. -unix_socket - Used by drivers that support it to connect via Unix socket files. If you are - using PostgreSQL and want to use Unix sockets, leave the host key blank. -ssl_key - The file path to the SSL key file. (Only supported by MySQL). -ssl_cert - The file path to the SSL certificate file. (Only supported by MySQL). -ssl_ca - The file path to the SSL certificate authority. (Only supported by MySQL). -init - A list of queries that should be sent to the database server as - when the connection is created. -log - Set to ``true`` to enable query logging. When enabled queries will be logged - at a ``debug`` level with the ``queriesLog`` scope. -quoteIdentifiers - Set to ``true`` if you are using reserved words or special characters in - your table or column names. Enabling this setting will result in queries - built using the :doc:`/orm/query-builder` having identifiers quoted when - creating SQL. It should be noted that this decreases performance because - each query needs to be traversed and manipulated before being executed. -flags - An associative array of PDO constants that should be passed to the - underlying PDO instance. See the PDO documentation for the flags supported - by the driver you are using. -cacheMetadata - Either boolean ``true``, or a string containing the cache configuration to - store meta data in. Having metadata caching disabled by setting it to ``false`` - is not advised and can result in very poor performance. See the - :ref:`database-metadata-cache` section for more information. -mask - Set the permissions on the generated database file. (Only supported by SQLite) -cache - The ``cache`` flag to send to SQLite. -mode - The ``mode`` flag value to send to SQLite. - - -At this point, you might want to take a look at the -:doc:`/intro/conventions`. The correct naming for your tables (and the addition -of some columns) can score you some free functionality and help you avoid -configuration. For example, if you name your database table big\_boxes, your -table BigBoxesTable, and your controller BigBoxesController, everything will -work together automatically. By convention, use underscores, lower case, and -plural forms for your database table names - for example: bakers, -pastry\_stores, and savory\_cakes. - -.. note:: - - If your MySQL server is configured with ``skip-character-set-client-handshake`` - then you MUST use the ``flags`` config to set your charset encoding. For example:: - - 'flags' => [\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'] - -.. _read-and-write-connections: - -Read and Write Connections -========================== - -Connections can have separate read and write roles. Read -roles are expected to represent read-only replicas and write roles are expected -to be the default connection and support write operations. - -Read roles are configured by providing a ``read`` key in the connection config. -Write roles are configured by providing a ``write`` key. - -Role configurations override the values in the shared connection config. If the read -and write role configurations are the same, a single connection to the database is used -for both:: - - 'default' => [ - 'driver' => 'mysql', - 'username' => '...', - 'password' => '...', - 'database' => '...', - 'read' => [ - 'host' => 'read-db.example.com', - ], - 'write' => [ - 'host' => 'write-db.example.com', - ] - ]; - -You can specify the same value for both ``read`` and ``write`` key without creating -multiple connections to the database. - -.. php:namespace:: Cake\Datasource - -Managing Connections -==================== - -.. php:class:: ConnectionManager - -The ``ConnectionManager`` class acts as a registry to access database -connections your application has. It provides a place that other objects can get -references to existing connections. - -Accessing Connections ---------------------- - -.. php:staticmethod:: get($name) - -Once configured connections can be fetched using -:php:meth:`Cake\\Datasource\\ConnectionManager::get()`. This method will -construct and load a connection if it has not been built before, or return the -existing known connection:: - - use Cake\Datasource\ConnectionManager; - - $connection = ConnectionManager::get('default'); - -Attempting to load connections that do not exist will throw an exception. - -Creating Connections at Runtime -------------------------------- - -Using ``setConfig()`` and ``get()`` you can create new connections that are not -defined in your configuration files at runtime:: - - ConnectionManager::setConfig('my_connection', $config); - $connection = ConnectionManager::get('my_connection'); - -See the :ref:`database-configuration` for more information on the configuration -data used when creating connections. - -.. _database-data-types: - -.. php:namespace:: Cake\Database - -Data Types -========== - -.. php:class:: TypeFactory - -Since not every database vendor includes the same set of data types, or -the same names for similar data types, CakePHP provides a set of abstracted -data types for use with the database layer. The types CakePHP supports are: - -string - Maps to ``VARCHAR`` type. In SQL Server the ``NVARCHAR`` types are used. -char - Maps to ``CHAR`` type. In SQL Server the ``NCHAR`` type is used. -text - Maps to ``TEXT`` types. -uuid - Maps to the UUID type if a database provides one, otherwise this will - generate a ``CHAR(36)`` field. -binaryuuid - Maps to the UUID type if the database provides one, otherwise this will - generate a ``BINARY(16)`` column -nativeuuid - Maps to the UUID type in MySQL with MariaDb. In all other databases, - ``nativeuuid`` is an alias for ``uuid``. -integer - Maps to the ``INTEGER`` type provided by the database. BIT is not yet supported - at this moment. -smallinteger - Maps to the ``SMALLINT`` type provided by the database. -tinyinteger - Maps to the ``TINYINT`` or ``SMALLINT`` type provided by the database. In MySQL - ``TINYINT(1)`` is treated as a boolean. -biginteger - Maps to the ``BIGINT`` type provided by the database. -float - Maps to either ``DOUBLE`` or ``FLOAT`` depending on the database. The ``precision`` - option can be used to define the precision used. -decimal - Maps to the ``DECIMAL`` type. Supports the ``length`` and ``precision`` - options. Values for decimal type ares be represented as strings (not as float - as some might expect). This is because decimal types are used to represent - exact numeric values in databases and using float type for them in PHP can - potentially lead to precision loss. - - If you want the values to be `float` in your PHP code then consider using - `FLOAT` or `DOUBLE` type columns in your database. Also, depending on your use - case you can explicitly map your decimal columns to `float` type in your table - schema. -boolean - Maps to ``BOOLEAN`` except in MySQL, where ``TINYINT(1)`` is used to represent - booleans. ``BIT(1)`` is not yet supported at this moment. -binary - Maps to the ``BLOB`` or ``BYTEA`` type provided by the database. -date - Maps to a native ``DATE`` column type. The return value of this column - type is :php:class:`Cake\\I18n\\Date` which emulates the date related - methods of PHP's ``DateTime`` class. -datetime - See :ref:`datetime-type`. -datetimefractional - See :ref:`datetime-type`. -timestamp - Maps to the ``TIMESTAMP`` type. -timestampfractional - Maps to the ``TIMESTAMP(N)`` type. -time - Maps to a ``TIME`` type in all databases. -json - Maps to a ``JSON`` type if it's available, otherwise it maps to ``TEXT``. -enum - See :ref:`enum-type`. -geometry - Maps to a generic geometry storage type. -point - Maps to a single point in geospatial storage. -linestring - Maps to a single line in geospatial storage. -polygon - Maps to a single polygon in geospatial storage. - -These types are used in both the schema reflection features that CakePHP -provides, and schema generation features CakePHP uses when using test fixtures. - -Each type can also provide translation functions between PHP and SQL -representations. These methods are invoked based on the type hints provided when -doing queries. For example a column that is marked as 'datetime' will -automatically convert input parameters from ``DateTime`` instances into a -timestamp or formatted datestrings. Likewise, 'binary' columns will accept file -handles, and generate file handles when reading data. - -.. versionchanged:: 5.1.0 - The ``geometry``, ``point``, ``linestring``, and ``polygon`` types were - added. - -.. versionchanged:: 5.2.0 - The ``nativeuuid`` type was added. - -.. _datetime-type: - -DateTime Type -------------- - -.. php:class:: DateTimeType - -Maps to a native ``DATETIME`` column type. In PostgreSQL and SQL Server this -turns into a ``TIMESTAMP`` type. The default return value of this column type is -:php:class:`Cake\\I18n\\DateTime` which extends `Chronos -`_ and the native ``DateTimeImmutable``. - -.. php:method:: setTimezone(string|\DateTimeZone|null $timezone) - -If your database server's timezone does not match your application's PHP timezone -then you can use this method to specify your database's timezone. This timezone -will then used when converting PHP objects to database's datetime string and -vice versa. - -.. php:class:: DateTimeFractionalType - -Can be used to map datetime columns that contain microseconds such as -``DATETIME(6)`` in MySQL. To use this type you need to add it as a mapped type:: - - // in config/bootstrap.php - use Cake\Database\TypeFactory; - use Cake\Database\Type\DateTimeFractionalType; - - // Overwrite the default datetime type with a more precise one. - TypeFactory::map('datetime', DateTimeFractionalType::class); - -.. php:class:: DateTimeTimezoneType - -Can be used to map datetime columns that contain time zones such as -``TIMESTAMPTZ`` in PostgreSQL. To use this type you need to add it as a mapped type:: - - // in config/bootstrap.php - use Cake\Database\TypeFactory; - use Cake\Database\Type\DateTimeTimezoneType; - - // Overwrite the default datetime type with a more precise one. - TypeFactory::map('datetime', DateTimeTimezoneType::class); - -.. _enum-type: - -Enum Type ---------- - -.. php:class:: EnumType - -Maps a `BackedEnum `_ to a string or integer column. -To use this type you need to specify which column is associated to which BackedEnum inside the table class:: - - use App\Model\Enum\ArticleStatus; - use Cake\Database\Type\EnumType; - - // in src/Model/Table/ArticlesTable.php - public function initialize(array $config): void - { - parent::initialize($config); - - $this->getSchema()->setColumnType('status', EnumType::from(ArticleStatus::class)); - } - -Where ``ArticleStatus`` contains something like:: - - namespace App\Model\Enum; - - enum ArticleStatus: string - { - case Published = 'Y'; - case Unpublished = 'N'; - } - -CakePHP recommends a few conventions for enums: - -- Enum classnames should follow ``{Entity}{ColumnName}`` style to enable - detection while running bake and to aid with project consistency. -- Enum cases should use CamelCase style. -- Enums should implement the ``Cake\Database\Type\EnumLabelInterface`` to - improve compatibility with bake, and ``FormHelper``. - -Geospatial Types ----------------- - -The ``geometry``, ``point``, ``linestring``, and ``polygon`` types are also known -as the "geospatial types". CakePHP offers limited support for geospatial -columns. Currently they can be defined in migrations, read in schema reflection, -and have values set as text. - -.. versionadded:: 5.1.0 - Geospatial schema types were added. - -.. _adding-custom-database-types: - -Adding Custom Types -------------------- - -.. php:class:: TypeFactory -.. php:staticmethod:: map($name, $class) - -If you need to use vendor specific types that are not built into CakePHP you can -add additional new types to CakePHP's type system. Type classes are expected to -implement the following methods: - -* ``toPHP``: Casts given value from a database type to a PHP equivalent. -* ``toDatabase``: Casts given value from a PHP type to one acceptable by a database. -* ``toStatement``: Casts given value to its Statement equivalent. -* ``marshal``: Marshals flat data into PHP objects. - -To fulfill the basic interface, extend :php:class:`Cake\\Database\\Type`. -For example if we wanted to add a PointMutation type, we could make the following type -class:: - - // in src/Database/Type/PointMutationType.php - - namespace App\Database\Type; - - use Cake\Database\Driver; - use Cake\Database\Type\BaseType; - use PDO; - - class PointMutationType extends BaseType - { - public function toPHP(mixed $value, Driver $driver): mixed - { - if ($value === null) { - return null; - } - - return $this->pmDecode($valu - - public function marshal(mixed $value): mixed - { - if (is_array($value) || $value === null) { - return $value; - } - - return $this->pmDecode($value); - } - - public function toDatabase(mixed $value, Driver $driver): mixed - { - return sprintf('%d%s>%s', $value['position'], $value['from'], $value['to']); - } - - public function toStatement(mixed $value, Driver $driver): int - { - if ($value === null) { - return PDO::PARAM_NULL; - } - - return PDO::PARAM_STR; - } - - protected function pmDecode(mixed $value): mixed - { - if (preg_match('/^(\d+)([a-zA-Z])>([a-zA-Z])$/', $value, $matches)) { - return [ - 'position' => (int) $matches[1], - 'from' => $matches[2], - 'to' => $matches[3] - ]; - } - - return null; - } - } - -By default the ``toStatement()`` method will treat values as strings which will -work for our new type. - -Connecting Custom Datatypes to Schema Reflection and Generation ---------------------------------------------------------------- - -Once we've created our new type, we need to add it into -the type mapping. During our application bootstrap we should do the following:: - - use Cake\Database\TypeFactory; - - TypeFactory::map('point_mutation', \App\Database\Type\PointMutationType:class); - - -We then have two ways to use our datatype in our models. - -#. The first path is to overwrite the reflected schema data to use our new type. -#. The second is to implement ``Cake\Database\Type\ColumnSchemaAwareInterface`` - and define the SQL column type and reflection logic. - -Overwriting the reflected schema with our custom type will enable CakePHP's -database layer to automatically convert PointMutation data when creating queries. In your -Table's :ref:`getSchema() method ` add the - -following:: - - class WidgetsTable extends Table - { - public function initialize(array $config): void - { - return parent::getSchema()->setColumnType('mutation', 'point_mutation'); - - } - } - -Implementing ``ColumnSchemaAwareInterface`` gives you more control over -custom datatypes. This avoids overwriting schema definitions if your -datatype has an unambiguous SQL column definition. For example, we could have -our PointMutation type be used anytime a ``TEXT`` column with a specific comment is -used:: - - // in src/Database/Type/PointMutationType.php - - namespace App\Database\Type; - - use Cake\Database\Driver; - use Cake\Database\Type\BaseType; - use Cake\Database\Type\ColumnSchemaAwareInterface; - use Cake\Database\Schema\TableSchemaInterface; - use PDO; - - class PointMutationType extends BaseType - implements ColumnSchemaAwareInterface - { - // other methods from earlier - - /** - * Convert abstract schema definition into a driver specific - * SQL snippet that can be used in a CREATE TABLE statement. - * - * Returning null will fall through to CakePHP's built-in types. - */ - public function getColumnSql( - TableSchemaInterface $schema, - string $column, - Driver $driver - ): ?string { - $data = $schema->getColumn($column); - $sql = $driver->quoteIdentifier($column); - $sql .= ' JSON'; - if (isset($data['null']) && $data['null'] === false) { - $sql .= ' NOT NULL'; - } - - return $sql; - } - - /** - * Convert the column data returned from schema reflection - * into the abstract schema data. - * - * Returning null will fall through to CakePHP's built-in types. - */ - public function convertColumnDefinition( - array $definition, - Driver $driver - ): ?array { - return [ - 'type' => $this->_name, - 'length' => null, - ]; - } - -The ``$definition`` data passed to ``convertColumnDefinition()`` will contain -the following keys. All keys will exist but may contain ``null`` if the key has -no value for the current database driver: - -- ``length`` The length of a column if available.. -- ``precision`` The precision of the column if available. -- ``scale`` Can be included for SQLServer connections. - -.. _mapping-custom-datatypes-to-sql-expressions: - -Mapping Custom Datatypes to SQL Expressions -------------------------------------------- - -The previous example maps a custom datatype for a 'point_mutation' column type -which is easily represented as a string in a SQL statement. Complex SQL data -types cannot be represented as strings/integers in SQL queries. When working -with these datatypes your Type class needs to implement the -``Cake\Database\Type\ExpressionTypeInterface`` interface. This interface lets -your custom type represent a value as a SQL expression. As an example, we'll -build a simple Type class for handling ``POINT`` type data out of MySQL. First -we'll define a 'value' object that we can use to represent ``POINT`` data in -PHP:: - - // in src/Database/Point.php - namespace App\Database; - - // Our value object is immutable. - class Point - { - protected $_lat; - protected $_long; - - // Factory method. - public static function parse($value) - { - // Parse the WKB data from MySQL. - $unpacked = unpack('x4/corder/Ltype/dlat/dlong', $value); - - return new static($unpacked['lat'], $unpacked['long']); - } - - public function __construct($lat, $long) - { - $this->_lat = $lat; - $this->_long = $long; - } - - public function lat() - { - return $this->_lat; - } - - public function long() - { - return $this->_long; - } - } - -With our value object created, we'll need a Type class to map data into this -value object and into SQL expressions:: - - namespace App\Database\Type; - - use App\Database\Point; - use Cake\Database\Driver; - use Cake\Database\Expression\FunctionExpression; - use Cake\Database\ExpressionInterface; - use Cake\Database\Type\BaseType; - use Cake\Database\Type\ExpressionTypeInterface; - - class PointType extends BaseType implements ExpressionTypeInterface - { - public function toPHP($value, Driver $d): mixed - { - return $value === null ? null : Point::parse($value); - } - - public function marshal($value): mixed - { - if (is_string($value)) { - $value = explode(',', $value); - } - if (is_array($value)) { - return new Point($value[0], $value[1]); - } - - return null; - } - - public function toExpression($value): ExpressionInterface - { - if ($value instanceof Point) { - return new FunctionExpression( - 'POINT', - [ - $value->lat(), - $value->long() - ] - ); - } - if (is_array($value)) { - return new FunctionExpression('POINT', [$value[0], $value[1]]); - } - // Handle other cases. - } - - public function toDatabase($value, Driver $driver): mixed - { - return $value; - } - } - -The above class does a few interesting things: - -* The ``toPHP`` method handles parsing the SQL query results into a value - object. -* The ``marshal`` method handles converting, data such as given request data, into our value object. - We're going to accept string values like ``'10.24,12.34`` and arrays for now. -* The ``toExpression`` method handles converting our value object into the - equivalent SQL expressions. In our example the resulting SQL would be - something like ``POINT(10.24, 12.34)``. - -Once we've built our custom type, we'll need to :ref:`connect our type -to our table class `. - -Connection Classes -================== - -.. php:class:: Connection - -Connection classes provide a simple interface to interact with database -connections in a consistent way. They are intended as a more abstract interface to -the driver layer and provide features for executing queries, logging queries, and doing -transactional operations. - -.. _database-queries: - -Executing Queries ------------------ - -.. php:method:: execute(string $sql, array $params = [], array $types = []): \Cake\Database\StatementInterface - -Once you've gotten a connection object, you'll probably want to issue some -queries with it. CakePHP's database abstraction layer provides wrapper features -on top of PDO and native drivers. These wrappers provide a similar interface to -PDO. There are a few different ways you can run queries depending on the type of -query you need to run and what kind of results you need back. The most basic -method is ``execute()`` which allows you to run complet SQL queries:: - - $statement = $connection->execute('UPDATE articles SET published = 1 WHERE id = 2'); - -For parameterized queries use the 2nd argument:: - - $statement = $connection->execute( - 'UPDATE articles SET published = ? WHERE id = ?', - [1, 2] - ); - -Without any type hinting information, ``execute`` will assume all placeholders -are string values. If you need to bind specific types of data, you can use their -abstract type names when creating a query:: - - $statement = $connection->execute( - 'UPDATE articles SET published_date = ? WHERE id = ?', - [new DateTime('now'), 2], - ['date', 'integer'] - ); - -.. php:method:: selectQuery() - -These methods allow you to use rich data types in your applications and properly convert -them into SQL statements. The last and most flexible way of creating queries is -to use the :doc:`/orm/query-builder`. This approach allows you to build complex and -expressive queries without having to use platform specific SQL. When using the -query builder, no SQL will be sent to the database server until the ``execute()`` -method is called, or the query is iterated. Iterating a query will first execute -it and then start iterating over the result set:: - - $query = $connection->selectQuery(); - $query->select('*') - ->from('articles') - ->where(['published' => true]); - - foreach ($query as $row) { - // Do something with the row. - } - -.. note:: - - Instead of iterating the ``$query`` you can also call it's ``all()`` method - to get the results. - -.. php:method:: updateQuery() - -This method provides you a builder for ``UPDATE`` queries:: - - $query = $connection->updateQuery('articles') - ->set(['published' => true]) - ->where(['id' => 2]); - $statement = $query->execute(); - -.. php:method:: insertQuery() - -This method provides you a builder for ``INSERT`` queries:: - - $query = $connection->insertQuery(); - $query->into('articles') - ->columns(['title']) - ->values(['1st article']); - $statement = $query->execute(); - -.. php:method:: deleteQuery() - -This method provides you a builder for ``DELETE`` queries:: - - $query = $connection->deleteQuery(); - $query->delete('articles') - ->where(['id' => 2]); - $statement = $query->execute(); - -Using Transactions ------------------- - -The connection objects provide you a few simple ways you do database -transactions. The most basic way of doing transactions is through the ``begin()``, -``commit()`` and ``rollback()`` methods, which map to their SQL equivalents:: - - $connection->begin(); - $connection->execute('UPDATE articles SET published = ? WHERE id = ?', [true, 2]); - $connection->execute('UPDATE articles SET published = ? WHERE id = ?', [false, 4]); - $connection->commit(); - -.. php:method:: transactional(callable $callback) - -In addition to this interface connection instances also provide the -``transactional()`` method which makes handling the begin/commit/rollback calls -much simpler:: - - $connection->transactional(function ($connection) { - $connection->execute('UPDATE articles SET published = ? WHERE id = ?', [true, 2]); - $connection->execute('UPDATE articles SET published = ? WHERE id = ?', [false, 4]); - }); - -In addition to basic queries, you can execute more complex queries using either -the :doc:`/orm/query-builder` or :doc:`/orm/table-objects`. The transactional method will -do the following: - -- Call ``begin``. -- Call the provided closure. -- If the closure raises an exception, a rollback will be issued. The original - exception will be re-thrown. -- If the closure returns ``false``, a rollback will be issued. -- If the closure executes successfully, the transaction will be committed. - -Interacting with Statements -=========================== - -When using the lower level database API, you will often encounter statement -objects. These objects allow you to manipulate the underlying prepared statement -from the driver. After creating and executing a query object, or using -``execute()`` you will have a ``StatementInterface`` instance. - -Executing & Fetching Rows -------------------------- - -Once a query is executed using ``execute()``, results can be fetched using -``fetch()``, ``fetchAll()``:: - - $statement->execute(); - - // Read one row. - $row = $statement->fetch('assoc'); - - // Read all rows. - $rows = $statement->fetchAll('assoc'); - -Getting affected Row Counts ---------------------------- - -After executing a statement, you can fetch the number of affected rows:: - - $rowCount = $statement->rowCount(); - -Checking Error Codes --------------------- - -If your query was not successful, you can get related error information -using the ``errorCode()`` and ``errorInfo()`` methods. These methods work the -same way as the ones provided by PDO:: - - $code = $statement->errorCode(); - $info = $statement->errorInfo(); - -.. _database-query-logging: - -Query Logging -============= - -Query logging can be enabled when configuring your connection by setting the -``log`` option to ``true``. - -When query logging is enabled, queries will be logged to -:php:class:`Cake\\Log\\Log` using the 'debug' level, and the 'queriesLog' scope. -You will need to have a logger configured to capture this level & scope. Logging -to ``stderr`` can be useful when working on unit tests, and logging to -files/syslog can be useful when working with web requests:: - - use Cake\Log\Log; - - // Console logging - Log::setConfig('queries', [ - 'className' => 'Console', - 'stream' => 'php://stderr', - 'scopes' => ['queriesLog'] - ]); - - // File logging - Log::setConfig('queries', [ - 'className' => 'File', - 'path' => LOGS, - 'file' => 'queries.log', - 'scopes' => ['queriesLog'] - ]); - -.. note:: - - Query logging is only intended for debugging/development uses. You should - never leave query logging on in production as it will negatively impact the - performance of your application. - -.. _identifier-quoting: - -Identifier Quoting -================== - -By default CakePHP does **not** quote identifiers in generated SQL queries. The -reason for this is identifier quoting has a few drawbacks: - -* Performance overhead - Quoting identifiers is much slower and complex than not doing it. -* Not necessary in most cases - In non-legacy databases that follow CakePHP's - conventions there is no reason to quote identifiers. - -If you are using a legacy schema that requires identifier quoting you can enable -it using the ``quoteIdentifiers`` setting in your -:ref:`database-configuration`. You can also enable this feature at runtime:: - - $connection->getDriver()->enableAutoQuoting(); - -When enabled, identifier quoting will cause additional query traversal that -converts all identifiers into ``IdentifierExpression`` objects. - -.. note:: - - SQL snippets contained in QueryExpression objects will not be modified. - -.. _database-metadata-cache: - -Metadata Caching -================ - -CakePHP's ORM uses database reflection to determine the schema, indexes and -foreign keys your application contains. Because this metadata changes -infrequently and can be expensive to access, it is typically cached. By default, -metadata is stored in the ``_cake_model_`` cache configuration. You can define -a custom cache configuration using the ``cacheMetatdata`` option in your -datasource configuration:: - - 'Datasources' => [ - 'default' => [ - // Other keys go here. - - // Use the 'orm_metadata' cache config for metadata. - 'cacheMetadata' => 'orm_metadata', - ] - ], - -You can also configure the metadata caching at runtime with the -``cacheMetadata()`` method:: - - // Disable the cache - $connection->cacheMetadata(false); - - // Enable the cache - $connection->cacheMetadata(true); - - // Use a custom cache config - $connection->cacheMetadata('orm_metadata'); - -CakePHP also includes a CLI tool for managing metadata caches. See the -:doc:`/console-commands/schema-cache` chapter for more information. - -Creating Databases -================== - -If you want to create a connection without selecting a database you can omit -the database name:: - - $dsn = 'mysql://root:password@localhost/'; - ConnectionManager::setConfig('setup', ['url' => $dsn]); - -You can now use your connection object to execute queries that create/modify -databases. For example to create a database:: - - $connection = ConnectionManager::get('setup'); - $connection->execute("CREATE DATABASE IF NOT EXISTS my_database"); - -.. note:: - - When creating a database it is a good idea to set the character set and - collation parameters (e.g. ``DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci``). - If these values are missing, the database will set whatever system default values it uses. - -.. meta:: - :title lang=en: Database Basics - :keywords lang=en: SQL,MySQL,MariaDB,PostGres,Postgres,postgres,PostgreSQL,PostGreSQL,postGreSql,select,insert,update,delete,statement,configuration,connection,database,data,types,custom,,executing,queries,transactions,prepared,statements,binding,fetching,row,count,error,codes,query,logging,identifier,quoting,metadata,caching diff --git a/en/orm/deleting-data.rst b/en/orm/deleting-data.rst deleted file mode 100644 index 7c7dad5602..0000000000 --- a/en/orm/deleting-data.rst +++ /dev/null @@ -1,126 +0,0 @@ -Deleting Data -############# - -.. php:namespace:: Cake\ORM - -.. php:class:: Table - :noindex: - -.. php:method:: delete(EntityInterface $entity, array $options = []) - -Once you've loaded an entity you can delete it by calling the originating -table's delete method:: - - // In a controller. - $entity = $this->Articles->get(2); - $result = $this->Articles->delete($entity); - -When deleting entities a few things happen: - -1. The :ref:`delete rules ` will be applied. If the rules - fail, deletion will be prevented. -2. The ``Model.beforeDelete`` event is triggered. If this event is stopped, the - delete will be aborted and the event's result will be returned. -3. The entity will be deleted. -4. All dependent associations will be deleted. If associations are being deleted - as entities, additional events will be dispatched. -5. Any junction table records for BelongsToMany associations will be removed. -6. The ``Model.afterDelete`` event will be triggered. - -By default all deletes happen within a transaction. You can disable the -transaction with the atomic option:: - - $result = $this->Articles->delete($entity, ['atomic' => false]); - -The ``$options`` parameter supports the following options: - -- ``atomic`` Defaults to true. When true the deletion happens within - a transaction. -- ``checkRules`` Defaults to true. Check deletion rules before deleting - records. - -Cascading Deletes ------------------ - -When deleting entities, associated data can also be deleted. If your HasOne and -HasMany associations are configured as ``dependent``, delete operations will -'cascade' to those entities as well. By default entities in associated tables -are removed using :php:meth:`Cake\\ORM\\Table::deleteAll()`. You can elect to -have the ORM load related entities, and delete them individually by setting the -``cascadeCallbacks`` option to ``true``. A sample HasMany association with both -these options enabled would be:: - - // In a Table's initialize method. - $this->hasMany('Comments', [ - 'dependent' => true, - 'cascadeCallbacks' => true, - ]); - -.. note:: - - Setting ``cascadeCallbacks`` to ``true``, results in considerably slower deletes - when compared to bulk deletes. The cascadeCallbacks option should only be - enabled when your application has important work handled by event listeners. - -Bulk Deletes ------------- - -.. php:method:: deleteMany(iterable $entities, array $options = []) - -If you have an array of entities you want to delete you can use ``deleteMany()`` -to delete them in a single transaction:: - - // Get a boolean indicating success - $success = $this->Articles->deleteMany($entities); - - // Will throw a PersistenceFailedException if any entity cannot be deleted. - $this->Articles->deleteManyOrFail($entities); - -The ``$options`` for these methods are the same as ``delete()``. Deleting -records with these method **will** trigger events. - -.. php:method:: deleteAll($conditions) - -There may be times when deleting rows one by one is not efficient or useful. -In these cases it is more performant to use a bulk-delete to remove many rows at -once:: - - // Delete all the spam - public function destroySpam() - { - return $this->deleteAll(['is_spam' => true]); - } - -A bulk-delete will be considered successful if 1 or more rows are deleted. The -function returns the number of deleted records as an integer. - -.. warning:: - - deleteAll will *not* trigger beforeDelete/afterDelete events. - If you need callbacks triggered, first load the entities with ``find()`` - and delete them in a loop. - -Strict Deletes --------------- - -.. php:method:: deleteOrFail(EntityInterface $entity, array $options = []) - -Using this method will throw an -:php:exc:`Cake\\ORM\\Exception\\PersistenceFailedException` if: - -* the entity is new -* the entity has no primary key value -* application rules checks failed -* the delete was aborted by a callback. - -If you want to track down the entity that failed to delete, you can use the -:php:meth:`Cake\\ORM\Exception\\PersistenceFailedException::getEntity()` method:: - - try { - $table->deleteOrFail($entity); - } catch (\Cake\ORM\Exception\PersistenceFailedException $e) { - echo $e->getEntity(); - } - -As this internally performs a :php:meth:`Cake\\ORM\\Table::delete()` call, all -corresponding delete events will be triggered. diff --git a/en/orm/entities.rst b/en/orm/entities.rst deleted file mode 100644 index 88bbadc38d..0000000000 --- a/en/orm/entities.rst +++ /dev/null @@ -1,612 +0,0 @@ -Entities -######## - -.. php:namespace:: Cake\ORM - -.. php:class:: Entity - -While :doc:`/orm/table-objects` represent and provide access to a collection of -objects, entities represent individual rows or domain objects in your -application. Entities contain methods to manipulate and -access the data they contain. Fields can also be accessed as properties on the object. - -Entities are created for you each time you iterate the query instance returned -by ``find()`` of a table object or when you call ``all()`` or ``first()`` method -of the query instance. - -Creating Entity Classes -======================= - -You don't need to create entity classes to get started with the ORM in CakePHP. -However, if you want to have custom logic in your entities you will need to -create classes. By convention entity classes live in **src/Model/Entity/**. If -our application had an ``articles`` table we could create the following entity:: - - // src/Model/Entity/Article.php - namespace App\Model\Entity; - - use Cake\ORM\Entity; - - class Article extends Entity - { - } - -Right now this entity doesn't do very much. However, when we load data from our -articles table, we'll get instances of this class. - -.. note:: - - If you don't define an entity class CakePHP will use the basic Entity class. - -Creating Entities -================= - -Entities can be directly instantiated:: - - use App\Model\Entity\Article; - - $article = new Article(); - -When instantiating an entity you can pass the fields with the data you want -to store in them:: - - use App\Model\Entity\Article; - - $article = new Article([ - 'id' => 1, - 'title' => 'New Article', - 'created' => new DateTime('now') - ]); - -The preferred way of getting new entities is using the ``newEmptyEntity()`` method from the -``Table`` objects:: - - use Cake\ORM\Locator\LocatorAwareTrait; - - $article = $this->fetchTable('Articles')->newEmptyEntity(); - - $article = $this->fetchTable('Articles')->newEntity([ - 'id' => 1, - 'title' => 'New Article', - 'created' => new DateTime('now') - ]); - -``$article`` will be an instance of ``App\Model\Entity\Article`` or fallback to -``Cake\ORM\Entity`` instance if you haven't created the ``Article`` class. - -.. note:: - - Prior to CakePHP 4.3 you need to use ``$this->getTableLocator->get('Articles')`` - to get the table instance. - -Accessing Entity Data -===================== - -Entities provide a few ways to access the data they contain. Most commonly you -will access the data in an entity using object notation:: - - use App\Model\Entity\Article; - - $article = new Article; - $article->title = 'This is my first post'; - echo $article->title; - -You can also use the ``get()`` and ``set()`` methods. - -.. php:method:: set($field, $value = null, array $options = []) - -.. php:method:: get($field) - -For example:: - - $article->set('title', 'This is my first post'); - echo $article->get('title'); - -.. php:method:: patch(array $fields, array $options = []) - -Using ``patch()`` you can mass assign multiple fields at once:: - - $article->patch([ - 'title' => 'My first post', - 'body' => 'It is the best ever!' - ]); - -.. note:: - - ``patch()`` is available since CakePHP 5.2.0. Prior to that you should use - ``set()`` instead. - -.. warning:: - - When updating entities with request data you should configure which fields - can be set with mass assignment. - -You can check if fields are defined in your entities with ``has()``:: - - $article = new Article([ - 'title' => 'First post', - 'user_id' => null - ]); - $article->has('title'); // true - $article->has('user_id'); // true - $article->has('undefined'); // false - -The ``has()`` method will return ``true`` if a field is defined. You can use -``isEmpty()`` and ``hasValue()`` to check if a field contains a 'non-empty' -value:: - - $article = new Article([ - 'title' => 'First post', - 'user_id' => null, - 'text' => '', - 'links' => [], - ]); - $article->has('title'); // true - $article->isEmpty('title'); // false - $article->hasValue('title'); // true - - $article->has('user_id'); // true - $article->isEmpty('user_id'); // true - $article->hasValue('user_id'); // false - - $article->has('text'); // true - $article->isEmpty('text'); // true - $article->hasValue('text'); // false - - $article->has('links'); // true - $article->isEmpty('links'); // true - $article->hasValue('links'); // false - -If you often partially load entities you should enable strict-property access -behavior to ensure you're not using properties that haven't been loaded. On -a per-entity basis you can enable this behavior:: - - $article->requireFieldPresence(); - -Once enabled, accessing properties that are not defined will raise -a :php:exc:`Cake\ORM\MissingPropertyException`. - -Accessors & Mutators -==================== - -In addition to the simple get/set interface, entities allow you to provide -accessors and mutator methods. These methods let you customize how fields -are read or set. - -Accessors ---------- - -Accessors let you customize how fields are read. They use the convention of -``_get(FieldName)`` with ``(FieldName)`` being the CamelCased version (multiple -words are joined together to a single word with the first letter of each word -capitalized) of the field name. - -They receive the basic value stored in the ``_fields`` array as their only -argument. For example:: - - namespace App\Model\Entity; - - use Cake\ORM\Entity; - - class Article extends Entity - { - protected function _getTitle($title) - { - return strtoupper($title); - } - } - -The example above converts the value of the ``title`` field to an uppercase -version each time it is read. It would be run when getting the field through any -of these two ways:: - - echo $article->title; // returns FOO instead of foo - echo $article->get('title'); // returns FOO instead of foo - -.. note:: - - Code in your accessors is executed each time you reference the field. You can - use a local variable to cache it if you are performing a resource-intensive - operation in your accessor like this: `$myEntityProp = $entity->my_property`. - -.. warning:: - - Accessors will be used when saving entities, so be careful when defining methods - that format data, as the formatted data will be persisted. - -Mutators --------- - -You can customize how fields get set by defining a mutator. They use the -convention of ``_set(FieldName)`` with ``(FieldName)`` being the CamelCased version -of the field name. - -Mutators should always return the value that should be stored in the field. -You can also use mutators to set other fields. When doing this, -be careful to not introduce any loops, as CakePHP will not prevent infinitely -looping mutator methods. For example:: - - namespace App\Model\Entity; - - use Cake\ORM\Entity; - use Cake\Utility\Text; - - class Article extends Entity - { - protected function _setTitle($title) - { - $this->slug = Text::slug($title); - - return strtoupper($title); - } - } - -The example above is doing two things: It stores a modified version of the -given value in the ``slug`` field and stores an uppercase version in the -``title`` field. It would be run when setting the field through -any of these two ways:: - - $user->title = 'foo'; // sets slug field and stores FOO instead of foo - $user->set('title', 'foo'); // sets slug field and stores FOO instead of foo - -.. warning:: - - Accessors are also run before entities are persisted to the database. - If you want to transform fields but not persist that transformation, - we recommend using virtual fields as those are not persisted. - -.. _entities-virtual-fields: - -Creating Virtual Fields ------------------------ - -By defining accessors you can provide access to fields that do not -actually exist. For example if your users table has ``first_name`` and -``last_name`` you could create a method for the full name:: - - namespace App\Model\Entity; - - use Cake\ORM\Entity; - - class User extends Entity - { - protected function _getFullName() - { - return $this->first_name . ' ' . $this->last_name; - } - } - -You can access virtual fields as if they existed on the entity. The property -name will be the lower case and underscored version of the method (``full_name``):: - - echo $user->full_name; - echo $user->get('full_name'); - -Do bear in mind that virtual fields cannot be used in finds. If you want -them to be part of JSON or array representations of your entities, -see :ref:`exposing-virtual-fields`. - -Checking if an Entity Has Been Modified -======================================= - -.. php:method:: dirty($field = null, $dirty = null) - -You may want to make code conditional based on whether or not fields have -changed in an entity. For example, you may only want to validate fields when -they change:: - - // See if the title has been modified. - $article->isDirty('title'); - -You can also flag fields as being modified. This is handy when appending into -array fields as this wouldn't automatically mark the field as dirty, only -exchanging completely would.:: - - // Add a comment and mark the field as changed. - $article->comments[] = $newComment; - $article->setDirty('comments', true); - -In addition you can also base your conditional code on the original field -values by using the ``getOriginal()`` method. This method will either return -the original value of the field if it has been modified or its actual value. - -You can also check for changes to any field in the entity:: - - // See if the entity has changed - $article->isDirty(); - -To remove the dirty mark from fields in an entity, you can use the ``clean()`` -method:: - - $article->clean(); - -When creating a new entity, you can avoid the fields from being marked as dirty -by passing an extra option:: - - $article = new Article(['title' => 'New Article'], ['markClean' => true]); - -To get a list of all dirty fields of an ``Entity`` you may call:: - - $dirtyFields = $entity->getDirty(); - -Validation Errors -================= - -After you :ref:`save an entity ` any validation errors will be -stored on the entity itself. You can access any validation errors using the -``getErrors()``, ``getError()`` or ``hasErrors()`` methods:: - - // Get all the errors - $errors = $user->getErrors(); - - // Get the errors for a single field. - $errors = $user->getError('password'); - - // Does the entity or any nested entity have an error. - $user->hasErrors(); - - // Does only the root entity have an error - $user->hasErrors(false); - -The ``setErrors()`` or ``setError()`` method can also be used to set the errors -on an entity, making it easier to test code that works with error messages:: - - $user->setError('password', ['Password is required']); - $user->setErrors([ - 'password' => ['Password is required'], - 'username' => ['Username is required'] - ]); - -.. _entities-mass-assignment: - -Mass Assignment -=============== - -While setting fields to entities in bulk is simple and convenient, it can -create significant security issues. Bulk assigning user data from the request -into an entity allows the user to modify any and all columns. When using -anonymous entity classes or creating the entity class with the :doc:`/bake` -CakePHP does not protect against mass-assignment. - -The ``_accessible`` property allows you to provide a map of fields and -whether or not they can be mass-assigned. The values ``true`` and ``false`` -indicate whether a field can or cannot be mass-assigned:: - - namespace App\Model\Entity; - - use Cake\ORM\Entity; - - class Article extends Entity - { - protected array $_accessible = [ - 'title' => true, - 'body' => true - ]; - } - -In addition to concrete fields there is a special ``*`` field which defines the -fallback behavior if a field is not specifically named:: - - namespace App\Model\Entity; - - use Cake\ORM\Entity; - - class Article extends Entity - { - protected array $_accessible = [ - 'title' => true, - 'body' => true, - '*' => false, - ]; - } - -.. note:: If the ``*`` field is not defined it will default to ``false``. - -Avoiding Mass Assignment Protection ------------------------------------ - -When creating a new entity using the ``new`` keyword you can tell it to not -protect itself against mass assignment:: - - use App\Model\Entity\Article; - - $article = new Article(['id' => 1, 'title' => 'Foo'], ['guard' => false]); - -Modifying the Guarded Fields at Runtime ---------------------------------------- - -You can modify the list of guarded fields at runtime using the ``setAccess()`` -method:: - - // Make user_id accessible. - $article->setAccess('user_id', true); - - // Make title guarded. - $article->setAccess('title', false); - -.. note:: - - Modifying accessible fields affects only the instance the method is called - on. - -When using the ``newEntity()`` and ``patchEntity()`` methods in the ``Table`` -objects you can customize mass assignment protection with options. Please refer -to the :ref:`changing-accessible-fields` section for more information. - -Bypassing Field Guarding ------------------------- - -There are some situations when you want to allow mass-assignment to guarded -fields:: - - $article->patch($fields, ['guard' => false]); - -By setting the ``guard`` option to ``false``, you can ignore the accessible -field list for a single call to ``patch()``. - -Checking if an Entity was Persisted ------------------------------------ - -It is often necessary to know if an entity represents a row that is already -in the database. In those situations use the ``isNew()`` method:: - - if (!$article->isNew()) { - echo 'This article was saved already!'; - } - -If you are certain that an entity has already been persisted, you can use -``setNew()``:: - - $article->setNew(false); - - $article->setNew(true); - -.. _lazy-load-associations: - -Lazy Loading Associations -========================= - -While eager loading associations is generally the most efficient way to access -your associations, there may be times when you need to lazily load associated -data. Before we get into how to lazy load associations, we should discuss the -differences between eager loading and lazy loading associations: - -Eager loading - Eager loading uses joins (where possible) to fetch data from the - database in as *few* queries as possible. When a separate query is required, - like in the case of a HasMany association, a single query is emitted to - fetch *all* the associated data for the current set of objects. -Lazy loading - Lazy loading defers loading association data until it is absolutely - required. While this can save CPU time because possibly unused data is not - hydrated into objects, it can result in many more queries being emitted to - the database. For example looping over a set of articles & their comments - will frequently emit N queries where N is the number of articles being - iterated. - -While lazy loading is not included by CakePHP's ORM, you can just use one of the -community plugins to do so. We recommend `the LazyLoad Plugin -`__ - -After adding the plugin to your entity, you will be able to do the following:: - - $article = $this->Articles->findById($id); - - // The comments property was lazy loaded - foreach ($article->comments as $comment) { - echo $comment->body; - } - -Creating Re-usable Code with Traits -=================================== - -You may find yourself needing the same logic in multiple entity classes. PHP's -traits are a great fit for this. You can put your application's traits in -**src/Model/Entity**. By convention traits in CakePHP are suffixed with -``Trait`` so they can be discernible from classes or interfaces. Traits are -often a good complement to behaviors, allowing you to provide functionality for -the table and entity objects. - -For example if we had SoftDeletable plugin, it could provide a trait. This trait -could give methods for marking entities as 'deleted', the method ``softDelete`` -could be provided by a trait:: - - // SoftDelete/Model/Entity/SoftDeleteTrait.php - - namespace SoftDelete\Model\Entity; - - trait SoftDeleteTrait - { - public function softDelete() - { - $this->set('deleted', true); - } - } - -You could then use this trait in your entity class by importing it and including -it:: - - namespace App\Model\Entity; - - use Cake\ORM\Entity; - use SoftDelete\Model\Entity\SoftDeleteTrait; - - class Article extends Entity - { - use SoftDeleteTrait; - } - -Converting to Arrays/JSON -========================= - -When building APIs, you may often need to convert entities into arrays or JSON -data. CakePHP makes this simple:: - - // Get an array. - // Associations will be converted with toArray() as well. - $array = $user->toArray(); - - // Convert to JSON - // Associations will be converted with jsonSerialize hook as well. - $json = json_encode($user); - -When converting an entity to an JSON, the virtual & hidden field lists are -applied. Entities are recursively converted to JSON as well. This means that if you -eager loaded entities and their associations CakePHP will correctly handle -converting the associated data into the correct format. - -.. _exposing-virtual-fields: - -Exposing Virtual Fields ------------------------ - -By default virtual fields are not exported when converting entities to -arrays or JSON. In order to expose virtual fields you need to make them -visible. When defining your entity class you can provide a list of virtual -field that should be exposed:: - - namespace App\Model\Entity; - - use Cake\ORM\Entity; - - class User extends Entity - { - protected array $_virtual = ['full_name']; - } - -This list can be modified at runtime using the ``setVirtual()`` method:: - - $user->setVirtual(['full_name', 'is_admin']); - -Hiding Fields -------------- - -There are often fields you do not want exported in JSON or array formats. For -example it is often unwise to expose password hashes or account recovery -questions. When defining an entity class, define which fields should be -hidden:: - - namespace App\Model\Entity; - - use Cake\ORM\Entity; - - class User extends Entity - { - protected $_hidden = ['password']; - } - -This list can be modified at runtime using the ``setHidden()`` method:: - - $user->setHidden(['password', 'recovery_question']); - -Storing Complex Types -===================== - -Accessor & Mutator methods on entities are not intended to contain the logic for -serializing and unserializing complex data coming from the database. Refer to -the :ref:`saving-complex-types` section to understand how your application can -store more complex data types like arrays and objects. - -.. meta:: - :title lang=en: Entities - :keywords lang=en: entity, entities, single row, individual record diff --git a/en/orm/query-builder.rst b/en/orm/query-builder.rst deleted file mode 100644 index 60453bb04b..0000000000 --- a/en/orm/query-builder.rst +++ /dev/null @@ -1,1937 +0,0 @@ -Query Builder -############# - - -.. php:namespace:: Cake\ORM\Query\SelectQuery - -.. php:class:: SelectQuery - -The ORM's query builder provides a simple to use fluent interface for creating -and running queries. By composing queries together, you can create advanced -queries using unions and subqueries with ease. - -Underneath the covers, the query builder uses PDO prepared statements which -protect against SQL injection attacks. - -The SelectQuery Object -====================== - -The easiest way to create a ``SelectQuery`` object is to use ``find()`` from a -``Table`` object. This method will return an incomplete query ready to be -modified. You can also use a table's connection object to access the lower level -query builder that does not include ORM features, if necessary. See the -:ref:`database-queries` section for more information:: - - use Cake\ORM\Locator\LocatorAwareTrait; - - $articles = $this->fetchTable('Articles'); - - // Start a new query. - $query = $articles->find(); - -When inside a controller, you can use the automatic table variable that is -created using the conventions system:: - - // Inside ArticlesController.php - - $query = $this->Articles->find(); - -Selecting Rows From A Table ---------------------------- - -:: - - use Cake\ORM\Locator\LocatorAwareTrait; - - $query = $this->fetchTable('Articles')->find(); - - foreach ($query->all() as $article) { - debug($article->title); - } - -For the remaining examples, assume that ``$articles`` is a -:php:class:`~Cake\\ORM\\Table`. When inside controllers, you can use -``$this->Articles`` instead of ``$articles``. - -Almost every method in a ``SelectQuery`` object will return the same query, this means -that ``SelectQuery`` objects are lazy, and will not be executed unless you tell them -to:: - - $query->where(['id' => 1]); // Return the same query object - $query->orderBy(['title' => 'DESC']); // Still same object, no SQL executed - -You can of course chain the methods you call on SelectQuery objects:: - - $query = $articles - ->find() - ->select(['id', 'name']) - ->where(['id !=' => 1]) - ->orderBy(['created' => 'DESC']); - - foreach ($query->all() as $article) { - debug($article->created); - } - -If you try to call ``debug()`` on a SelectQuery object, you will see its internal -state and the SQL that will be executed in the database:: - - debug($articles->find()->where(['id' => 1])); - - // Outputs - // ... - // 'sql' => 'SELECT * FROM articles where id = ?' - // ... - -You can execute a query directly without having to use ``foreach`` on it. -The easiest way is to either call the ``all()`` or ``toList()`` methods:: - - $resultsIteratorObject = $articles - ->find() - ->where(['id >' => 1]) - ->all(); - - foreach ($resultsIteratorObject as $article) { - debug($article->id); - } - - $resultsArray = $articles - ->find() - ->where(['id >' => 1]) - ->all() - ->toList(); - - foreach ($resultsArray as $article) { - debug($article->id); - } - - debug($resultsArray[0]->title); - -In the above example, ``$resultsIteratorObject`` will be an instance of -``Cake\ORM\ResultSet``, an object you can iterate and apply several extracting -and traversing methods on. - -Often, there is no need to call ``all()``, you can simply iterate the -SelectQuery object to get its results. Query objects can also be used directly as the -result object; trying to iterate the query, calling ``toList()`` or ``toArray()``, will -result in the query being executed and results returned to you. - -Selecting A Single Row From A Table ------------------------------------ - -You can use the ``first()`` method to get the first result in the query:: - - $article = $articles - ->find() - ->where(['id' => 1]) - ->first(); - - debug($article->title); - -Getting A List Of Values From A Column --------------------------------------- - -:: - - // Use the extract() method from the collections library - // This executes the query as well - $allTitles = $articles->find()->all()->extract('title'); - - foreach ($allTitles as $title) { - echo $title; - } - -You can also get a key-value list out of a query result:: - - $list = $articles->find('list')->all(); - foreach ($list as $id => $title) { - echo "$id : $title" - } - -For more information on how to customize the fields used for populating the list -refer to :ref:`table-find-list` section. - -ResultSet Is A Collection Object --------------------------------- - -Once you get familiar with the Query object methods, it is strongly encouraged -that you visit the :doc:`Collection ` section to -improve your skills in efficiently traversing the results. The ResultSet (returned -by calling the ``SelectQuery``'s ``all()`` method) implements the collection interface:: - - // Use the combine() method from the collections library - // This is equivalent to find('list') - $keyValueList = $articles->find()->all()->combine('id', 'title'); - - // An advanced example - $results = $articles->find() - ->where(['id >' => 1]) - ->orderBy(['title' => 'DESC']) - ->all() - ->map(function ($row) { - $row->trimmedTitle = trim($row->title); - - return $row; - }) - ->combine('id', 'trimmedTitle') // combine() is another collection method - ->toArray(); // Also a collections library method - - foreach ($results as $id => $trimmedTitle) { - echo "$id : $trimmedTitle"; - } - -Queries Are Lazily Evaluated ----------------------------- - -Query objects are lazily evaluated. This means a query is not executed until one -of the following things occur: - -- The query is iterated with ``foreach``. -- The query's ``execute()`` method is called. This will return the underlying - statement object, and is to be used with insert/update/delete queries. -- The query's ``first()`` method is called. This will return the first result in the set - built by ``SELECT`` (it adds ``LIMIT 1`` to the query). -- The query's ``all()`` method is called. This will return the result set and - can only be used with ``SELECT`` statements. -- The query's ``toList()`` or ``toArray()`` method is called. - -Until one of these conditions are met, the query can be modified without additional -SQL being sent to the database. It also means that if a Query hasn't been -evaluated, no SQL is ever sent to the database. Once executed, modifying and -re-evaluating a query will result in additional SQL being run. Calling the same query without modification multiple times will return same reference. - -If you want to take a look at what SQL CakePHP is generating, you can turn -database :ref:`query logging ` on. - -Selecting Data -============== - -CakePHP makes building ``SELECT`` queries simple. To limit the fields fetched, -you can use the ``select()`` method:: - - $query = $articles->find(); - $query->select(['id', 'title', 'body']); - foreach ($query->all() as $row) { - debug($row->title); - } - -You can set aliases for fields by providing fields as an associative array:: - - // Results in SELECT id AS pk, title AS aliased_title, body ... - $query = $articles->find(); - $query->select(['pk' => 'id', 'aliased_title' => 'title', 'body']); - -To select distinct fields, you can use the ``distinct()`` method:: - - // Results in SELECT DISTINCT country FROM ... - $query = $articles->find(); - $query->select(['country']) - ->distinct(['country']); - -To set some basic conditions you can use the ``where()`` method:: - - // Conditions are combined with AND - $query = $articles->find(); - $query->where(['title' => 'First Post', 'published' => true]); - - // You can call where() multiple times - $query = $articles->find(); - $query->where(['title' => 'First Post']) - ->where(['published' => true]); - -You can also pass an anonymous function to the ``where()`` method. The passed -anonymous function will receive an instance of -``\Cake\Database\Expression\QueryExpression`` as its first argument, and -``\Cake\ORM\Query\SelectQuery`` as its second:: - - $query = $articles->find(); - $query->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->eq('published', true); - }); - -See the :ref:`advanced-query-conditions` section to find out how to construct -more complex ``WHERE`` conditions. - - -Selecting Specific Fields -------------------------- - -By default a query will select all fields from a table, the exception is when you -call the ``select()`` function yourself and pass certain fields:: - - // Only select id and title from the articles table - $articles->find()->select(['id', 'title']); - -If you wish to still select all fields from a table after having called -``select($fields)``, you can pass the table instance to ``select()`` for this -purpose:: - - // Only all fields from the articles table including - // a calculated slug field. - $query = $articlesTable->find(); - $query - ->select(['slug' => $query->func()->concat(['title' => 'identifier', '-', 'id' => 'identifier'])]) - ->select($articlesTable); // Select all fields from articles - -You can use ``selectAlso()`` to select all fields on a table and -*also* select some additional fields:: - - $query = $articlesTable->find(); - $query->selectAlso(['count' => $query->func()->count('*')]); - -If you want to select all but a few fields on a table, you can use -``selectAllExcept()``:: - - $query = $articlesTable->find(); - - // Get all fields except the published field. - $query->selectAllExcept($articlesTable, ['published']); - -You can also pass an ``Association`` object when working with contained -associations. - -.. _using-sql-functions: - -Using SQL Functions -------------------- - -CakePHP's ORM offers abstraction for some commonly used SQL functions. Using the -abstraction allows the ORM to select the platform specific implementation of the -function you want. For example, ``concat`` is implemented differently in MySQL, -PostgreSQL and SQL Server. Using the abstraction allows your code to be -portable:: - - // Results in SELECT COUNT(*) count FROM ... - $query = $articles->find(); - $query->select(['count' => $query->func()->count('*')]); - -Note that most of the functions accept an additional argument to specify the types -to bind to the arguments and/or the return type, for example:: - - $query->select(['minDate' => $query->func()->min('date', ['date']); - -For details, see the documentation for :php:class:`Cake\\Database\\FunctionsBuilder`. - -You can access existing wrappers for several SQL functions through ``SelectQuery::func()``: - -``rand()`` - Generate a random value between 0 and 1 via SQL. -``sum()`` - Calculate a sum. `Assumes arguments are literal values.` -``avg()`` - Calculate an average. `Assumes arguments are literal values.` -``min()`` - Calculate the min of a column. `Assumes arguments are literal values.` -``max()`` - Calculate the max of a column. `Assumes arguments are literal values.` -``count()`` - Calculate the count. `Assumes arguments are literal values.` -``cast()`` - Convert a field or expression from one data type to another. -``concat()`` - Concatenate two values together. `Assumes arguments are bound parameters.` -``coalesce()`` - Coalesce values. `Assumes arguments are bound parameters.` -``dateDiff()`` - Get the difference between two dates/times. `Assumes arguments are bound parameters.` -``now()`` - Defaults to returning date and time, but accepts 'time' or 'date' to return only - those values. -``extract()`` - Returns the specified date part from the SQL expression. -``dateAdd()`` - Add the time unit to the date expression. -``dayOfWeek()`` - Returns a FunctionExpression representing a call to SQL WEEKDAY function. - -Window-Only Functions -^^^^^^^^^^^^^^^^^^^^^ - -These window-only functions contain a window expression by default: - -``rowNumber()`` - Returns an Aggregate expression for the ``ROW_NUMBER()`` SQL function. -``lag()`` - Returns an Aggregate expression for the ``LAG()`` SQL function. -``lead()`` - Returns an Aggregate expression for the ``LEAD()`` SQL function. - -When providing arguments for SQL functions, there are two kinds of parameters -you can use, literal arguments and bound parameters. Identifier/Literal parameters allow -you to reference columns or other SQL literals. Bound parameters can be used to -safely add user data to SQL functions. For example:: - - $query = $articles->find()->innerJoinWith('Categories'); - $concat = $query->func()->concat([ - 'Articles.title' => 'identifier', - ' - CAT: ', - 'Categories.name' => 'identifier', - ' - Age: ', - $query->func()->dateDiff([ - 'NOW()' => 'literal', - 'Articles.created' => 'identifier', - ]) - ]); - $query->select(['link_title' => $concat]); - -Both ``literal`` and ``identifier`` arguments allow you to reference other columns -and SQL literals while ``identifier`` will be appropriately quoted if auto-quoting -is enabled. If not marked as literal or identifier, arguments will be bound -parameters allowing you to safely pass user data to the function. - -The above example generates something like this in MYSQL. - - -.. code-block:: mysql - - SELECT CONCAT( - Articles.title, - :c0, - Categories.name, - :c1, - (DATEDIFF(NOW(), Articles.created)) - ) FROM articles; - -The ``:c0`` argument will have ``' - CAT:'`` text bound when the query is -executed. The ``dateDiff`` expression was translated to the appropriate SQL. - -Custom Functions -^^^^^^^^^^^^^^^^ - -If ``func()`` does not already wrap the SQL function you need, you can call -it directly through ``func()`` and still safely pass arguments and user data -as described. Make sure you pass the appropriate argument type for custom -functions or they will be treated as bound parameters:: - - $query = $articles->find(); - $year = $query->func()->year([ - 'created' => 'identifier' - ]); - $time = $query->func()->date_format([ - 'created' => 'identifier', - "'%H:%i'" => 'literal' - ]); - $query->select([ - 'yearCreated' => $year, - 'timeCreated' => $time - ]); - -These custom function would generate something like this in MYSQL: - -.. code-block:: mysql - - SELECT YEAR(created) as yearCreated, - DATE_FORMAT(created, '%H:%i') as timeCreated - FROM articles; - -.. note:: - Use ``func()`` to pass untrusted user data to any SQL function. - -Ordering Results ----------------- - -To apply ordering, you can use the ``order`` method:: - - $query = $articles->find() - ->orderBy(['title' => 'ASC', 'id' => 'ASC']); - -When calling ``orderBy()`` multiple times on a query, multiple clauses will be -appended. However, when using finders you may sometimes need to overwrite the -``ORDER BY``. Set the second parameter of ``orderBy()`` (as well as -``orderByAsc()`` or ``orderByDesc()``) to ``SelectQuery::OVERWRITE`` or to ``true``:: - - $query = $articles->find() - ->orderBy(['title' => 'ASC']); - // Later, overwrite the ORDER BY clause instead of appending to it. - $query = $articles->find() - ->orderBy(['created' => 'DESC'], SelectQuery::OVERWRITE); - -The ``orderByAsc`` and ``orderByDesc`` methods can be used when you need to sort on -complex expressions:: - - $query = $articles->find(); - $concat = $query->func()->concat([ - 'title' => 'identifier', - 'synopsis' => 'identifier' - ]); - $query->orderByAsc($concat); - -To build complex order clauses, use a Closure to build order expressions:: - - $query->orderByAsc(function (QueryExpression $exp, SelectQuery $query) { - return $exp->addCase(/* ... */); - }); - - -Limiting Results ----------------- - -To limit the number of rows or set the row offset you can use the ``limit()`` -and ``page()`` methods:: - - // Fetch rows 50 to 100 - $query = $articles->find() - ->limit(50) - ->page(2); - -As you can see from the examples above, all the methods that modify the query -provide a fluent interface, allowing you to build a query through chained method -calls. - -Aggregates - Group and Having ------------------------------ - -When using aggregate functions like ``count`` and ``sum`` you may want to use -``group by`` and ``having`` clauses:: - - $query = $articles->find(); - $query->select([ - 'count' => $query->func()->count('view_count'), - 'published_date' => 'DATE(created)' - ]) - ->groupBy('published_date') - ->having(['count >' => 3]); - -Case Statements ---------------- - -The ORM also offers the SQL ``case`` expression. The ``case`` expression allows -for implementing ``if ... then ... else`` logic inside your SQL. This can be useful -for reporting on data where you need to conditionally sum or count data, or where you -need to specific data based on a condition. - -If we wished to know how many published articles are in our database, we could use the following SQL: - -.. code-block:: sql - - SELECT - COUNT(CASE WHEN published = 'Y' THEN 1 END) AS number_published, - COUNT(CASE WHEN published = 'N' THEN 1 END) AS number_unpublished - FROM articles - -To do this with the query builder, we'd use the following code:: - - $query = $articles->find(); - $publishedCase = $query->newExpr() - ->case() - ->when(['published' => 'Y']) - ->then(1); - $unpublishedCase = $query->newExpr() - ->case() - ->when(['published' => 'N']) - ->then(1); - - $query->select([ - 'number_published' => $query->func()->count($publishedCase), - 'number_unpublished' => $query->func()->count($unpublishedCase) - ]); - -The ``when()`` method accepts SQL snippets, array conditions, and ``Closure`` -for when you need additional logic to build the cases. If we wanted to classify -cities into SMALL, MEDIUM, or LARGE based on population size, we could do the -following:: - - $query = $cities->find(); - $sizing = $query->newExpr()->case() - ->when(['population <' => 100000]) - ->then('SMALL') - ->when($query->newExpr()->between('population', 100000, 999000)) - ->then('MEDIUM') - ->when(['population >=' => 999001]) - ->then('LARGE'); - $query = $query->select(['size' => $sizing]); - # SELECT CASE - # WHEN population < 100000 THEN 'SMALL' - # WHEN population BETWEEN 100000 AND 999000 THEN 'MEDIUM' - # WHEN population >= 999001 THEN 'LARGE' - # END AS size - -You need to be careful when including user provided data into case expressions -as it can create SQL injection vulnerabilities:: - - // Unsafe do *not* use - $case->when($requestData['published']); - - // Instead pass user data as values to array conditions - $case->when(['published' => $requestData['published']]); - -For more complex scenarios you can use ``QueryExpression`` objects and bound -values:: - - $userValue = $query->newExpr() - ->case() - ->when($query->newExpr('population >= :userData')) - ->then(123, 'integer'); - - $query->select(['val' => $userValue]) - ->bind(':userData', $requestData['value'], 'integer'); - -By using bindings you can safely embed user data into complex raw SQL snippets. - -``then()``, ``when()`` and ``else()`` will try to infer the -value type based on the parameter type. If you need to bind a value as -a different type you can declare the desired type:: - - $case->when(['published' => true])->then('1', 'integer'); - -You can create ``if ... then ... else`` conditions by using ``else()``:: - - $published = $query->newExpr() - ->case() - ->when(['published' => true]) - ->then('Y'); - ->else('N'); - - # CASE WHEN published = true THEN 'Y' ELSE 'N' END; - -Also, it's possible to create the simple variant by passing a value to ``case()``:: - - $published = $query->newExpr() - ->case($query->identifier('published')) - ->when(true) - ->then('Y'); - ->else('N'); - - # CASE published WHEN true THEN 'Y' ELSE 'N' END; - -The ``addCase`` function can also chain together multiple statements to create -``if .. then .. [elseif .. then .. ] [ .. else ]`` logic inside your SQL. - -If we wanted to classify cities into SMALL, MEDIUM, or LARGE based on population -size, we could do the following:: - - $query = $cities->find() - ->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->addCase( - [ - $q->newExpr()->lt('population', 100000), - $q->newExpr()->between('population', 100000, 999000), - $q->newExpr()->gte('population', 999001), - ], - ['SMALL', 'MEDIUM', 'LARGE'], # values matching conditions - ['string', 'string', 'string'] # type of each value - ); - }); - # WHERE CASE - # WHEN population < 100000 THEN 'SMALL' - # WHEN population BETWEEN 100000 AND 999000 THEN 'MEDIUM' - # WHEN population >= 999001 THEN 'LARGE' - # END - -Any time there are fewer case conditions than values, ``addCase`` will -automatically produce an ``if .. then .. else`` statement:: - - $query = $cities->find() - ->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->addCase( - [ - $q->newExpr()->eq('population', 0), - ], - ['DESERTED', 'INHABITED'], # values matching conditions - ['string', 'string'] # type of each value - ); - }); - # WHERE CASE - # WHEN population = 0 THEN 'DESERTED' ELSE 'INHABITED' END - -Fetching Arrays Instead of Entities ------------------------------------ - -While ORMs and object result sets are powerful, creating entities is sometimes -unnecessary. For example, when accessing aggregated data, building an Entity may -not make sense. The process of converting the database results to entities is -called hydration. If you wish to disable this process you can do this:: - - $query = $articles->find(); - $query->enableHydration(false); // Results as arrays instead of entities - $result = $query->toList(); // Execute the query and return the array - -After executing those lines, your result should look similar to this:: - - [ - ['id' => 1, 'title' => 'First Article', 'body' => 'Article 1 body' ...], - ['id' => 2, 'title' => 'Second Article', 'body' => 'Article 2 body' ...], - ... - ] - -.. _format-results: - -Adding Calculated Fields ------------------------- - -After your queries, you may need to do some post-processing. If you need to add -a few calculated fields or derived data, you can use the ``formatResults()`` -method. This is a lightweight way to map over the result sets. If you need more -control over the process, or want to reduce results you should use -the :ref:`Map/Reduce ` feature instead. If you were querying a list -of people, you could calculate their age with a result formatter:: - - // Assuming we have built the fields, conditions and containments. - $query->formatResults(function (\Cake\Collection\CollectionInterface $results) { - return $results->map(function ($row) { - $row['age'] = $row['birth_date']->diff(new \DateTime)->y; - - return $row; - }); - }); - -As you can see in the example above, formatting callbacks will get a -``ResultSetDecorator`` as their first argument. The second argument will be -the Query instance the formatter was attached to. The ``$results`` argument can -be traversed and modified as necessary. - -Result formatters are required to return an iterator object, which will be used -as the return value for the query. Formatter functions are applied after all the -Map/Reduce routines have been executed. Result formatters can be applied from -within contained associations as well. CakePHP will ensure that your formatters -are properly scoped. For example, doing the following would work as you may -expect:: - - // In a method in the Articles table - $query->contain(['Authors' => function ($q) { - return $q->formatResults(function (\Cake\Collection\CollectionInterface $authors) { - return $authors->map(function ($author) { - $author['age'] = $author['birth_date']->diff(new \DateTime)->y; - - return $author; - }); - }); - }]); - - // Get results - $results = $query->all(); - - // Outputs 29 - echo $results->first()->author->age; - -As seen above, the formatters attached to associated query builders are scoped -to operate only on the data in the association. CakePHP will ensure that -computed values are inserted into the correct entity. - -If you want to replace the results of an association finder with -``formatResults`` and your replacement data is an associative array, use -``preserveKeys`` to retain keys when results are mapped to the parent query. For -example:: - - public function findSlugged(SelectQuery $query): SelectQuery - { - return $query->applyOptions(['preserveKeys' => true]) - ->formatResults(function ($results) { - return $results->indexBy(function ($record) { - return Text::slug($record->name); - }); - }); - } - -The ``preserveKeys`` option can be set as a contain option as well. - -.. versionadded:: 5.1.0 - The ``preserveKeys`` option was added. - - - -.. _advanced-query-conditions: - -Advanced Conditions -=================== - -The query builder makes it simple to build complex ``where`` clauses. -Grouped conditions can be expressed by providing combining ``where()`` and -expression objects. For simple queries, you can build conditions using -an array of conditions:: - - $query = $articles->find() - ->where([ - 'author_id' => 3, - 'OR' => [['view_count' => 2], ['view_count' => 3]], - ]); - -The above would generate SQL like - -.. code-block:: sql - - SELECT * FROM articles WHERE author_id = 3 AND (view_count = 2 OR view_count = 3) - -If you'd prefer to avoid deeply nested arrays, you can use the callback form of -``where()`` to build your queries. The callback accepts a QueryExpression which allows -you to use the expression builder interface to build more complex conditions without arrays. -For example:: - - $query = $articles->find()->where(function (QueryExpression $exp, SelectQuery $query) { - // Use add() to add multiple conditions for the same field. - $author = $query->newExpr()->or(['author_id' => 3])->add(['author_id' => 2]); - $published = $query->newExpr()->and(['published' => true, 'view_count' => 10]); - - return $exp->or([ - 'promoted' => true, - $query->newExpr()->and([$author, $published]) - ]); - }); - -The above generates SQL similar to: - -.. code-block:: sql - - SELECT * - FROM articles - WHERE ( - ( - (author_id = 2 OR author_id = 3) - AND - (published = 1 AND view_count = 10) - ) - OR promoted = 1 - ) - -The ``QueryExpression`` passed to the callback allows you to use both -**combinators** and **conditions** to build the full expression. - -Combinators - These create new ``QueryExpression`` objects and set how the conditions added - to that expression are joined together. - - - ``and()`` creates new expression objects that joins all conditions with ``AND``. - - ``or()`` creates new expression objects that joins all conditions with ``OR``. - -Conditions - These are added to the expression and automatically joined together - depending on which combinator was used. - -The ``QueryExpression`` passed to the callback function defaults to ``and()``:: - - $query = $articles->find() - ->where(function (QueryExpression $exp) { - return $exp - ->eq('author_id', 2) - ->eq('published', true) - ->notEq('spam', true) - ->gt('view_count', 10); - }); - -Since we started off using ``where()``, we don't need to call ``and()``, as -that happens implicitly. The above shows a few new condition -methods being combined with ``AND``. The resulting SQL would look like: - -.. code-block:: sql - - SELECT * - FROM articles - WHERE ( - author_id = 2 - AND published = 1 - AND spam != 1 - AND view_count > 10) - -However, if we wanted to use both ``AND`` & ``OR`` conditions we could do the -following:: - - $query = $articles->find() - ->where(function (QueryExpression $exp) { - $orConditions = $exp->or(['author_id' => 2]) - ->eq('author_id', 5); - - return $exp - ->add($orConditions) - ->eq('published', true) - ->gte('view_count', 10); - }); - -Which would generate the SQL similar to: - -.. code-block:: sql - - SELECT * - FROM articles - WHERE ( - (author_id = 2 OR author_id = 5) - AND published = 1 - AND view_count >= 10 - ) - -The **combinators** also allow you pass in a callback which takes -the new expression object as a parameter if you want to separate -the method chaining:: - - $query = $articles->find() - ->where(function (QueryExpression $exp) { - $orConditions = $exp->or(function (QueryExpression $or) { - return $or->eq('author_id', 2) - ->eq('author_id', 5); - }); - - return $exp - ->not($orConditions) - ->lte('view_count', 10); - }); - -You can negate sub-expressions using ``not()``:: - - $query = $articles->find() - ->where(function (QueryExpression $exp) { - $orConditions = $exp->or(['author_id' => 2]) - ->eq('author_id', 5); - - return $exp - ->not($orConditions) - ->lte('view_count', 10); - }); - -Which will generate the following SQL looking like: - -.. code-block:: sql - - SELECT * - FROM articles - WHERE ( - NOT (author_id = 2 OR author_id = 5) - AND view_count <= 10 - ) - -It is also possible to build expressions using SQL functions:: - - $query = $articles->find() - ->where(function (QueryExpression $exp, SelectQuery $q) { - $year = $q->func()->year([ - 'created' => 'identifier' - ]); - - return $exp - ->gte($year, 2014) - ->eq('published', true); - }); - -Which will generate the following SQL looking like: - -.. code-block:: sql - - SELECT * - FROM articles - WHERE ( - YEAR(created) >= 2014 - AND published = 1 - ) - -When using the expression objects you can use the following methods to create -conditions: - -- ``eq()`` Creates an equality condition:: - - $query = $cities->find() - ->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->eq('population', '10000'); - }); - # WHERE population = 10000 - -- ``notEq()`` Creates an inequality condition:: - - $query = $cities->find() - ->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->notEq('population', '10000'); - }); - # WHERE population != 10000 - -- ``like()`` Creates a condition using the ``LIKE`` operator:: - - $query = $cities->find() - ->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->like('name', '%A%'); - }); - # WHERE name LIKE "%A%" - -- ``notLike()`` Creates a negated ``LIKE`` condition:: - - $query = $cities->find() - ->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->notLike('name', '%A%'); - }); - # WHERE name NOT LIKE "%A%" - -- ``in()`` Create a condition using ``IN``:: - - $query = $cities->find() - ->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->in('country_id', ['AFG', 'USA', 'EST']); - }); - # WHERE country_id IN ('AFG', 'USA', 'EST') - -- ``notIn()`` Create a negated condition using ``IN``:: - - $query = $cities->find() - ->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->notIn('country_id', ['AFG', 'USA', 'EST']); - }); - # WHERE country_id NOT IN ('AFG', 'USA', 'EST') - -- ``gt()`` Create a ``>`` condition:: - - $query = $cities->find() - ->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->gt('population', '10000'); - }); - # WHERE population > 10000 - -- ``gte()`` Create a ``>=`` condition:: - - $query = $cities->find() - ->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->gte('population', '10000'); - }); - # WHERE population >= 10000 - -- ``lt()`` Create a ``<`` condition:: - - $query = $cities->find() - ->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->lt('population', '10000'); - }); - # WHERE population < 10000 - -- ``lte()`` Create a ``<=`` condition:: - - $query = $cities->find() - ->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->lte('population', '10000'); - }); - # WHERE population <= 10000 - -- ``isNull()`` Create an ``IS NULL`` condition:: - - $query = $cities->find() - ->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->isNull('population'); - }); - # WHERE (population) IS NULL - -- ``isNotNull()`` Create a negated ``IS NULL`` condition:: - - $query = $cities->find() - ->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->isNotNull('population'); - }); - # WHERE (population) IS NOT NULL - -- ``between()`` Create a ``BETWEEN`` condition:: - - $query = $cities->find() - ->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->between('population', 999, 5000000); - }); - # WHERE population BETWEEN 999 AND 5000000, - -- ``exists()`` Create a condition using ``EXISTS``:: - - $subquery = $cities->find() - ->select(['id']) - ->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->equalFields('countries.id', 'cities.country_id'); - }) - ->andWhere(['population >' => 5000000]); - - $query = $countries->find() - ->where(function (QueryExpression $exp, SelectQuery $q) use ($subquery) { - return $exp->exists($subquery); - }); - # WHERE EXISTS (SELECT id FROM cities WHERE countries.id = cities.country_id AND population > 5000000) - -- ``notExists()`` Create a negated condition using ``EXISTS``:: - - $subquery = $cities->find() - ->select(['id']) - ->where(function (QueryExpression $exp, SelectQuery $q) { - return $exp->equalFields('countries.id', 'cities.country_id'); - }) - ->andWhere(['population >' => 5000000]); - - $query = $countries->find() - ->where(function (QueryExpression $exp, SelectQuery $q) use ($subquery) { - return $exp->notExists($subquery); - }); - # WHERE NOT EXISTS (SELECT id FROM cities WHERE countries.id = cities.country_id AND population > 5000000) - -Expression objects should cover many commonly used functions and expressions. If -you find yourself unable to create the required conditions with expressions you -can may be able to use ``bind()`` to manually bind parameters into conditions:: - - $query = $cities->find() - ->where([ - 'start_date BETWEEN :start AND :end', - ]) - ->bind(':start', '2014-01-01', 'date') - ->bind(':end', '2014-12-31', 'date'); - -In situations when you can't get, or don't want to use the builder methods to -create the conditions you want you can also use snippets of SQL in where -clauses:: - - // Compare two fields to each other - $query->where(['Categories.parent_id != Parents.id']); - -.. warning:: - - The field names used in expressions, and SQL snippets should **never** - contain untrusted content as you will create SQL Injection vectors. See the - :ref:`using-sql-functions` section for how to safely include unsafe data - into function calls. - -Using Identifiers in Expressions --------------------------------- - -When you need to reference a column or SQL identifier in your queries you can -use the ``identifier()`` method:: - - $query = $countries->find(); - $query->select([ - 'year' => $query->func()->year([$query->identifier('created')]) - ]) - ->where(function ($exp, $query) { - return $exp->gt('population', 100000); - }); - -You can use ``identifier()`` in comparisons to aggregations too:: - - $query = $this->Orders->find(); - $query->select(['Customers.customer_name', 'total_orders' => $query->func()->count('Orders.order_id')]) - ->contain('Customers') - ->groupBy(['Customers.customer_name']) - ->having(['total_orders >=' => $query->identifier('Customers.minimum_order_count')]); - -.. warning:: - - To prevent SQL injections, Identifier expressions should never have - untrusted data passed into them. - -Collation ---------------------------------- - -In situations that you need to deal with accented characters, multilingual data -or case-sensitive comparisons, you can use the ``$collation`` parameter of ``IdentifierExpression`` -or ``StringExpression`` to apply a character expression to a certain collation:: - - use Cake\Database\Expression\IdentifierExpression; - - $collation = 'Latin1_general_CI_AI'; //sql server example - $query = $cities->find() - ->where(function (QueryExpression $exp, Query $q) use ($collation) { - return $exp->like(new IdentifierExpression('name', $collation), '%São José%'); - }); - # WHERE name COLLATE LIKE Latin1_general_CI_AI "%São José%" - - -Automatically Creating IN Clauses ---------------------------------- - -When building queries using the ORM, you will generally not have to indicate the -data types of the columns you are interacting with, as CakePHP can infer the -types based on the schema data. If in your queries you'd like CakePHP to -automatically convert equality to ``IN`` comparisons, you'll need to indicate -the column data type:: - - $query = $articles->find() - ->where(['id' => $ids], ['id' => 'integer[]']); - - // Or include IN to automatically cast to an array. - $query = $articles->find() - ->where(['id IN' => $ids]); - -The above will automatically create ``id IN (...)`` instead of ``id = ?``. This -can be useful when you do not know whether you will get a scalar or array of -parameters. The ``[]`` suffix on any data type name indicates to the query -builder that you want the data handled as an array. If the data is not an array, -it will first be cast to an array. After that, each value in the array will -be cast using the :ref:`type system `. This works with -complex types as well. For example, you could take a list of DateTime objects -using:: - - $query = $articles->find() - ->where(['post_date' => $dates], ['post_date' => 'date[]']); - -Automatic IS NULL Creation --------------------------- - -When a condition value is expected to be ``null`` or any other value, you can -use the ``IS`` operator to automatically create the correct expression:: - - $query = $categories->find() - ->where(['parent_id IS' => $parentId]); - -The above will generate``parent_id = :c1`` or ``parent_id IS NULL`` depending on -the type of ``$parentId`` - -Automatic IS NOT NULL Creation ------------------------------- - -When a condition value is expected not to be ``null`` or any other value, you -can use the ``IS NOT`` operator to automatically create the correct expression:: - - $query = $categories->find() - ->where(['parent_id IS NOT' => $parentId]); - -The above will generate``parent_id != :c1`` or ``parent_id IS NOT NULL`` -depending on the type of ``$parentId`` - - -Raw Expressions ---------------- - -When you cannot construct the SQL you need using the query builder, you can use -expression objects to add snippets of SQL to your queries:: - - $query = $articles->find(); - $expr = $query->newExpr()->add('1 + 1'); - $query->select(['two' => $expr]); - -``Expression`` objects can be used with any query builder methods like -``where()``, ``limit()``, ``groupBy()``, ``select()`` and many other methods. - -.. warning:: - - Using expression objects leaves you vulnerable to SQL injection. You should - never use untrusted data into expressions. - -Using Connection Roles ----------------------- - -If you have configured :ref:`read-and-write-connections` in your application, -you can have a query run on the ``read`` connection using one of the role -methods:: - - // Run a query on the read connection - $query->useReadRole(); - - // Run a query on the write connection (default) - $query->useWriteRole(); - -.. versionadded:: 4.5.0 - Query role methods were added in 4.5.0 - -Expression Conjuction ---------------------- - -It is possible to change the conjunction used to join conditions in a query -expression using the method ``setConjunction``:: - - $query = $articles->find(); - $expr = $query->newExpr(['1','1'])->setConjunction('+'); - $query->select(['two' => $expr]); - -And can be used combined with aggregations too:: - - $query = $products->find(); - $query->select(function ($query) { - $stockQuantity = $query->func()->sum('Stocks.quantity'); - $totalStockValue = $query->func()->sum( - $query->newExpr(['Stocks.quantity', 'Products.unit_price']) - ->setConjunction('*') - ); - - return [ - 'Products.name', - 'stock_quantity' => $stockQuantity, - 'Products.unit_price', - 'total_stock_value' => $totalStockValue - ]; - }) - ->innerJoinWith('Stocks') - ->groupBy(['Products.id', 'Products.name', 'Products.unit_price']); - -Tuple Comparison ----------------- - -Tuple comparison involves comparing two rows of data (tuples) element by element, -typically using comparison operators like ``<, >, =``:: - - $products->find() - ->where([ - 'OR' => [ - ['unit_price <' => 20], - ['unit_price' => 20, 'tax_percentage <=' => 5], - ] - ]); - - # WHERE (unit_price < 20 OR (unit_price = 20 AND tax_percentage <= 5)) - -The same result can be achieved using ``TupleComparison``:: - - use Cake\Database\Expression\TupleComparison; - - $products->find() - ->where( - new TupleComparison( - ['unit_price', 'tax_percentage'], - [20, 5], - ['integer', 'integer'], # type of each value - '<=' - ) - ); - - # WHERE (unit_price, tax_percentage) <= (20, 5)) - -Tuple Comparison can also be used with ``IN`` and the result can be transformed -even on DBMS that does not natively support it:: - - $articles->find() - ->where( - new TupleComparison( - ['articles.id', 'articles.author_id'], - [[10, 10], [30, 10]], - ['integer', 'integer'], - 'IN' - ), - ); - - # WHERE (1) = ( SELECT (1) WHERE ( ( articles.id = : 10 AND articles.author_id = : 10 ) OR ( articles.id = : 30 AND articles.author_id = : 30 ) ) ) - -.. note:: - Tuple comparison transform only supports the ``IN`` and ``=`` operators - -Getting Results -=============== - -Once you've made your query, you'll want to retrieve rows from it. There are -a few ways of doing this:: - - // Iterate the query - foreach ($query as $row) { - // Do stuff. - } - - // Get the results - $results = $query->all(); - -You can use :doc:`any of the collection ` methods -on your query objects to pre-process or transform the results:: - - // Use one of the collection methods. - $ids = $query->map(function ($row) { - return $row->id; - }); - - $maxAge = $query->max(function ($max) { - return $max->age; - }); - -You can use ``first`` or ``firstOrFail`` to retrieve a single record. These -methods will alter the query adding a ``LIMIT 1`` clause:: - - // Get just the first row - $row = $query->first(); - - // Get the first row or an exception. - $row = $query->firstOrFail(); - -.. _query-count: - -Returning the Total Count of Records ------------------------------------- - -Using a single query object, it is possible to obtain the total number of rows -found for a set of conditions:: - - $total = $articles->find()->where(['is_active' => true])->count(); - -The ``count()`` method will ignore the ``limit``, ``offset`` and ``page`` -clauses, thus the following will return the same result:: - - $total = $articles->find()->where(['is_active' => true])->limit(10)->count(); - -This is useful when you need to know the total result set size in advance, -without having to construct another ``SelectQuery`` object. Likewise, all result -formatting and map-reduce routines are ignored when using the ``count()`` -method. - -Moreover, it is possible to return the total count for a query containing group -by clauses without having to rewrite the query in any way. For example, consider -this query for retrieving article ids and their comments count:: - - $query = $articles->find(); - $query->select(['Articles.id', $query->func()->count('Comments.id')]) - ->matching('Comments') - ->groupBy(['Articles.id']); - $total = $query->count(); - -After counting, the query can still be used for fetching the associated -records:: - - $list = $query->all(); - -Sometimes, you may want to provide an alternate method for counting the total -records of a query. One common use case for this is providing -a cached value or an estimate of the total rows, or to alter the query to remove -expensive unneeded parts such as left joins. This becomes particularly handy -when using the CakePHP built-in pagination system which calls the ``count()`` -method:: - - $query = $query->where(['is_active' => true])->counter(function ($query) { - return 100000; - }); - $query->count(); // Returns 100000 - -In the example above, when the pagination component calls the count method, it -will receive the estimated hard-coded number of rows. - -.. _caching-query-results: - -Caching Loaded Results ----------------------- - -When fetching entities that don't change often you may want to cache the -results. The ``SelectQuery`` class makes this simple:: - - $query->cache('recent_articles'); - -Will enable caching on the query's result set. If only one argument is provided -to ``cache()`` then the 'default' cache configuration will be used. You can -control which caching configuration is used with the second parameter:: - - // String config name. - $query->cache('recent_articles', 'dbResults'); - - // Instance of CacheEngine - $query->cache('recent_articles', $memcache); - -In addition to supporting static keys, the ``cache()`` method accepts a function -to generate the key. The function you give it will receive the query as an -argument. You can then read aspects of the query to dynamically generate the -cache key:: - - // Generate a key based on a simple checksum - // of the query's where clause - $query->cache(function ($q) { - return 'articles-' . md5(serialize($q->clause('where'))); - }); - -The cache method makes it simple to add cached results to your custom finders or -through event listeners. - -When the results for a cached query are fetched the following happens: - -1. If the query has results set, those will be returned. -2. The cache key will be resolved and cache data will be read. If the cache data - is not empty, those results will be returned. -3. If the cache misses, the query will be executed, the ``Model.beforeFind`` event - will be triggered, and a new ``ResultSet`` will be created. This - ``ResultSet`` will be written to the cache and returned. - -.. note:: - - You cannot cache a streaming query result. - -Loading Associations -==================== - -The builder can help you retrieve data from multiple tables at the same time -with the minimum amount of queries possible. To be able to fetch associated -data, you first need to setup associations between the tables as described in -the :doc:`/orm/associations` section. This technique of combining queries -to fetch associated data from other tables is called **eager loading**. - -.. include:: ./retrieving-data-and-resultsets.rst - :start-after: start-contain - :end-before: end-contain - -Filtering by Associated Data ----------------------------- - -.. include:: ./retrieving-data-and-resultsets.rst - :start-after: start-filtering - :end-before: end-filtering - -.. _adding-joins: - -Adding Joins ------------- - -In addition to loading related data with ``contain()``, you can also add -additional joins with the query builder:: - - $query = $articles->find() - ->join([ - 'table' => 'comments', - 'alias' => 'c', - 'type' => 'LEFT', - 'conditions' => 'c.article_id = articles.id', - ]); - -You can append multiple joins at the same time by passing an associative array -with multiple joins:: - - $query = $articles->find() - ->join([ - 'c' => [ - 'table' => 'comments', - 'type' => 'LEFT', - 'conditions' => 'c.article_id = articles.id', - ], - 'u' => [ - 'table' => 'users', - 'type' => 'INNER', - 'conditions' => 'u.id = articles.user_id', - ] - ]); - -As seen above, when adding joins the alias can be the outer array key. Join -conditions can also be expressed as an array of conditions:: - - $query = $articles->find() - ->join([ - 'c' => [ - 'table' => 'comments', - 'type' => 'LEFT', - 'conditions' => [ - 'c.created >' => new DateTime('-5 days'), - 'c.moderated' => true, - 'c.article_id = articles.id' - ] - ], - ], ['c.created' => 'datetime', 'c.moderated' => 'boolean']); - -When creating joins by hand and using array based conditions, you need to -provide the datatypes for each column in the join conditions. By providing -datatypes for the join conditions, the ORM can correctly convert data types into -SQL. In addition to ``join()`` you can use ``rightJoin()``, ``leftJoin()`` and -``innerJoin()`` to create joins:: - - // Join with an alias and string conditions - $query = $articles->find(); - $query->leftJoin( - ['Authors' => 'authors'], - ['Authors.id = Articles.author_id']); - - // Join with an alias, array conditions, and types - $query = $articles->find(); - $query->innerJoin( - ['Authors' => 'authors'], - [ - 'Authors.promoted' => true, - 'Authors.created' => new DateTime('-5 days'), - 'Authors.id = Articles.author_id', - ], - [ - 'Authors.promoted' => 'boolean', - 'Authors.created' => 'datetime', - ] - ); - -It should be noted that if you set the ``quoteIdentifiers`` option to ``true`` when -defining your ``Connection``, join conditions between table fields should be set as follow:: - - $query = $articles->find() - ->join([ - 'c' => [ - 'table' => 'comments', - 'type' => 'LEFT', - 'conditions' => [ - 'c.article_id' => new \Cake\Database\Expression\IdentifierExpression('articles.id'), - ], - ], - ]); - -This ensures that all of your identifiers will be quoted across the Query, avoiding errors with -some database Drivers (PostgreSQL notably) - -Inserting Data -============== - -Unlike earlier examples, you should can't use ``find()`` to create insert queries. -Instead, create a new ``InsertQuery`` object using ``insertQuery()``:: - - $query = $articles->insertQuery(); - $query->insert(['title', 'body']) - ->values([ - 'title' => 'First post', - 'body' => 'Some body text', - ]) - ->execute(); - -To insert multiple rows with only one query, you can chain the ``values()`` -method as many times as you need:: - - $query = $articles->insertQuery(); - $query->insert(['title', 'body']) - ->values([ - 'title' => 'First post', - 'body' => 'Some body text', - ]) - ->values([ - 'title' => 'Second post', - 'body' => 'Another body text', - ]) - ->execute(); - -Generally, it is easier to insert data using entities and -:php:meth:`~Cake\\ORM\\Table::save()`. By composing a ``SELECT`` and -``INSERT`` query together, you can create ``INSERT INTO ... SELECT`` style -queries:: - - $select = $articles->find() - ->select(['title', 'body', 'published']) - ->where(['id' => 3]); - - $query = $articles->insertQuery() - ->insert(['title', 'body', 'published']) - ->values($select) - ->execute(); - -.. note:: - Inserting records with the query builder will not trigger events such as - ``Model.afterSave``. Instead you should use the :doc:`ORM to save - data `. - -.. _query-builder-updating-data: - -Updating Data -============= - -As with insert queries, you should not use ``find()`` to create update queries. -Instead, create new a ``Query`` object using ``updateQuery()``:: - - $query = $articles->updateQuery(); - $query->set(['published' => true]) - ->where(['id' => $id]) - ->execute(); - -Generally, it is easier to update data using entities and -:php:meth:`~Cake\\ORM\\Table::patchEntity()`. - -.. note:: - Updating records with the query builder will not trigger events such as - ``Model.afterSave``. Instead you should use the :doc:`ORM to save - data `. - -Deleting Data -============= - -As with insert queries, you can't use ``find()`` to create delete queries. -Instead, create new a query object using ``deleteQuery()``:: - - $query = $articles->deleteQuery(); - $query->where(['id' => $id]) - ->execute(); - -Generally, it is easier to delete data using entities and -:php:meth:`~Cake\\ORM\\Table::delete()`. - -SQL Injection Prevention -======================== - -While the ORM and database abstraction layers prevent most SQL injections -issues, it is still possible to leave yourself vulnerable through improper use. - -When using condition arrays, the key/left-hand side as well as single value -entries must not contain user data:: - - $query->where([ - // Data on the key/left-hand side is unsafe, as it will be - // inserted into the generated query as-is - $userData => $value, - - // The same applies to single value entries, they are not - // safe to use with user data in any form - $userData, - "MATCH (comment) AGAINST ($userData)", - 'created < NOW() - ' . $userData - ]); - -When using the expression builder, column names must not contain user data:: - - $query->where(function (QueryExpression $exp) use ($userData, $values) { - // Column names in all expressions are not safe. - return $exp->in($userData, $values); - }); - -When building function expressions, function names should never contain user -data:: - - // Not safe. - $query->func()->{$userData}($arg1); - - // Also not safe to use an array of - // user data in a function expression - $query->func()->coalesce($userData); - -Raw expressions are never safe:: - - $expr = $query->newExpr()->add($userData); - $query->select(['two' => $expr]); - -Binding values --------------- - -It is possible to protect against many unsafe situations by using bindings. -Values can be bound to queries using the :php:meth:`Cake\\Database\\Query::bind()` -method. - -The following example would be a safe variant of the unsafe, SQL injection prone -example given above:: - - $query - ->where([ - 'MATCH (comment) AGAINST (:userData)', - 'created < NOW() - :moreUserData', - ]) - ->bind(':userData', $userData, 'string') - ->bind(':moreUserData', $moreUserData, 'datetime'); - -.. note:: - - Unlike :php:meth:`Cake\\Database\\StatementInterface::bindValue()`, - ``Query::bind()`` requires to pass the named placeholders including the - colon! - -More Complex Queries -==================== - -If your application requires using more complex queries, you can express many -complex queries using the ORM query builder. - -Unions ------- - -Unions are created by composing one or more select queries together:: - - $inReview = $articles->find() - ->where(['need_review' => true]); - - $unpublished = $articles->find() - ->where(['published' => false]); - - $unpublished->union($inReview); - -You can create ``UNION ALL`` queries using the ``unionAll()`` method:: - - $inReview = $articles->find() - ->where(['need_review' => true]); - - $unpublished = $articles->find() - ->where(['published' => false]); - - $unpublished->unionAll($inReview); - -Intersections -------------- - -Intersections allow you to combine the result sets of two queries together and -finding results with overlapping results. Intersections are created by composing -one or more select queries together:: - - $inReview = $articles->find() - ->where(['need_review' => true]); - - $unpublished = $articles->find() - ->where(['published' => false]); - - $unpublished->intersect($inReview); - -You can create ``INTERSECT ALL`` queries using the ``intersectAll()`` method:: - - $inReview = $articles->find() - ->where(['need_review' => true]); - - $unpublished = $articles->find() - ->where(['published' => false]); - - $unpublished->intersectAll($inReview); - -.. versionadded:: 5.1.0 - ``intersect()`` and ``intersectAll()`` were added. - -Subqueries ----------- - -Subqueries enable you to compose queries together and build conditions and -results based on the results of other queries:: - - $matchingComment = $articles->getAssociation('Comments')->find() - ->select(['article_id']) - ->distinct() - ->where(['comment LIKE' => '%CakePHP%']); - - // Use a subquery to create conditions - $query = $articles->find() - ->where(['id IN' => $matchingComment]); - - // Join the results of a subquery into another query. - // Giving the subquery an alias provides a way to reference - // results in subquery. - $query = $articles->find(); - $query->from(['matches' => $matchingComment]) - ->innerJoin( - ['Articles' => 'articles'], - ['Articles.id' => $query->identifier('matches.id') ] - ); - -Subqueries are accepted anywhere a query expression can be used. For example, in -the ``select()``, ``from()`` and ``join()`` methods. The above example uses a standard -``ORM\Query\SelectQuery`` object that will generate aliases, these aliases can make -referencing results in the outer query more complex. As of 4.2.0 you can use -``Table::subquery()`` to create a specialized query instance that will not -generate aliases:: - - $comments = $articles->getAssociation('Comments')->getTarget(); - - $matchingComment = $comments->subquery() - ->select(['article_id']) - ->distinct() - ->where(['comment LIKE' => '%CakePHP%']); - - $query = $articles->find() - ->where(['id IN' => $matchingComment]); - -Adding Locking Statements -------------------------- - -Most relational database vendors support taking out locks when doing select -operations. You can use the ``epilog()`` method for this:: - - // In MySQL - $query->epilog('FOR UPDATE'); - -The ``epilog()`` method allows you to append raw SQL to the end of queries. You -should never put raw user data into ``epilog()``. - -Window Functions ----------------- - -Window functions allow you to perform calculations using rows related to the -current row. They are commonly used to calculate totals or offsets on partial sets of rows -in the query. For example if we wanted to find the date of the earliest and latest comment on -each article we could use window functions:: - - $query = $articles->find(); - $query->select([ - 'Articles.id', - 'Articles.title', - 'Articles.user_id' - 'oldest_comment' => $query->func() - ->min('Comments.created') - ->partition('Comments.article_id'), - 'latest_comment' => $query->func() - ->max('Comments.created') - ->partition('Comments.article_id'), - ]) - ->innerJoinWith('Comments'); - -The above would generate SQL similar to: - -.. code-block:: sql - - SELECT - Articles.id, - Articles.title, - Articles.user_id - MIN(Comments.created) OVER (PARTITION BY Comments.article_id) AS oldest_comment, - MAX(Comments.created) OVER (PARTITION BY Comments.article_id) AS latest_comment, - FROM articles AS Articles - INNER JOIN comments AS Comments - -Window expressions can be applied to most aggregate functions. Any aggregate function -that cake abstracts with a wrapper in ``FunctionsBuilder`` will return an ``AggregateExpression`` -which lets you attach window expressions. You can create custom aggregate functions -through ``FunctionsBuilder::aggregate()``. - -These are the most commonly supported window features. Most features are provided -by ``AggregateExpresion``, but make sure you follow your database documentation on use and restrictions. - -- ``orderBy($fields)`` Order the aggregate group the same as a query ORDER BY. -- ``partition($expressions)`` Add one or more partitions to the window based on column - names. -- ``rows($start, $end)`` Define a offset of rows that precede and/or follow the - current row that should be included in the aggregate function. -- ``range($start, $end)`` Define a range of row values that precede and/or follow - the current row that should be included in the aggregate function. This - evaluates values based on the ``orderBy()`` field. - -If you need to re-use the same window expression multiple times you can create -named windows using the ``window()`` method:: - - $query = $articles->find(); - - // Define a named window - $query->window('related_article', function ($window, $query) { - $window->partition('Comments.article_id'); - - return $window; - }); - - $query->select([ - 'Articles.id', - 'Articles.title', - 'Articles.user_id' - 'oldest_comment' => $query->func() - ->min('Comments.created') - ->over('related_article'), - 'latest_comment' => $query->func() - ->max('Comments.created') - ->over('related_article'), - ]); - -Common Table Expressions ------------------------- - -Common Table Expressions or CTE are useful when building reporting queries where -you need to compose the results of several smaller query results together. They -can serve a similar purpose to database views or subquery results. Common Table -Expressions differ from derived tables and views in a couple ways: - -#. Unlike views, you don't have to maintain schema for common table expressions. - The schema is implicitly based on the result set of the table expression. -#. You can reference the results of a common table expression multiple times - without incurring performance penalties unlike subquery joins. - -As an example lets fetch a list of customers and the number of orders each of -them has made. In SQL we would use: - -.. code-block:: sql - - WITH orders_per_customer AS ( - SELECT COUNT(*) AS order_count, customer_id FROM orders GROUP BY customer_id - ) - SELECT name, orders_per_customer.order_count - FROM customers - INNER JOIN orders_per_customer ON orders_per_customer.customer_id = customers.id - -To build that query with the ORM query builder we would use:: - - // Start the final query - $query = $this->Customers->find(); - - // Attach a common table expression - $query->with(function ($cte) { - // Create a subquery to use in our table expression - $q = $this->Orders->subquery(); - $q->select([ - 'order_count' => $q->func()->count('*'), - 'customer_id', - ]) - ->groupBy('customer_id'); - - // Attach the new query to the table expression - return $cte - ->name('orders_per_customer') - ->query($q); - }); - - // Finish building the final query - $query->select([ - 'name', - 'order_count' => 'orders_per_customer.order_count', - ]) - ->join([ - // Define the join with our table expression - 'orders_per_customer' => [ - 'table' => 'orders_per_customer', - 'conditions' => 'orders_per_customer.customer_id = Customers.id', - ], - ]); - -If you need to build a recursive query (``WITH RECURSIVE …``), chain ``->recursive()`` onto ``return $cte``. - -Executing Complex Queries -------------------------- - -While the query builder makes most queries possible through builder methods, -very complex queries can be tedious and complicated to build. You may want to -:ref:`execute the desired SQL directly `. - -Executing SQL directly allows you to fine tune the query that will be run. -However, doing so doesn't let you use ``contain`` or other higher level ORM -features. diff --git a/en/orm/retrieving-data-and-resultsets.rst b/en/orm/retrieving-data-and-resultsets.rst deleted file mode 100644 index b2a06083a7..0000000000 --- a/en/orm/retrieving-data-and-resultsets.rst +++ /dev/null @@ -1,1266 +0,0 @@ -Retrieving Data & Results Sets -############################## - -.. php:namespace:: Cake\ORM - -.. php:class:: Table - -While table objects provide an abstraction around a 'repository' or collection -of objects, when you query for individual records you get 'entity' objects. -While this section discusses the different ways you can find and load entities, -you should read the :doc:`/orm/entities` section for more information on -entities. - -Debugging Queries and ResultSets -================================ - -Since the ORM now returns Collections and Entities, debugging these objects can -be more complicated than in previous CakePHP versions. There are now various -ways to inspect the data returned by the ORM. - -- ``debug($query)`` Shows the SQL and bound parameters, does not show results. -- ``sql($query)`` Shows the final rendered SQL when DebugKit is installed. -- ``debug($query->all())`` Shows the ResultSet properties (not the results). -- ``debug($query->toList())`` Show results in an array. -- ``debug(iterator_to_array($query))`` Shows query results in an array format. -- ``debug(json_encode($query, JSON_PRETTY_PRINT))`` More human readable results. -- ``debug($query->first())`` Show the properties of a single entity. -- ``debug((string)$query->first())`` Show the properties of a single entity as JSON. - -Getting a Single Entity by Primary Key -====================================== - -.. php:method:: get($id, $options = []) - -It is often convenient to load a single entity from the database when editing or -viewing entities and their related data. You can do this by using ``get()``:: - - // In a controller or table method. - - // Get a single article - $article = $articles->get($id); - - // Get a single article, and related comments - $article = $articles->get($id, contain: ['Comments']); - -If the get operation does not find any results a -``Cake\Datasource\Exception\RecordNotFoundException`` will be raised. You can -either catch this exception yourself, or allow CakePHP to convert it into a 404 -error. - -Like ``find()``, ``get()`` also has caching integrated. You can use the -``cache`` option when calling ``get()`` to perform read-through caching:: - - // In a controller or table method. - - // Use any cache config or CacheEngine instance & a generated key - $article = $articles->get($id, cache: 'custom'); - - // Use any cache config or CacheEngine instance & specific key - $article = $articles->get($id, cache: 'custom', key: 'mykey'); - - // Explicitly disable caching - $article = $articles->get($id, cache: false); - -Optionally you can ``get()`` an entity using :ref:`custom-find-methods`. For -example you may want to get all translations for an entity. You can achieve that -by using the ``finder`` option:: - - $article = $articles->get($id, 'translations'); - -The list of options supported by get() are: - -- ``cache`` cache config. -- ``key`` cache key. -- ``finder`` custom finder function. -- ``conditions`` provide conditions for the WHERE clause of your query. -- ``limit`` Set the number of rows you want. -- ``offset`` Set the page offset you want. You can also use ``page`` to make - the calculation simpler. -- ``contain`` define the associations to eager load. -- ``fields`` limit the fields loaded into the entity. Only loading some fields - can cause entities to behave incorrectly. -- ``group`` add a GROUP BY clause to your query. This is useful when using - aggregating functions. -- ``having`` add a HAVING clause to your query. -- ``join`` define additional custom joins. - - -Using Finders to Load Data -========================== - -.. php:method:: find($type, mixed ...$args) - -Before you can work with entities, you'll need to load them. The easiest way to -do this is using the ``find()`` method. The find method provides a short and -extensible way to find the data you are interested in:: - - // In a controller or table method. - - // Find all the articles - $query = $articles->find('all'); - -The return value of any ``find()`` method is always -a :php:class:`Cake\\ORM\\Query\\SelectQuery` object. The SelectQuery class allows you to further -refine a query after creating it. SelectQuery objects are evaluated lazily, and do not -execute until you start fetching rows, convert it to an array, or when the -``all()`` method is called:: - - // In a controller or table method. - - // Find all the articles. - // At this point the query has not run. - $query = $articles->find('all'); - - // Calling all() will execute the query - // and return the result set. - $results = $query->all(); - - // Once we have a result set we can get all the rows - $data = $results->toList(); - - // Converting the query to a key-value array will also execute it. - $data = $query->toArray(); - -.. note:: - - Once you've started a query you can use the :doc:`/orm/query-builder` - interface to build more complex queries, adding additional conditions, - limits, or include associations using the fluent interface. - -:: - - // In a controller or table method. - $query = $articles->find('all') - ->where(['Articles.created >' => new DateTime('-10 days')]) - ->contain(['Comments', 'Authors']) - ->limit(10); - -You can also provide many commonly used options to ``find()``:: - - // In a controller or table method. - $query = $articles->find('all', - conditions: ['Articles.created >' => new DateTime('-10 days')], - contain: ['Authors', 'Comments'], - limit: 10 - ); - -The list of named arguments supported by find() by default are: - -- ``conditions`` provide conditions for the WHERE clause of your query. -- ``limit`` Set the number of rows you want. -- ``offset`` Set the page offset you want. You can also use ``page`` to make - the calculation simpler. -- ``contain`` define the associations to eager load. -- ``fields`` limit the fields loaded into the entity. Only loading some fields - can cause entities to behave incorrectly. -- ``group`` add a GROUP BY clause to your query. This is useful when using - aggregating functions. -- ``having`` add a HAVING clause to your query. -- ``join`` define additional custom joins. -- ``order`` order the result set. - -Any options that are not in this list will be passed to beforeFind listeners -where they can be used to modify the query object. You can use the -``getOptions()`` method on a query object to retrieve the options used. While -you can pass query objects to your controllers, we recommend that you package -your queries up as :ref:`custom-find-methods` instead. Using custom finder -methods will let you re-use your queries and make testing easier. - -By default queries and result sets will return :doc:`/orm/entities` objects. You -can retrieve basic arrays by disabling hydration:: - - $query->disableHydration(); - - // $data is ResultSet that contains array data. - $data = $query->all(); - -.. _table-find-first: - -Getting the First Result -======================== - -The ``first()`` method allows you to fetch only the first row from a query. If -the query has not been executed, a ``LIMIT 1`` clause will be applied:: - - // In a controller or table method. - $query = $articles->find('all', order: ['Articles.created' => 'DESC']); - $row = $query->first(); - -This approach replaces ``find('first')`` in previous versions of CakePHP. You -may also want to use the ``get()`` method if you are loading entities by primary -key. - -.. note:: - - The ``first()`` method will return ``null`` if no results are found. - -Getting a Count of Results -========================== - -Once you have created a query object, you can use the ``count()`` method to get -a result count of that query:: - - // In a controller or table method. - $query = $articles->find('all', conditions: ['Articles.title LIKE' => '%Ovens%']); - $number = $query->count(); - -See :ref:`query-count` for additional usage of the ``count()`` method. - -.. _table-find-list: - -Finding Key/Value Pairs -======================= - -It is often useful to generate an associative array of data from your -application's data. For example, this is very useful when creating `` - -.. note:: - - Since this is an *edit* form, a hidden ``input`` field is generated to - override the default HTTP method. - -In some cases, the entity's ID is automatically appended to the end of the form's ``action`` URL. If you would like to *avoid* an ID being added to the URL, you can pass a string to ``$options['url']``, such as ``'/my-account'`` or ``\Cake\Routing\Router::url(['controller' => 'Users', 'action' => 'myAccount'])``. - -Options for Form Creation -------------------------- - -The ``$options`` array is where most of the form configuration -happens. This special array can contain a number of different -key-value pairs that affect the way the form tag is generated. -Valid values: - -* ``'type'`` - Allows you to choose the type of form to create. If no type is - provided then it will be autodetected based on the form context. - Valid values: - - * ``'get'`` - Will set the form method to HTTP GET. - * ``'file'`` - Will set the form method to POST and the ``'enctype'`` to - "multipart/form-data". - * ``'post'`` - Will set the method to POST. - * ``'put', 'delete', 'patch'`` - Will override the HTTP method with PUT, - DELETE or PATCH respectively, when the form is submitted. - -* ``'method'`` - Valid values are the same as above. Allows you to explicitly - override the form's method. - -* ``'url'`` - Specify the URL the form will submit to. Can be a string or a URL - array. - -* ``'encoding'`` - Sets the ``accept-charset`` encoding for the form. Defaults - to ``Configure::read('App.encoding')``. - -* ``'enctype'`` - Allows you to set the form encoding explicitly. - -* ``'templates'`` - The templates you want to use for this form. Any templates - provided will be merged on top of the already loaded templates. Can be either - a filename (without extension) from ``/config`` or an array of templates to use. - -* ``'context'`` - Additional options for the form context class. (For example - the ``EntityContext`` accepts a ``'table'`` option that allows you to set the - specific Table class the form should be based on.) - -* ``'idPrefix'`` - Prefix for generated ID attributes. - -* ``'templateVars'`` - Allows you to provide template variables for the - ``formStart`` template. - -* ``autoSetCustomValidity`` - Set to ``true`` to use custom required and notBlank - validation messages in the control's HTML5 validity message. Default is ``true``. - -.. tip:: - - Besides the above options you can provide, in the ``$options`` argument, - any valid HTML attributes that you want to pass to the created ``form`` - element. - -.. _form-values-from-query-string: - -Getting form values from other values sources ------------------------------------------------ - -A FormHelper's values sources define where its rendered elements, such as -input-tags, receive their values from. - -The supported sources are ``context``, ``data`` and ``query``. You can use one -or more sources by setting ``valueSources`` option or by using ``setValuesSource()``. -Any widgets generated by ``FormHelper`` will gather their values from the sources, -in the order you setup. - -By default ``FormHelper`` draws its values from ``data`` or ``context``, i.e. it will -fetch data from ``$request->getData()`` or, if not present, from the active context's -data, that are the entity's data in the case of ``EntityContext``. - -If however, you are building a form that needs to read from the query string, you can -change where ``FormHelper`` reads input data from:: - - // Use query string instead of request data: - echo $this->Form->create($article, [ - 'type' => 'get', - 'valueSources' => ['query', 'context'], - ]); - - // Same effect: - echo $this->Form - ->setValueSources(['query', 'context']) - ->create($articles, ['type' => 'get']); - -When input data has to be processed by the entity, i.e. marshal transformations, table -query result or entity computations, and displayed after one or multiple form submissions -where request data is retained, you need to put ``context`` first:: - - // Prioritize context over request data: - echo $this->Form->create($article, - 'valueSources' => ['context', 'data'], - ]); - -The value sources will be reset to the default ``['data', 'context']`` when ``end()`` -is called. - -Changing the HTTP Method for a Form ------------------------------------ - -By using the ``type`` option you can change the HTTP method a form will use:: - - echo $this->Form->create($article, ['type' => 'get']); - -Output: - -.. code-block:: html - - - -Specifying a ``'file'`` value for ``type``, changes the form submission method -to 'post', and includes an ``enctype`` of "multipart/form-data" on the form tag. -This is to be used if there are any file elements inside the form. The absence -of the proper ``enctype`` attribute will cause the file uploads not to function. - -For example:: - - echo $this->Form->create($article, ['type' => 'file']); - -Output: - -.. code-block:: html - - - -When using ``'put'``, ``'patch'`` or ``'delete'`` as ``'type'`` values, your -form will be functionally equivalent to a 'post' form, but when submitted, the -HTTP request method will be overridden with 'PUT', 'PATCH' or 'DELETE', -respectively. -This allows CakePHP to emulate proper REST support in web browsers. - -Setting a URL for the Form --------------------------- - -Using the ``'url'`` option allows you to point the form to a specific action in -your current controller or another controller in your application. - -For example, -if you'd like to point the form to the ``publish()`` action of the current -controller, you would supply an ``$options`` array, like the following:: - - echo $this->Form->create($article, ['url' => ['action' => 'publish']]); - -Output: - -.. code-block:: html - - - -If the desired form action isn't in the current controller, you can specify -a complete URL for the form action. The supplied URL can be relative to your -CakePHP application:: - - echo $this->Form->create(null, [ - 'url' => [ - 'controller' => 'Articles', - 'action' => 'publish', - ], - ]); - -Output: - -.. code-block:: html - - - -Or you can point to an external domain:: - - echo $this->Form->create(null, [ - 'url' => 'https://www.google.com/search', - 'type' => 'get', - ]); - -Output: - -.. code-block:: html - - - -Use ``'url' => false`` if you don't want to output a URL as the form action. - -Using Custom Validators ------------------------ - -Often models will have multiple validator sets, you can have FormHelper -mark fields required based on the specific validator your controller -action is going to apply. For example, your Users table has specific validation -rules that only apply when an account is being registered:: - - echo $this->Form->create($user, [ - 'context' => ['validator' => 'register'], - ]); - -The above will use validation rules defined in the ``register`` validator, which -are defined by ``UsersTable::validationRegister()``, for ``$user`` and all -related associations. If you are creating a form for associated entities, you -can define validation rules for each association by using an array:: - - echo $this->Form->create($user, [ - 'context' => [ - 'validator' => [ - 'Users' => 'register', - 'Comments' => 'default', - ], - ], - ]); - -The above would use ``register`` for the user, and ``default`` for the user's -comments. FormHelper uses validators to generate HTML5 required attributes, -relevant ARIA attributes, and set error messages with the `browser validator API -`_ -. If you would like to disable HTML5 validation messages use:: - - $this->Form->setConfig('autoSetCustomValidity', false); - -This will not disable ``required``/``aria-required`` attributes. - -Creating context classes ------------------------- - -While the built-in context classes are intended to cover the basic cases you'll -encounter you may need to build a new context class if you are using a different -ORM. In these situations you need to implement the -`Cake\\View\\Form\\ContextInterface -`_ . Once -you have implemented this interface you can wire your new context into the -FormHelper. It is often best to do this in a ``View.beforeRender`` event -listener, or in an application view class:: - - $this->Form->addContextProvider('myprovider', function ($request, $data) { - if ($data['entity'] instanceof MyOrmClass) { - return new MyProvider($data); - } - }); - -Context factory functions are where you can add logic for checking the form -options for the correct type of entity. If matching input data is found you can -return an object. If there is no match return null. - -.. _automagic-form-elements: - -Creating Form Controls -====================== - -.. php:method:: control(string $fieldName, array $options = []) - -* ``$fieldName`` - A field name in the form ``'Modelname.fieldname'``. -* ``$options`` - An optional array that can include both - :ref:`control-specific-options`, and options of the other methods (which - ``control()`` employs internally to generate various HTML elements) as - well as any valid HTML attributes. - -The ``control()`` method lets you generate complete form controls. These -controls will include a wrapping ``div``, ``label``, control widget, and validation error if -necessary. By using the metadata in the form context, this method will choose an -appropriate control type for each field. Internally ``control()`` uses the other -methods of FormHelper. - -.. tip:: - - Please note that while the fields generated by the ``control()`` method are - called generically "inputs" on this page, technically speaking, the - ``control()`` method can generate not only all of the HTML ``input`` type - elements, but also other HTML form elements such as ``select``, - ``button``, ``textarea``. - -By default the ``control()`` method will employ the following widget templates:: - - 'inputContainer' => '
      {{content}}
      ' - 'input' => '' - 'requiredClass' => 'required' - 'containerClass' => 'input' - -In case of validation errors it will also use:: - - 'inputContainerError' => '
      {{content}}{{error}}
      ' - -The type of control created (when we provide no additional options to specify the -generated element type) is inferred via model introspection and -depends on the column datatype: - -Column Type - Resulting Form Field -string, uuid (char, varchar, etc.) - text -boolean, tinyint(1) - checkbox -decimal - number -float - number -integer - number -text - textarea -text, with name of password, passwd - password -text, with name of email - email -text, with name of tel, telephone, or phone - tel -date - date -datetime, timestamp - datetime-local -datetimefractional, timestampfractional - datetime-local -time - time -month - month -year - select with years -binary - file - -The ``$options`` parameter allows you to choose a specific control type if -you need to:: - - echo $this->Form->control('published', ['type' => 'checkbox']); - -.. tip:: - - As a small subtlety, generating specific elements via the ``control()`` - form method will always also generate the wrapping ``div``, by default. - Generating the same type of element via one of the specific form methods - (e.g. ``$this->Form->checkbox('published');``) in most cases won't generate - the wrapping ``div``. Depending on your needs you can use one or another. - -.. _html5-required: - -The wrapping ``div`` will have a ``required`` class name appended if the -validation rules for the model's field indicate that it is required and not -allowed to be empty. You can disable automatic ``required`` flagging using the -``'required'`` option:: - - echo $this->Form->control('title', ['required' => false]); - -To skip browser validation triggering for the whole form you can set option -``'formnovalidate' => true`` for the input button you generate using -:php:meth:`~Cake\\View\\Helper\\FormHelper::submit()` or set ``'novalidate' => -true`` in options for :php:meth:`~Cake\\View\\Helper\\FormHelper::create()`. - -For example, let's assume that your Users model includes fields for a -*username* (varchar), *password* (varchar), *approved* (datetime) and -*quote* (text). You can use the ``control()`` method of the FormHelper to -create appropriate controls for all of these form fields:: - - echo $this->Form->create($user); - // The following generates a Text input - echo $this->Form->control('username'); - // The following generates a Password input - echo $this->Form->control('password'); - // Assuming 'approved' is a datetime or timestamp field the following - //generates an input of type "datetime-local" - echo $this->Form->control('approved'); - // The following generates a Textarea element - echo $this->Form->control('quote'); - - echo $this->Form->button('Add'); - echo $this->Form->end(); - -A more extensive example showing some options for a date field:: - - echo $this->Form->control('birth_date', [ - 'label' => 'Date of birth', - 'min' => date('Y') - 70, - 'max' => date('Y') - 18, - ]); - -Besides the specific :ref:`control-specific-options`, -you also can specify any option accepted by corresponding specific method -for the chosen (or inferred by CakePHP) -control type and any HTML attribute (for instance ``onfocus``). - -If you want to create a ``select`` form field while using a *belongsTo* (or -*hasOne*) relation, you can add the following to your UsersController -(assuming your User *belongsTo* Group):: - - $this->set('groups', $this->Users->Groups->find('list')->all()); - -Afterwards, add the following to your view template:: - - echo $this->Form->control('group_id', ['options' => $groups]); - -To make a ``select`` box for a *belongsToMany* Groups association you can -add the following to your UsersController:: - - $this->set('groups', $this->Users->Groups->find('list')->all()); - -Afterwards, add the following to your view template:: - - echo $this->Form->control('groups._ids', ['options' => $groups]); - -If your model name consists of two or more words (e.g. -"UserGroups"), when passing the data using ``set()`` you should name your -data in a pluralised and -`lower camelCased `_ -format as follows:: - - $this->set('userGroups', $this->UserGroups->find('list')->all()); - -.. note:: - - You should not use ``FormHelper::control()`` to generate submit buttons. Use - :php:meth:`~Cake\\View\\Helper\\FormHelper::submit()` instead. - -Field Naming Conventions ------------------------- - -When creating control widgets you should name your fields after the matching -attributes in the form's entity. For example, if you created a form for an -``$article`` entity, you would create fields named after the properties. E.g. -``title``, ``body`` and ``published``. - -You can create controls for associated models, or arbitrary models by passing in -``association.fieldname`` as the first parameter:: - - echo $this->Form->control('association.fieldname'); - -Any dots in your field names will be converted into nested request data. For -example, if you created a field with a name ``0.comments.body`` you would get -a name attribute that looks like ``0[comments][body]``. This convention matches -the conventions you use with the ORM. Details for the various association types -can be found in the :ref:`associated-form-inputs` section. - -When creating datetime related controls, FormHelper will append a field-suffix. -You may notice additional fields named ``year``, ``month``, ``day``, ``hour``, -``minute``, or ``meridian`` being added. These fields will be automatically -converted into ``DateTime`` objects when entities are marshalled. - -.. _control-specific-options: - -Options for Control -------------------- - -``FormHelper::control()`` supports a large number of options via its ``$options`` -argument. In addition to its own options, ``control()`` accepts options for the -inferred/chosen generated control types (e.g. for ``checkbox`` or ``textarea``), -as well as HTML attributes. This subsection will cover the options specific to -``FormHelper::control()``. - -* ``$options['type']`` - A string that specifies the widget type - to be generated. In addition to the field types found in the - :ref:`automagic-form-elements`, you can also create ``'file'``, - ``'password'``, and any other type supported by HTML5. By specifying a - ``'type'`` you will force the type of the generated control, overriding model - introspection. Defaults to ``null``. - - For example:: - - echo $this->Form->control('field', ['type' => 'file']); - echo $this->Form->control('email', ['type' => 'email']); - - Output: - - .. code-block:: html - -
      - - -
      - - -* ``$options['label']`` - Either a string caption or an array of - :ref:`options for the label`. You can set this key to the - string you would like to be displayed within the label that usually - accompanies the ``input`` HTML element. Defaults to ``null``. - - For example:: - - echo $this->Form->control('name', [ - 'label' => 'The User Alias' - ]); - - Output: - - .. code-block:: html - -
      - - -
      - - Alternatively, set this key to ``false`` to disable the generation of the - ``label`` element. - - For example:: - - echo $this->Form->control('name', ['label' => false]); - - Output: - - .. code-block:: html - -
      - -
      - - If the label is disabled, and a ``placeholder`` attribute is provided, the - generated input will have ``aria-label`` set. - - Set the ``label`` option to an array to provide additional options for the - ``label`` element. If you do this, you can use a ``'text'`` key in - the array to customize the label text. - - For example:: - - echo $this->Form->control('name', [ - 'label' => [ - 'class' => 'thingy', - 'text' => 'The User Alias' - ] - ]); - - Output: - - .. code-block:: html - -
      - - -
      - -* ``$options['options']`` - You can provide in here an array containing - the elements to be generated for widgets such as ``radio`` or ``select``, - which require an array of items as an argument (see - :ref:`create-radio-button` and :ref:`create-select-picker` for more details). - Defaults to ``null``. - -* ``$options['error']`` - Using this key allows you to override the default - model error messages and can be used, for example, to set i18n messages. To - disable the error message output & field classes set the ``'error'`` key to - ``false``. Defaults to ``null``. - - For example:: - - echo $this->Form->control('name', ['error' => false]); - - To override the model error messages use an array with - the keys matching the original validation error messages. - - For example:: - - $this->Form->control('name', [ - 'error' => ['Not long enough' => __('This is not long enough')] - ]); - - As seen above you can set the error message for each validation - rule you have in your models. In addition you can provide i18n - messages for your forms. - - To disable the HTML entity encoding for error messages only, the ``'escape'`` - sub key can be used:: - - $this->Form->control('name', [ - 'error' => ['escape' => false], - ]); - -* ``$options['nestedInput']`` - Used with checkboxes and radio buttons. - Controls whether the input element is generated - inside or outside the ``label`` element. When ``control()`` generates a - checkbox or a radio button, you can set this to ``false`` to force the - generation of the HTML ``input`` element outside of the ``label`` element. - - On the other hand you can set this to ``true`` for any control type to force the - generated input element inside the label. If you change this for radio buttons - then you need to also modify the default - :ref:`radioWrapper` template. Depending on the generated - control type it defaults to ``true`` or ``false``. - -* ``$options['templates']`` - The templates you want to use for this input. Any - specified templates will be merged on top of the already loaded templates. - This option can be either a filename (without extension) in ``/config`` that - contains the templates you want to load, or an array of templates to use. - -* ``$options['labelOptions']`` - Set this to ``false`` to disable labels around - nestedWidgets or set it to an array of attributes to be provided to the - ``label`` tag. - -* ``$options['readonly']`` - Set the field to ``readonly`` in form. - - For example:: - - echo $this->Form->control('name', ['readonly' => true]); - -Generating Specific Types of Controls -===================================== - -In addition to the generic ``control()`` method, ``FormHelper`` has specific -methods for generating a number of different types of controls. These can be used -to generate just the control widget itself, and combined with other methods like -:php:meth:`~Cake\\View\\Helper\\FormHelper::label()` and -:php:meth:`~Cake\\View\\Helper\\FormHelper::error()` to generate fully custom -form layouts. - -.. _general-control-options: - -Common Options For Specific Controls ------------------------------------- - -Many of the various control element methods support a common set of options which, -depending on the form method used, must be provided inside the ``$options`` or -in the ``$attributes`` array argument. All of these options are also supported -by the ``control()`` method. -To reduce repetition, the common options shared by all control methods are -as follows: - -* ``'id'`` - Set this key to force the value of the DOM id for the control. - This will override the ``'idPrefix'`` that may be set. - -* ``'default'`` - Used to set a default value for the control field. The - value is used if the data passed to the form does not contain a value for the - field (or if no data is passed at all). If no default value is provided, the - column's default value will be used. - - Example usage:: - - echo $this->Form->text('ingredient', ['default' => 'Sugar']); - - Example with ``select`` field (size "Medium" will be selected as - default):: - - $sizes = ['s' => 'Small', 'm' => 'Medium', 'l' => 'Large']; - echo $this->Form->select('size', $sizes, ['default' => 'm']); - - .. note:: - - You cannot use ``default`` to check a checkbox - instead you might - set the value in ``$this->request->getData()`` in your controller, - or set the control option ``'checked'`` to ``true``. - - Beware of using ``false`` to assign a default value. A ``false`` value is - used to disable/exclude options of a control field, so ``'default' => false`` - would not set any value at all. Instead use ``'default' => 0``. - -* ``'value'`` - Used to set a specific value for the control field. This - will override any value that may else be injected from the context, such as - Form, Entity or ``request->getData()`` etc. - - .. note:: - - If you want to set a field to not render its value fetched from - context or valuesSource you will need to set ``'value'`` to ``''`` - (instead of setting it to ``null``). - -In addition to the above options, you can mixin any HTML attribute you wish to -use. Any non-special option name will be treated as an HTML attribute, and -applied to the generated HTML control element. - -Creating Input Elements -======================= - -The rest of the methods available in the FormHelper are for -creating specific form elements. Many of these methods also make -use of a special ``$options`` or ``$attributes`` parameter. In this case, -however, this parameter is used primarily to specify HTML tag attributes -(such as the value or DOM id of an element in the form). - -Creating Text Inputs --------------------- - -.. php:method:: text(string $name, array $options) - -* ``$name`` - A field name in the form ``'Modelname.fieldname'``. -* ``$options`` - An optional array including any of the - :ref:`general-control-options` as well as any valid HTML attributes. - -Creates a simple ``input`` HTML element of ``text`` type. - -For example:: - - echo $this->Form->text('username', ['class' => 'users']); - -Will output: - -.. code-block:: html - - - -Creating Password Inputs ------------------------- - -.. php:method:: password(string $fieldName, array $options) - -* ``$fieldName`` - A field name in the form ``'Modelname.fieldname'``. -* ``$options`` - An optional array including any of the - :ref:`general-control-options` as well as any valid HTML attributes. - -Creates a simple ``input`` element of ``password`` type. - -For example:: - - echo $this->Form->password('password'); - -Will output: - -.. code-block:: html - - - -Creating Hidden Inputs ----------------------- - -.. php:method:: hidden(string $fieldName, array $options) - -* ``$fieldName`` - A field name in the form ``'Modelname.fieldname'``. -* ``$options`` - An optional array including any of the - :ref:`general-control-options` as well as any valid HTML attributes. - -Creates a hidden form input. - -For example:: - - echo $this->Form->hidden('id'); - -Will output: - -.. code-block:: html - - - -Creating Textareas ------------------- - -.. php:method:: textarea(string $fieldName, array $options) - -* ``$fieldName`` - A field name in the form ``'Modelname.fieldname'``. -* ``$options`` - An optional array including any of the - :ref:`general-control-options`, of the specific textarea options (see below) - as well as any valid HTML attributes. - -Creates a textarea control field. The default widget template used is:: - - 'textarea' => '' - -For example:: - - echo $this->Form->textarea('notes'); - -Will output: - -.. code-block:: html - - - -If the form is being edited (i.e. the array ``$this->request->getData()`` -contains the information previously saved for the ``User`` entity), the value -corresponding to ``notes`` field will automatically be added to the HTML -generated. - -Example: - -.. code-block:: html - - - -**Options for Textarea** - -In addition to the :ref:`general-control-options`, ``textarea()`` supports a -couple of specific options: - -* ``'escape'`` - Determines whether or not the contents of the textarea should - be escaped. Defaults to ``true``. - - For example:: - - echo $this->Form->textarea('notes', ['escape' => false]); - // OR.... - echo $this->Form->control('notes', ['type' => 'textarea', 'escape' => false]); - -* ``'rows', 'cols'`` - You can use these two keys to set the HTML attributes - which specify the number of rows and columns for the ``textarea`` field. - - For example:: - - echo $this->Form->textarea('comment', ['rows' => '5', 'cols' => '5']); - - Output: - - .. code-block:: html - - - -Creating Select, Checkbox and Radio Controls --------------------------------------------- - -These controls share some commonalities and a few options and thus, they are -all grouped in this subsection for easier reference. - -.. _checkbox-radio-select-options: - -Options for Select, Checkbox and Radio Controls -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can find below the options which are shared by ``select()``, -``checkbox()`` and ``radio()`` (the options particular only to one of the -methods are described in each method's own section.) - -* ``'value'`` - Sets or selects the value of the affected element(s): - - * For checkboxes, it sets the HTML ``'value'`` attribute assigned - to the ``input`` element to whatever you provide as value. - - * For radio buttons or select pickers it defines which element will be - selected when the form is rendered (in this case ``'value'`` must be - assigned a valid, existent element value). May also be used in - combination with any select-type control, - such as ``date()``, ``time()``, ``dateTime()``:: - - echo $this->Form->time('close_time', [ - 'value' => '13:30:00', - ]); - - .. note:: - - The ``'value'`` key for ``date()`` and ``dateTime()`` controls may also have - as value a UNIX timestamp, or a DateTime object. - - For a ``select`` control where you set the ``'multiple'`` attribute to - ``true``, you can provide an array with the values you want to select - by default:: - - // HTML ' - -May also use:: - - 'optgroup' => '{{content}}' - 'selectMultiple' => '' - -**Attributes for Select Pickers** - -* ``'multiple'`` - If set to ``true`` allows multiple selections in the select - picker. If set to ``'checkbox'``, multiple checkboxes will be created instead. - Defaults to ``null``. - -* ``'escape'`` - Boolean. If ``true`` the contents of the ``option`` elements - inside the select picker will be HTML entity encoded. Defaults to ``true``. - -* ``'val'`` - Allows preselecting a value in the select picker. - -* ``'disabled'`` - Controls the ``disabled`` attribute. If set to ``true`` - disables the whole select picker. If set to an array it will disable - only those specific ``option`` elements whose values are provided in - the array. - -The ``$options`` argument allows you to manually specify -the contents of the ``option`` elements of a ``select`` control. - -For example:: - - echo $this->Form->select('field', [1, 2, 3, 4, 5]); - -Output: - -.. code-block:: html - - - -The array for ``$options`` can also be supplied as key-value pairs. - -For example:: - - echo $this->Form->select('field', [ - 'Value 1' => 'Label 1', - 'Value 2' => 'Label 2', - 'Value 3' => 'Label 3' - ]); - -Output: - -.. code-block:: html - - - -If you would like to generate a ``select`` with optgroups, just pass -data in hierarchical format (nested array). This works on multiple -checkboxes and radio buttons too, but instead of ``optgroup`` it wraps -the elements in ``fieldset`` elements. - -For example:: - - $options = [ - 'Group 1' => [ - 'Value 1' => 'Label 1', - 'Value 2' => 'Label 2', - ], - 'Group 2' => [ - 'Value 3' => 'Label 3', - ], - ]; - echo $this->Form->select('field', $options); - -Output: - -.. code-block:: html - - - -To generate HTML attributes within an ``option`` tag:: - - $options = [ - ['text' => 'Description 1', 'value' => 'value 1', 'attr_name' => 'attr_value 1'], - ['text' => 'Description 2', 'value' => 'value 2', 'attr_name' => 'attr_value 2'], - ['text' => 'Description 3', 'value' => 'value 3', 'other_attr_name' => 'other_attr_value'], - ]; - echo $this->Form->select('field', $options); - -Output: - -.. code-block:: html - - - -**Controlling Select Pickers via Attributes** - -By using specific options in the ``$attributes`` parameter you can control -certain behaviors of the ``select()`` method. - -* ``'empty'`` - Set the ``'empty'`` key in the ``$attributes`` argument - to ``true`` (the default value is ``false``) to add a blank option with an - empty value at the top of your dropdown list. - - For example:: - - $options = ['M' => 'Male', 'F' => 'Female']; - echo $this->Form->select('gender', $options, ['empty' => true]); - - Will output: - - .. code-block:: html - - - -* ``'escape'`` - The ``select()`` method allows for an attribute - called ``'escape'`` which accepts a boolean value and determines - whether to HTML entity encode the contents of the ``select``'s ``option`` - elements. - - For example:: - - // This will prevent HTML-encoding the contents of each option element - $options = ['M' => 'Male', 'F' => 'Female']; - echo $this->Form->select('gender', $options, ['escape' => false]); - -* ``'multiple'`` - If set to ``true``, the select picker will allow - multiple selections. - - For example:: - - echo $this->Form->select('field', $options, ['multiple' => true]); - - Alternatively, set ``'multiple'`` to ``'checkbox'`` in order to output a - list of related checkboxes:: - - $options = [ - 'Value 1' => 'Label 1', - 'Value 2' => 'Label 2' - ]; - echo $this->Form->select('field', $options, [ - 'multiple' => 'checkbox' - ]); - - Output: - - .. code-block:: html - - -
      - -
      -
      - -
      - -* ``'disabled'`` - This option can be set in order to disable all or some - of the ``select``'s ``option`` items. To disable all items set ``'disabled'`` - to ``true``. To disable only certain items, assign to ``'disabled'`` - an array containing the keys of the items to be disabled. - - For example:: - - $options = [ - 'M' => 'Masculine', - 'F' => 'Feminine', - 'N' => 'Neuter' - ]; - echo $this->Form->select('gender', $options, [ - 'disabled' => ['M', 'N'] - ]); - - Will output: - - .. code-block:: html - - - - This option also works when ``'multiple'`` is set to ``'checkbox'``:: - - $options = [ - 'Value 1' => 'Label 1', - 'Value 2' => 'Label 2' - ]; - echo $this->Form->select('field', $options, [ - 'multiple' => 'checkbox', - 'disabled' => ['Value 1'] - ]); - - Output: - - .. code-block:: html - - -
      - -
      -
      - -
      - -Creating File Inputs --------------------- - -.. php:method:: file(string $fieldName, array $options) - -* ``$fieldName`` - A field name in the form ``'Modelname.fieldname'``. -* ``$options`` - An optional array including any of the - :ref:`general-control-options` as well as any valid HTML attributes. - -Creates a file upload field in the form. -The widget template used by default is:: - - 'file' => '' - -To add a file upload field to a form, you must first make sure that -the form enctype is set to ``'multipart/form-data'``. - -So start off with a ``create()`` method such as the following:: - - echo $this->Form->create($document, ['enctype' => 'multipart/form-data']); - // OR - echo $this->Form->create($document, ['type' => 'file']); - -Next add a line that looks like either of the following two lines -to your form's view template file:: - - echo $this->Form->control('submittedfile', [ - 'type' => 'file' - ]); - - // OR - echo $this->Form->file('submittedfile'); - -.. note:: - - Due to the limitations of HTML itself, it is not possible to put - default values into input fields of type 'file'. Each time the form - is displayed, the value inside will be empty. - -To prevent the ``submittedfile`` from being over-written as blank, remove it -from ``$_accessible``. Alternatively, you can unset the index by using -``beforeMarshal``:: - - public function beforeMarshal(\Cake\Event\EventInterface $event, \ArrayObject $data, \ArrayObject $options) - { - if ($data['submittedfile'] === '') { - unset($data['submittedfile']); - } - } - - -Upon submission, file fields can be accessed though ``UploadedFileInterface`` -objects on the request. To move uploaded files to a permanent location, you can -use:: - - $fileobject = $this->request->getData('submittedfile'); - $destination = UPLOAD_DIRECTORY . $fileobject->getClientFilename(); - - // Existing files with the same name will be replaced. - $fileobject->moveTo($destination); - -.. note:: - - When using ``$this->Form->file()``, remember to set the form - encoding-type, by setting the ``'type'`` option to ``'file'`` in - ``$this->Form->create()``. - -.. _create-datetime-controls: - -Creating Date & Time Related Controls -------------------------------------- - -.. php:method:: dateTime(string $fieldName, array $options = []) - -* ``$fieldName`` - A string that will be used as a prefix for the HTML ``name`` - attribute of the ``select`` elements. -* ``$options`` - An optional array including any of the - :ref:`general-control-options` as well as any valid HTML attributes. - -This method will generate an input tag with type "datetime-local". - -For example :: - - form->dateTime('registered') ?> - -Output: - -.. code-block:: html - - - -The value for the input can be any valid datetime string or ``DateTime`` instance. - -For example :: - - form->dateTime('registered', ['value' => new DateTime()]) ?> - -Output: - -.. code-block:: html - - - -Creating Date Controls -~~~~~~~~~~~~~~~~~~~~~~ - -.. php:method:: date(string $fieldName, array $options = []) - -* ``$fieldName`` - A field name that will be used as a prefix for the HTML - ``name`` attribute of the ``select`` elements. -* ``$options`` - An optional array including any of the - :ref:`general-control-options` as well as any valid HTML attributes. - -This method will generate an input tag with type "date". - -For example :: - - form->date('registered') ?> - -Output: - -.. code-block:: html - - - -Creating Time Controls -~~~~~~~~~~~~~~~~~~~~~~ - -.. php:method:: time(string $fieldName, array $options = []) - -* ``$fieldName`` - A field name that will be used as a prefix for the HTML - ``name`` attribute of the ``select`` elements. -* ``$options`` - An optional array including any of the - :ref:`general-control-options` as well as any valid HTML attributes. - -This method will generate an input tag with type "time". - -For example :: - - echo $this->Form->time('released'); - -Output: - -.. code-block:: html - - - -Creating Month Controls -~~~~~~~~~~~~~~~~~~~~~~~ - -.. php:method:: month(string $fieldName, array $attributes) - -* ``$fieldName`` - A field name that will be used as a prefix for the HTML - ``name`` attribute of the ``select`` element. -* ``$options`` - An optional array including any of the - :ref:`general-control-options` as well as any valid HTML attributes. - -This method will generate an input tag with type "month". - -For example:: - - echo $this->Form->month('mob'); - -Will output: - -.. code-block:: html - - - -Creating Year Controls -~~~~~~~~~~~~~~~~~~~~~~ - -.. php:method:: year(string $fieldName, array $options = []) - -* ``$fieldName`` - A field name that will be used as a prefix for the HTML - ``name`` attribute of the ``select`` element. -* ``$options`` - An optional array including any of the - :ref:`general-control-options` as well as any valid HTML attributes. - Other valid options are: - - * ``min``: The lowest value to use in the year select picker. - * ``max``: The maximum value to use in the year select picker. - * ``order``: The order of year values in the year select picker. - Possible values are ``'asc'`` and ``'desc'``. Defaults to ``'desc'``. - -Creates a ``select`` element populated with the years from ``min`` to ``max`` -(when these options are provided) or else with values starting from -5 years -to +5 years counted from today. Additionally, HTML attributes may be supplied -in ``$options``. If ``$options['empty']`` is ``false``, the select picker will -not include an empty item in the list. - -For example, to create a year range from 2000 to the current year you -would do the following:: - - echo $this->Form->year('purchased', [ - 'min' => 2000, - 'max' => date('Y') - ]); - -If it was 2009, you would get the following: - -.. code-block:: html - - - -.. _create-label: - -Creating Labels -=============== - -.. php:method:: label(string $fieldName, string $text, array $options) - -* ``$fieldName`` - A field name in the form ``'Modelname.fieldname'``. -* ``$text`` - An optional string providing the label caption text. -* ``$options`` - Optional. Array containing any of the - :ref:`general-control-options` as well as any valid HTML attributes. - -Creates a ``label`` element. The argument ``$fieldName`` is used for generating -the HTML ``for`` attribute of the element; if ``$text`` is undefined, -``$fieldName`` will also be used to inflect the label's ``text`` attribute. - -For example:: - - echo $this->Form->label('name'); - echo $this->Form->label('name', 'Your username'); - -Output: - -.. code-block:: html - - - - -With the third parameter ``$options`` you can set the id or class:: - - echo $this->Form->label('name', null, ['id' => 'user-label']); - echo $this->Form->label('name', 'Your username', ['class' => 'highlight']); - -Output: - -.. code-block:: html - - - - -Displaying and Checking Errors -============================== - -FormHelper exposes a couple of methods that allow us to easily check for -field errors and when necessary display customized error messages. - -Displaying Errors ------------------ - -.. php:method:: error(string $fieldName, mixed $text, array $options) - -* ``$fieldName`` - A field name in the form ``'Modelname.fieldname'``. -* ``$text`` - Optional. A string or array providing the error message(s). If an - array, then it should be a hash of key names => messages. Defaults to - ``null``. -* ``$options`` - An optional array that can only contain a boolean with the key - ``'escape'``, which will define whether to HTML escape the - contents of the error message. Defaults to ``true``. - -Shows a validation error message, specified by ``$text``, for the given -field, in the event that a validation error has occurred. If ``$text`` is not -provided then the default validation error message for that field will be used. - -Uses the following template widgets:: - - 'error' => '
      {{content}}
      ' - 'errorList' => '
        {{content}}
      ' - 'errorItem' => '
    • {{text}}
    • ' - -The ``'errorList'`` and ``'errorItem'`` templates are used to format mutiple -error messages per field. - -Example:: - - // If in TicketsTable you have a 'notEmpty' validation rule: - public function validationDefault(Validator $validator): Validator - { - $validator - ->requirePresence('ticket', 'create') - ->notEmpty('ticket'); - } - - // And inside templates/Tickets/add.php you have: - echo $this->Form->text('ticket'); - - if ($this->Form->isFieldError('ticket')) { - echo $this->Form->error('ticket', 'Completely custom error message!'); - } - -If you would click the *Submit* button of your form without providing a value -for the *Ticket* field, your form would output: - -.. code-block:: html - - -
      Completely custom error message!
      - -.. note:: - - When using :php:meth:`~Cake\\View\\Helper\\FormHelper::control()`, errors are - rendered by default, so you don't need to use ``isFieldError()`` or call - ``error()`` manually. - -.. tip:: - - If you use a certain model field to generate multiple form fields via - ``control()``, and you want the same validation error message displayed for - each one, you will probably be better off defining a custom error message - inside the respective :ref:`validator rules`. - -Checking for Errors -------------------- - -.. php:method:: isFieldError(string $fieldName) - -* ``$fieldName`` - A field name in the form ``'Modelname.fieldname'``. - -Returns ``true`` if the supplied ``$fieldName`` has an active validation -error, otherwise returns ``false``. - -Example:: - - if ($this->Form->isFieldError('gender')) { - echo $this->Form->error('gender'); - } - - -.. _html5-validity-messages: - -Displaying validation messages in HTML5 validity messages ---------------------------------------------------------- - -If the ``autoSetCustomValidity`` FormHelper option is set to ``true``, error messages for -the field's required and notBlank validation rules will be used in lieu of the default -browser HTML5 required messages. Enabling the option will add the ``onvalid`` and ``oninvalid`` -event attributes to your fields, for example:: - - - -If you want to manually set those events with custom JavaScript, you can set the ``autoSetCustomValidity`` -option to ``false`` and use the special ``customValidityMessage`` template variable instead. This -template variable is added when a field is required:: - - // example template - [ - 'input' => '', - ] - - // would create an input like this - - -You could then use JavaScript to set the ``onvalid`` and ``oninvalid`` events as you like. - -Creating Buttons and Submit Elements -==================================== - -Creating Submit Elements ------------------------- - -.. php:method:: submit(string $caption, array $options) - -* ``$caption`` - An optional string providing the button's text caption or a - path to an image. Defaults to ``'Submit'``. -* ``$options`` - An optional array including any of the - :ref:`general-control-options`, or of the specific submit options (see below) - as well as any valid HTML attributes. - -Creates an ``input`` element of ``submit`` type, with ``$caption`` as value. -If the supplied ``$caption`` is a URL pointing to an image (i.e. if the string -contains '://' or contains any of the extensions '.jpg, .jpe, .jpeg, .gif'), -an image submit button will be generated, using the specified image if it -exists. If the first character is '/' then the image path is relative to -*webroot*, else if the first character is not '/' then the image path is -relative to *webroot/img*. - -By default it will use the following widget templates:: - - 'inputSubmit' => '' - 'submitContainer' => '
      {{content}}
      ' - -**Options for Submit** - -* ``'type'`` - Set this option to ``'reset'`` in order to generate reset buttons. - It defaults to ``'submit'``. - -* ``'templateVars'`` - Set this array to provide additional template variables - for the input element and its container. - -* Any other provided attributes will be assigned to the ``input`` element. - -The following:: - - echo $this->Form->submit('Click me'); - -Will output: - -.. code-block:: html - -
      - -You can pass a relative or absolute URL of an image to the -caption parameter instead of the caption text:: - - echo $this->Form->submit('ok.png'); - -Will output: - -.. code-block:: html - -
      - -Submit inputs are useful when you only need basic text or images. If you need -more complex button content you should use ``button()``. - -Creating Button Elements ------------------------- - -.. php:method:: button(string $title, array $options = []) - -* ``$title`` - Mandatory string providing the button's text caption. -* ``$options`` - An optional array including any of the - :ref:`general-control-options`, or of the specific button options (see below) - as well as any valid HTML attributes. - -Creates an HTML button with the specified title and a default type -of ``'button'``. - -**Options for Button** - -* ``'type'`` - You can set this to one of the following three - possible values: - - #. ``'submit'`` - Similarly to the ``$this->Form->submit()`` method it will - create a submit button. However this won't generate a wrapping ``div`` - as ``submit()`` does. This is the default type. - #. ``'reset'`` - Creates a form reset button. - #. ``'button'`` - Creates a standard push button. - -* ``'escapeTitle'`` - Boolean. If set to ``true`` it will HTML encode - the value provided inside ``$title``. Defaults to ``true``. - -* ``'escape'`` - Boolean. If set to ``true`` it will HTML encode - all the HTML attributes generated for the button. Defaults to ``true``. - -* ``'confirm'`` - The confirmation message to display on click. Defaults to - ``null``. - -For example:: - - echo $this->Form->button('A Button'); - echo $this->Form->button('Another Button', ['type' => 'button']); - echo $this->Form->button('Reset the Form', ['type' => 'reset']); - echo $this->Form->button('Submit Form', ['type' => 'submit']); - -Will output: - -.. code-block:: html - - - - - - -Example use of the ``'escapeTitle'`` option:: - - // Will render unescaped HTML. - echo $this->Form->button('Submit Form', [ - 'type' => 'submit', - 'escapeTitle' => false, - ]); - -Closing the Form -================ - -.. php:method:: end($secureAttributes = []) - -* ``$secureAttributes`` - Optional. Allows you to provide secure attributes - which will be passed as HTML attributes into the hidden input elements - generated for the FormProtectionComponent. - -The ``end()`` method closes and completes a form. Often, ``end()`` will only -output a closing form tag, but using ``end()`` is a good practice as it -enables FormHelper to insert the hidden form elements that -:php:class:`Cake\\Controller\\Component\\FormProtectionComponent` requires: - -.. code-block:: php - - Form->create(); ?> - - - - Form->end(); ?> - -If you need to add additional attributes to the generated hidden inputs -you can use the ``$secureAttributes`` argument. - -For example:: - - echo $this->Form->end(['data-type' => 'hidden']); - -Will output: - -.. code-block:: html - -
      - - -
      - -.. note:: - - If you are using - :php:class:`Cake\\Controller\\Component\\FormProtectionComponent` in your - application you should always end your forms with ``end()``. - -Creating Standalone Buttons and POST Links -========================================== - -Creating POST Buttons ---------------------- - -.. php:method:: postButton(string $title, mixed $url, array $options = []) - -* ``$title`` - Mandatory string providing the button's text caption. By default - not HTML encoded. -* ``$url`` - The URL of the form provided as a string or as array. -* ``$options`` - An optional array including any of the - :ref:`general-control-options`, or of the specific options (see below) as well - as any valid HTML attributes. - -Creates a `` -
      - - - -
      - - -Since this method generates a ``form`` element, do not use this method in an -already opened form. Instead use -:php:meth:`Cake\\View\\Helper\\FormHelper::submit()` -or :php:meth:`Cake\\View\\Helper\\FormHelper::button()` to create buttons -inside opened forms. - -Creating POST Links -------------------- - -.. php:method:: postLink(string $title, array|string|null $url = null, array $options = []) - -* ``$title`` - Mandatory string providing the text to be wrapped in ```` - tags. -* ``$url`` - Optional. String or array which contains the URL - of the form (Cake-relative or external URL starting with ``http://``). -* ``$options`` - An optional array including any of the - :ref:`general-control-options`, or of the specific options (see below) as well - as any valid HTML attributes. - -Creates an HTML link, but accesses the URL using the method you specify -(defaults to POST). Requires JavaScript to be enabled in browser:: - - // In your template, to delete an article, for example - Form->postLink( - 'Delete', - ['action' => 'delete', $article->id], - ['confirm' => 'Are you sure?']) - ?> - -**Options for POST Link** - -* ``'data'`` - Array with key/value to pass in hidden input. - -* ``'method'`` - Request method to use. For example, setting it to ``'delete'`` - will simulate a HTTP/1.1 DELETE request. Defaults to ``'post'``. - -* ``'confirm'`` - The confirmation message to display on click. Defaults to - ``null``. - -* ``'block'`` - Set this option to ``true`` to append the form to view block - ``'postLink'`` or provide a custom block name. Defaults to ``null``. - -* Also, the ``postLink`` method will accept the options which are valid for - the ``link()`` method. - -This method creates a ``
      `` element. If you want to use this method -inside of an existing form, you must use the ``block`` option so that the -new form is being set to a :ref:`view block ` that can be -rendered outside of the main form. - -If all you are looking for is a button to submit your form, then you should -use :php:meth:`Cake\\View\\Helper\\FormHelper::button()` or -:php:meth:`Cake\\View\\Helper\\FormHelper::submit()` instead. - -.. note:: - - Be careful to not put a postLink inside an open form. Instead use the - ``block`` option to buffer the form into a :ref:`view block ` - -.. _customizing-templates: - -Creating DELETE Links ---------------------- - -.. php:method:: deleteLink(string $title, array|string|null $url = null, array $options = []) - -* ``$title`` - Mandatory string providing the text to be wrapped in ```` - tags. -* ``$url`` - Optional. String or array which contains the URL - of the form (Cake-relative or external URL starting with ``http://``). -* ``$options`` - An optional array including any of the - :ref:`general-control-options`, or of the specific options (see below) as well - as any valid HTML attributes. - -Creates an HTML link, but accesses the URL using the method you specify -(defaults to DELETE). Requires JavaScript to be enabled in browser:: - - // In your template, to delete an article, for example - Form->deleteLink( - 'Delete', - ['action' => 'delete', $article->id], - ['confirm' => 'Are you sure?']) - ?> - -.. versionadded:: 5.2.0 - The ``deleteLink`` method was added. - -Customizing the Templates FormHelper Uses -========================================= - -Like many helpers in CakePHP, FormHelper uses string templates to format the -HTML it creates. While the default templates are intended to be a reasonable set -of defaults, you may need to customize the templates to suit your application. - -To change the templates when the helper is loaded you can set the ``'templates'`` -option when including the helper in your controller:: - - // In a View class - $this->loadHelper('Form', [ - 'templates' => 'app_form', - ]); - -This would load the tags found in **config/app_form.php**. This file should -contain an array of templates *indexed by name*:: - - // in config/app_form.php - return [ - 'inputContainer' => '
      {{content}}
      ', - ]; - -Any templates you define will replace the default ones included in the helper. -Templates that are not replaced, will continue to use the default values. - -You can also change the templates at runtime using the ``setTemplates()`` method:: - - $myTemplates = [ - 'inputContainer' => '
      {{content}}
      ', - ]; - $this->Form->setTemplates($myTemplates); - -.. warning:: - - Template strings containing a percentage sign (``%``) need special attention; - you should prefix this character with another percentage so it looks like - ``%%``. The reason is that internally templates are compiled to be used with - ``sprintf()``. Example: ``'
      {{content}}
      '`` - -List of Templates ------------------ - -The list of default templates, their default format and the variables they -expect can be found in the -`FormHelper API documentation `_. - -Using Distinct Custom Control Containers -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In addition to these templates, the ``control()`` method will attempt to use -distinct templates for each control container. For example, when creating -a datetime control the ``datetimeContainer`` will be used if it is present. -If that container is missing the ``inputContainer`` template will be used. - -For example:: - - // Add custom radio wrapping HTML - $this->Form->setTemplates([ - 'radioContainer' => '
      {{content}}
      ' - ]); - - // Create a radio set with our custom wrapping div. - echo $this->Form->control('email_notifications', [ - 'options' => ['y', 'n'], - 'type' => 'radio' - ]); - -Using Distinct Custom Form Groups -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Similar to controlling containers, the ``control()`` method will also attempt to use -distinct templates for each form group. A form group is a combo of label and -control. For example, when creating a radio control the ``radioFormGroup`` will be -used if it is present. If that template is missing by default each set of ``label`` -& ``input`` is rendered using the default ``formGroup`` template. - -For example:: - - // Add custom radio form group - $this->Form->setTemplates([ - 'radioFormGroup' => '
      {{label}}{{input}}
      ' - ]); - -Adding Additional Template Variables to Templates -------------------------------------------------- - -You can add additional template placeholders in custom templates, and populate -those placeholders when generating controls. - -For example:: - - // Add a template with the help placeholder. - $this->Form->setTemplates([ - 'inputContainer' => '
      - {{content}} {{help}}
      ' - ]); - - // Generate an input and populate the help variable - echo $this->Form->control('password', [ - 'templateVars' => ['help' => 'At least 8 characters long.'], - ]); - -Output: - -.. code-block:: html - -
      - - - At least 8 characters long. -
      - -Moving Checkboxes & Radios Outside of a Label ---------------------------------------------- - -By default CakePHP nests checkboxes created via ``control()`` and radio buttons -created by both ``control()`` and ``radio()`` within label elements. -This helps make it easier to integrate popular CSS frameworks. If you need to -place checkbox/radio inputs outside of the label you can do so by modifying the -templates:: - - $this->Form->setTemplates([ - 'nestingLabel' => '{{hidden}}{{input}}{{text}}', - 'formGroup' => '{{input}}{{label}}', - ]); - -This will make radio buttons and checkboxes render outside of their labels. - -Generating Entire Forms -======================= - -Creating Multiple Controls --------------------------- - -.. php:method:: controls(array $fields = [], array $options = []) - -* ``$fields`` - An array of fields to generate. Allows setting - custom types, labels and other options for each specified field. -* ``$options`` - Optional. An array of options. Valid keys are: - - #. ``'fieldset'`` - Set this to ``false`` to disable the fieldset. - If empty, the fieldset will be enabled. Can also be an array of parameters - to be applied as HTML attributes to the ``fieldset`` tag. - #. ``legend`` - String used to customize the ``legend`` text. Set this to - ``false`` to disable the legend for the generated input set. - -Generates a set of controls for the given context wrapped in a -``fieldset``. You can specify the generated fields by including them:: - - echo $this->Form->controls([ - 'name', - 'email' - ]); - -You can customize the legend text using an option:: - - echo $this->Form->controls($fields, ['legend' => 'Update news post']); - -You can customize the generated controls by defining additional options in the -``$fields`` parameter:: - - echo $this->Form->controls([ - 'name' => ['label' => 'custom label'], - ]); - -When customizing, ``$fields``, you can use the ``$options`` parameter to -control the generated legend/fieldset. - -For example:: - - echo $this->Form->controls( - [ - 'name' => ['label' => 'custom label'], - ], - [ - 'legend' => 'Update your post', - ] - ); - -If you disable the ``fieldset``, the ``legend`` will not print. - -Creating Controls for a Whole Entity ------------------------------------- - -.. php:method:: allControls(array $fields, array $options = []) - -* ``$fields`` - Optional. An array of customizations for the fields that will - be generated. Allows setting custom types, labels and other options. -* ``$options`` - Optional. An array of options. Valid keys are: - - #. ``'fieldset'`` - Set this to ``false`` to disable the fieldset. - If empty, the fieldset will be enabled. Can also be an array of - parameters to be applied as HTMl attributes to the ``fieldset`` tag. - #. ``legend`` - String used to customize the ``legend`` text. Set this to - ``false`` to disable the legend for the generated control set. - -This method is closely related to ``controls()``, however the ``$fields`` argument -is defaulted to *all* fields in the current top-level entity. To exclude -specific fields from the generated controls, set them to ``false`` in the -``$fields`` parameter:: - - echo $this->Form->allControls(['password' => false]); - -.. _associated-form-inputs: - -Creating Inputs for Associated Data -=================================== - -Creating forms for associated data is straightforward and is closely related to -the paths in your entity's data. Assuming the following table relations: - -* Authors HasOne Profiles -* Authors HasMany Articles -* Articles HasMany Comments -* Articles BelongsTo Authors -* Articles BelongsToMany Tags - -If we were editing an article with its associations loaded we could -create the following controls:: - - $this->Form->create($article); - - // Article controls. - echo $this->Form->control('title'); - - // Author controls (belongsTo) - echo $this->Form->control('author.id'); - echo $this->Form->control('author.first_name'); - echo $this->Form->control('author.last_name'); - - // Author profile (belongsTo + hasOne) - echo $this->Form->control('author.profile.id'); - echo $this->Form->control('author.profile.username'); - - // Tags controls (belongsToMany) - // as separate inputs - echo $this->Form->control('tags.0.id'); - echo $this->Form->control('tags.0.name'); - echo $this->Form->control('tags.1.id'); - echo $this->Form->control('tags.1.name'); - - // Inputs for the joint table (articles_tags) - echo $this->Form->control('tags.0._joinData.starred'); - echo $this->Form->control('tags.1._joinData.starred'); - - // Comments controls (hasMany) - echo $this->Form->control('comments.0.id'); - echo $this->Form->control('comments.0.comment'); - echo $this->Form->control('comments.1.id'); - echo $this->Form->control('comments.1.comment'); - -The above controls could then be marshalled into a completed entity graph using -the following code in your controller:: - - $article = $this->Articles->patchEntity($article, $this->request->getData(), [ - 'associated' => [ - 'Authors', - 'Authors.Profiles', - 'Tags', - 'Comments', - ], - ]); - -The above example shows an expanded example for belongs to many associations, -with separate inputs for each entity and join data record. You can also create -a multiple select input for belongs to many associations:: - - // Multiple select element for belongsToMany - // Does not support _joinData - echo $this->Form->control('tags._ids', [ - 'type' => 'select', - 'multiple' => true, - 'options' => $tags, // $tags is the output of $this->Articles->Tags->find('list')->all() in the controller - ]); - - -Adding Custom Widgets -===================== - -You can add custom control widgets in CakePHP, and use them like any other -control type. All of the core control types are implemented as widgets, which -means you can override any core widget with your own implementation as well. - -Building a Widget Class ------------------------ - -Widget classes have a very simple required interface. They must implement the -:php:class:`Cake\\View\\Widget\\WidgetInterface`. This interface requires -the ``render(array $data)`` and ``secureFields(array $data)`` methods to be -implemented. The ``render()`` method expects an array of data to build the -widget and is expected to return a string of HTML for the widget. -The ``secureFields()`` method expects an array of data as well and is expected -to return an array containing the list of fields to secure for this widget. -If CakePHP is constructing your widget you can expect to -get a ``Cake\View\StringTemplate`` instance as the first argument, followed by -any dependencies you define. If we wanted to build an Autocomplete widget you -could do the following:: - - namespace App\View\Widget; - - use Cake\View\Form\ContextInterface; - use Cake\View\StringTemplate; - use Cake\View\Widget\WidgetInterface; - - class AutocompleteWidget implements WidgetInterface - { - /** - * StringTemplate instance. - * - * @var \Cake\View\StringTemplate - */ - protected $_templates; - - /** - * Constructor. - * - * @param \Cake\View\StringTemplate $templates Templates list. - */ - public function __construct(StringTemplate $templates) - { - $this->_templates = $templates; - } - - /** - * Methods that render the widget. - * - * @param array $data The data to build an input with. - * @param \Cake\View\Form\ContextInterface $context The current form context. - * - * @return string - */ - public function render(array $data, ContextInterface $context): string - { - $data += [ - 'name' => '', - ]; - - return $this->_templates->format('autocomplete', [ - 'name' => $data['name'], - 'attrs' => $this->_templates->formatAttributes($data, ['name']) - ]); - } - - public function secureFields(array $data): array - { - return [$data['name']]; - } - } - -Obviously, this is a very simple example, but it demonstrates how a custom -widget could be built. This widget would render the "autocomplete" string -template, such as:: - - $this->Form->setTemplates([ - 'autocomplete' => '' - ]); - -For more information on string templates, see :ref:`customizing-templates`. - -Using Widgets -------------- - -You can load custom widgets when loading FormHelper or by using the -``addWidget()`` method. When loading FormHelper, widgets are defined as -a setting:: - - // In View class - $this->loadHelper('Form', [ - 'widgets' => [ - 'autocomplete' => ['Autocomplete'], - ], - ]); - -If your widget requires other widgets, you can have FormHelper populate those -dependencies by declaring them:: - - $this->loadHelper('Form', [ - 'widgets' => [ - 'autocomplete' => [ - 'App\View\Widget\AutocompleteWidget', - 'text', - 'label', - ], - ], - ]); - -In the above example, the ``autocomplete`` widget would depend on the ``text`` and -``label`` widgets. If your widget needs access to the View, you should use the -``_view`` 'widget'. When the ``autocomplete`` widget is created, it will be passed -the widget objects that are related to the ``text`` and ``label`` names. To add -widgets using the ``addWidget()`` method would look like:: - - // Using a classname. - $this->Form->addWidget( - 'autocomplete', - ['Autocomplete', 'text', 'label'] - ); - - // Using an instance - requires you to resolve dependencies. - $autocomplete = new AutocompleteWidget( - $this->Form->getTemplater(), - $this->Form->getWidgetLocator()->get('text'), - $this->Form->getWidgetLocator()->get('label'), - ); - $this->Form->addWidget('autocomplete', $autocomplete); - -Once added/replaced, widgets can be used as the control 'type':: - - echo $this->Form->control('search', ['type' => 'autocomplete']); - -This will create the custom widget with a ``label`` and wrapping ``div`` just -like ``controls()`` always does. Alternatively, you can create just the control -widget using the magic method:: - - echo $this->Form->autocomplete('search', $options); - -Working with FormProtectionComponent -==================================== - -:php:meth:`Cake\\Controller\\Component\\FormProtectionComponent` offers several -features that make your forms safer and more secure. By simply including the -``FormProtectionComponent`` in your controller, you'll automatically benefit from -form tampering-prevention features. - -As mentioned previously when using FormProtectionComponent, you should always close -your forms using :php:meth:`~Cake\\View\\Helper\\FormHelper::end()`. This will -ensure that the special ``_Token`` inputs are generated. - -.. php:method:: unlockField($name) - -* ``$name`` - Optional. The dot-separated name for the field. - -Unlocks a field making it exempt from the ``FormProtectionComponent`` field -hashing. This also allows the fields to be manipulated by JavaScript. -The ``$name`` parameter should be the entity property name for the field:: - - $this->Form->unlockField('id'); - -.. php:method:: secure(array $fields = [], array $secureAttributes = []) - -* ``$fields`` - Optional. An array containing the list of fields to use when - generating the hash. If not provided, then ``$this->fields`` will be used. -* ``$secureAttributes`` - Optional. An array of HTML attributes to be passed - into the generated hidden input elements. - -Generates a hidden ``input`` field with a security hash based on the fields used -in the form or an empty string when secured forms are not in use. -If ``$secureAttributes`` is set, these HTML attributes will be -merged into the hidden input tags generated for the FormProtectionComponent. This is -especially useful to set HTML5 attributes like ``'form'``. - -.. meta:: - :title lang=en: FormHelper - :description lang=en: The FormHelper focuses on creating forms quickly, in a way that will streamline validation, re-population and layout. - :keywords lang=en: form helper,cakephp form,form create,form input,form select,form file field,form label,form text,form password,form checkbox,form radio,form submit,form date time,form error,validate upload,unlock field,form security diff --git a/en/views/helpers/html.rst b/en/views/helpers/html.rst deleted file mode 100644 index cdaa031b1d..0000000000 --- a/en/views/helpers/html.rst +++ /dev/null @@ -1,845 +0,0 @@ -Html -#### - -.. php:namespace:: Cake\View\Helper - -.. php:class:: HtmlHelper(View $view, array $config = []) - -The role of the HtmlHelper in CakePHP is to make HTML-related -options easier, faster, and more resilient to change. Using this -helper will enable your application to be more light on its feet, -and more flexible on where it is placed in relation to the root of -a domain. - -Many HtmlHelper methods include a ``$attributes`` parameter, -that allow you to tack on any extra attributes on your tags. Here -are a few examples of how to use the ``$attributes`` parameter: - -.. code-block:: html - - Desired attributes: - Array parameter: ['class' => 'someClass'] - - Desired attributes: - Array parameter: ['name' => 'foo', 'value' => 'bar'] - -Inserting Well-Formatted Elements -================================= - -The most important task the HtmlHelper accomplishes is creating -well formed markup. This section will cover some of the -methods of the HtmlHelper and how to use them. - -Creating Charset Tags ---------------------- - -.. php:method:: charset($charset=null) - -Used to create a meta tag specifying the document's character. The default value -is UTF-8. An example use:: - - echo $this->Html->charset(); - -Will output: - -.. code-block:: html - - - -Alternatively, :: - - echo $this->Html->charset('ISO-8859-1'); - -Will output: - -.. code-block:: html - - - -Linking to CSS Files --------------------- - -.. php:method:: css(mixed $path, array $options = []) - -Creates a link(s) to a CSS style-sheet. If the ``block`` option is set to -``true``, the link tags are added to the ``css`` block which you can print -inside the head tag of the document. - -You can use the ``block`` option to control which block the link element -will be appended to. By default it will append to the ``css`` block. - -If key 'rel' in ``$options`` array is set to 'import' the stylesheet will be imported. - -This method of CSS inclusion assumes that the CSS file specified -resides inside the **webroot/css** directory if path doesn't start with a '/'. :: - - echo $this->Html->css('forms'); - -Will output: - -.. code-block:: html - - - -The first parameter can be an array to include multiple files. :: - - echo $this->Html->css(['forms', 'tables', 'menu']); - -Will output: - -.. code-block:: html - - - - - -You can include CSS files from any loaded plugin using -:term:`plugin syntax`. To include **plugins/DebugKit/webroot/css/toolbar.css** -you could use the following:: - - echo $this->Html->css('DebugKit.toolbar.css'); - -If you want to include a CSS file which shares a name with a loaded -plugin you can do the following. For example if you had a ``Blog`` plugin, -and also wanted to include **webroot/css/Blog.common.css**, you would:: - - echo $this->Html->css('Blog.common.css', ['plugin' => false]); - -Creating CSS Programatically ----------------------------- - -.. php:method:: style(array $data, boolean $oneline = true) - -Builds CSS style definitions based on the keys and values of the -array passed to the method. Especially handy if your CSS file is -dynamic. :: - - echo $this->Html->style([ - 'background' => '#633', - 'border-bottom' => '1px solid #000', - 'padding' => '10px' - ]); - -Will output:: - - background:#633; border-bottom:1px solid #000; padding:10px; - -Creating meta Tags ------------------- - -.. php:method:: meta(string|array $type, string $url = null, array $options = []) - -This method is handy for linking to external resources like RSS/Atom feeds -and favicons. Like css(), you can specify whether or not you'd like this tag -to appear inline or appended to the ``meta`` block by setting the 'block' -key in the $attributes parameter to ``true``, ie - ``['block' => true]``. - -If you set the "type" attribute using the $attributes parameter, -CakePHP contains a few shortcuts: - -========= ====================== - type translated value -========= ====================== -html text/html -rss application/rss+xml -atom application/atom+xml -icon image/x-icon -csrfToken The current CSRF token -========= ====================== - -.. code-block:: php - - echo $this->Html->meta( - 'favicon.ico', - '/favicon.ico', - ['type' => 'icon'] - ); - // Output (line breaks added) - // Note: The helper code makes two meta tags to ensure the - // icon is downloaded by both newer and older browsers - // which require different rel attribute values. - - - - echo $this->Html->meta( - 'Comments', - '/comments/index.rss', - ['type' => 'rss'] - ); - // Output (line breaks added) - - -This method can also be used to add the meta keywords and -descriptions. Example:: - - echo $this->Html->meta( - 'keywords', - 'enter any meta keyword here' - ); - // Output - - - echo $this->Html->meta( - 'description', - 'enter any meta description here' - ); - // Output - - - echo $this->Html->meta('csrfToken'); - // The CsrfProtection middleware must be loaded for your application - - -In addition to making predefined meta tags, you can create link elements:: - - Html->meta([ - 'link' => 'http://example.com/manifest', - 'rel' => 'manifest' - ]); - ?> - // Output - - -Any attributes provided to meta() when called this way will be added to the -generated link tag. - -.. versionchanged:: 5.1.0 - The ``csrfToken`` type was added. - -Linking to Images ------------------ - -.. php:method:: image(string $path, array $options = []) - -Creates a formatted image tag. The path supplied should be relative -to **webroot/img/**. :: - - echo $this->Html->image('cake_logo.png', ['alt' => 'CakePHP']); - -Will output: - -.. code-block:: html - - CakePHP - -To create an image link specify the link destination using the -``url`` option in ``$attributes``. :: - - echo $this->Html->image("recipes/6.jpg", [ - "alt" => "Brownies", - 'url' => ['controller' => 'Recipes', 'action' => 'view', 6] - ]); - -Will output: - -.. code-block:: html - -
      - Brownies - - -If you are creating images in emails, or want absolute paths to images you -can use the ``fullBase`` option:: - - echo $this->Html->image("logo.png", ['fullBase' => true]); - -Will output: - -.. code-block:: html - - - -You can include image files from any loaded plugin using -:term:`plugin syntax`. To include **plugins/DebugKit/webroot/img/icon.png** -You could use the following:: - - echo $this->Html->image('DebugKit.icon.png'); - -If you want to include an image file which shares a name with a loaded -plugin you can do the following. For example if you had a ``Blog`` plugin, -and also wanted to include **webroot/img/Blog.icon.png**, you would:: - - echo $this->Html->image('Blog.icon.png', ['plugin' => false]); - -If you would like the prefix of the URL to not be ``/img``, you can override this setting by specifying the prefix in the ``$options`` array :: - - echo $this->Html->image("logo.png", ['pathPrefix' => '']); - -Will output: - -.. code-block:: html - - - - -Creating Links --------------- - -.. php:method:: link($title, $url = null, array $options = []) - -General purpose method for creating HTML links. Use ``$options`` to -specify attributes for the element and whether or not the -``$title`` should be escaped. :: - - echo $this->Html->link( - 'Enter', - '/pages/home', - ['class' => 'button', 'target' => '_blank'] - ); - -Will output: - -.. code-block:: html - - Enter - -Use ``'_full'=>true`` option for absolute URLs:: - - echo $this->Html->link( - 'Dashboard', - ['controller' => 'Dashboards', 'action' => 'index', '_full' => true] - ); - -Will output: - -.. code-block:: html - - Dashboard - -Specify ``confirm`` key in options to display a JavaScript ``confirm()`` -dialog:: - - echo $this->Html->link( - 'Delete', - ['controller' => 'Recipes', 'action' => 'delete', 6], - ['confirm' => 'Are you sure you wish to delete this recipe?'] - ); - -Will output: - -.. code-block:: html - - - Delete - - -Query strings can also be created with ``link()``. :: - - echo $this->Html->link('View image', [ - 'controller' => 'Images', - 'action' => 'view', - 1, - '?' => ['height' => 400, 'width' => 500] - ]); - -Will output: - -.. code-block:: html - - View image - -HTML special characters in ``$title`` will be converted to HTML -entities. To disable this conversion, set the escape option to -``false`` in the ``$options`` array. :: - - echo $this->Html->link( - $this->Html->image("recipes/6.jpg", ["alt" => "Brownies"]), - "recipes/view/6", - ['escape' => false] - ); - -Will output: - -.. code-block:: html - - - Brownies - - -Setting ``escape`` to ``false`` will also disable escaping of attributes of the -link. You can use the option ``escapeTitle`` to disable just -escaping of title and not the attributes. :: - - echo $this->Html->link( - $this->Html->image('recipes/6.jpg', ['alt' => 'Brownies']), - 'recipes/view/6', - ['escapeTitle' => false, 'title' => 'hi "howdy"'] - ); - -Will output: - -.. code-block:: html - - - Brownies - - -Also check :php:meth:`Cake\\View\\Helper\\UrlHelper::build()` method -for more examples of different types of URLs. - -.. php:method:: linkFromPath(string $title, string $path, array $params = [], array $options = []) - -If you want to use route path strings, you can do that using this method:: - - echo $this->Html->linkFromPath('Index', 'Articles::index'); - // outputs: Index - - echo $this->Html->linkFromPath('View', 'MyBackend.Admin/Articles::view', [3]); - // outputs: View - -Linking to Videos and Audio Files ---------------------------------- - -.. php:method:: media(string|array $path, array $options) - -Options: - -- ``type`` Type of media element to generate, valid values are "audio" - or "video". If type is not provided media type is guessed based on - file's mime type. -- ``text`` Text to include inside the video tag -- ``pathPrefix`` Path prefix to use for relative URLs, defaults to - 'files/' -- ``fullBase`` If provided the src attribute will get a full address - including domain name - -Returns a formatted audio/video tag: - -.. code-block:: php - - Html->media('audio.mp3') ?> - - // Output - - - Html->media('video.mp4', [ - 'fullBase' => true, - 'text' => 'Fallback text' - ]) ?> - - // Output - - - Html->media( - ['video.mp4', ['src' => 'video.ogg', 'type' => "video/ogg; codecs='theora, vorbis'"]], - ['autoplay'] - ) ?> - - // Output - - -Linking to Javascript Files ---------------------------- - -.. php:method:: script(mixed $url, mixed $options) - -Include a script file(s), contained either locally or as a remote URL. - -By default, script tags are added to the document inline. If you override -this by setting ``$options['block']`` to ``true``, the script tags will instead -be added to the ``script`` block which you can print elsewhere in the document. -If you wish to override which block name is used, you can do so by setting -``$options['block']``. - -``$options['once']`` controls whether or -not you want to include this script once per request or more than -once. This defaults to ``true``. - -You can use $options to set additional properties to the -generated script tag. If an array of script tags is used, the -attributes will be applied to all of the generated script tags. - -This method of JavaScript file inclusion assumes that the -JavaScript file specified resides inside the **webroot/js** -directory:: - - echo $this->Html->script('scripts'); - -Will output: - -.. code-block:: html - - - -You can link to files with absolute paths as well to link files -that are not in **webroot/js**:: - - echo $this->Html->script('/otherdir/script_file'); - -You can also link to a remote URL:: - - echo $this->Html->script('https://code.jquery.com/jquery.min.js'); - -Will output: - -.. code-block:: html - - - -The first parameter can be an array to include multiple files. :: - - echo $this->Html->script(['jquery', 'wysiwyg', 'scripts']); - -Will output: - -.. code-block:: html - - - - - -You can append the script tag to a specific block using the ``block`` -option:: - - $this->Html->script('wysiwyg', ['block' => 'scriptBottom']); - -In your layout you can output all the script tags added to 'scriptBottom':: - - echo $this->fetch('scriptBottom'); - -You can include script files from any loaded plugin using -:term:`plugin syntax`. To include **plugins/DebugKit/webroot/js/toolbar.js** -You could use the following:: - - echo $this->Html->script('DebugKit.toolbar.js'); - -If you want to include a script file which shares a name with a loaded -plugin you can do the following. For example if you had a ``Blog`` plugin, -and also wanted to include **webroot/js/Blog.plugins.js**, you would:: - - echo $this->Html->script('Blog.plugins.js', ['plugin' => false]); - -Creating Inline Javascript Blocks ---------------------------------- - -.. php:method:: scriptBlock(string $code, array $options = []) - -To generate Javascript blocks from PHP view code, you can use one of the script -block methods. Scripts can either be output in place, or buffered into a block:: - - // Define a script block all at once, with the defer attribute. - $this->Html->scriptBlock('alert("hi")', ['defer' => true]); - - // Buffer a script block to be output later. - $this->Html->scriptBlock('alert("hi")', ['block' => true]); - -.. php:method:: scriptStart(array $options = []) -.. php:method:: scriptEnd() - -You can use the ``scriptStart()`` method to create a capturing block that will -output into a `` - -Generating maps with imports, scopes and integrity:: - - echo $this->Html->importmap([ - 'imports' => [ - 'jquery' => 'jquery-3.7.1.min.js', - 'wysiwyg' => '/editor/wysiwyg.js' - ], - 'scopes' => [ - 'scoped/' => [ - 'foo' => 'inner/foo', - ], - ], - 'integrity' => [ - 'jquery' => 'sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=', - ], - ]); - -Will output: - -.. code-block:: html - - - -Creating Nested Lists ---------------------- - -.. php:method:: nestedList(array $list, array $options = [], array $itemOptions = []) - -Build a nested list (UL/OL) out of an associative array:: - - $list = [ - 'Languages' => [ - 'English' => [ - 'American', - 'Canadian', - 'British', - ], - 'Spanish', - 'German', - ] - ]; - echo $this->Html->nestedList($list); - -Output: - -.. code-block:: html - - // Output (minus the whitespace) -
        -
      • Languages -
          -
        • English -
            -
          • American
          • -
          • Canadian
          • -
          • British
          • -
          -
        • -
        • Spanish
        • -
        • German
        • -
        -
      • -
      - -Creating Table Headings ------------------------ - -.. php:method:: tableHeaders(array $names, array $trOptions = null, array $thOptions = null) - -Creates a row of table header cells to be placed inside of -tags. :: - - echo $this->Html->tableHeaders(['Date', 'Title', 'Active']); - -Output: - -.. code-block:: html - - - - - - - -:: - - echo $this->Html->tableHeaders( - ['Date', 'Title','Active'], - ['class' => 'status'], - ['class' => 'product_table'] - ); - -Output: - -.. code-block:: html - - - - - - - -You can set attributes per column, these are used instead of the -defaults provided in the ``$thOptions``:: - - echo $this->Html->tableHeaders([ - 'id', - ['Name' => ['class' => 'highlight']], - ['Date' => ['class' => 'sortable']] - ]); - -Output: - -.. code-block:: html - - - - - - - -Creating Table Cells --------------------- - -.. php:method:: tableCells(array $data, array $oddTrOptions = null, array $evenTrOptions = null, $useCount = false, $continueOddEven = true) - -Creates table cells, in rows, assigning attributes differently -for odd- and even-numbered rows. Wrap a single table cell within an -[] for specific - - - -:: - - echo $this->Html->tableCells([ - ['Jul 7th, 2007', ['Best Brownies', ['class' => 'highlight']] , 'Yes'], - ['Jun 21st, 2007', 'Smart Cookies', 'Yes'], - ['Aug 1st, 2006', 'Anti-Java Cake', ['No', ['id' => 'special']]], - ]); - -Output: - -.. code-block:: html - - - - - - - - - - - - - - - - - -:: - - echo $this->Html->tableCells( - [ - ['Red', 'Apple'], - ['Orange', 'Orange'], - ['Yellow', 'Banana'], - ], - ['class' => 'darker'] - ); - -Output: - -.. code-block:: html - - - - - -Changing the Tags Output by HtmlHelper -====================================== - -.. php:method:: setTemplates(array $templates) - -Load an array of templates to add/replace templates:: - - // Load specific templates. - $this->Html->setTemplates([ - 'javascriptlink' => '' - ]); - -You can load a configuration file containing templates using the templater -directly:: - - // Load a configuration file with templates. - $this->Html->templater()->load('my_tags'); - -When loading files of templates, your file should look like:: - - '' - ]; - -.. warning:: - - Template strings containing a percentage sign (``%``) need special attention, - you should prefix this character with another percentage so it looks like - ``%%``. The reason is that internally templates are compiled to be used with - ``sprintf()``. Example: ``
      {{content}}
      `` - -.. meta:: - :title lang=en: HtmlHelper - :description lang=en: The role of the HtmlHelper in CakePHP is to make HTML-related options easier, faster, and more resilient to change. - :keywords lang=en: html helper,cakephp css,cakephp script,content type,html image,html link,html tag,script block,script start,html url,cakephp style,cakephp crumbs diff --git a/en/views/helpers/number.rst b/en/views/helpers/number.rst deleted file mode 100644 index df47fdeec3..0000000000 --- a/en/views/helpers/number.rst +++ /dev/null @@ -1,25 +0,0 @@ -Number -###### - -.. php:namespace:: Cake\View\Helper - -.. php:class:: NumberHelper(View $view, array $config = []) - -The NumberHelper contains convenient methods that enable display -numbers in common formats in your views. These methods include ways -to format currency, percentages, data sizes, format numbers to -specific precisions and also to give you more flexibility with -formatting numbers. - -.. include:: /core-libraries/number.rst - :start-after: start-cakenumber - :end-before: end-cakenumber - -.. warning:: - - All symbols are UTF-8. - -.. meta:: - :title lang=en: NumberHelper - :description lang=en: The NumberHelper contains convenience methods that enable display numbers in common formats in your views. - :keywords lang=en: number helper,currency,number format,number precision,format file size,format numbers diff --git a/en/views/helpers/paginator.rst b/en/views/helpers/paginator.rst deleted file mode 100644 index 19810632b6..0000000000 --- a/en/views/helpers/paginator.rst +++ /dev/null @@ -1,583 +0,0 @@ -Paginator -######### - -.. php:namespace:: Cake\View\Helper - -.. php:class:: PaginatorHelper(View $view, array $config = []) - -The PaginatorHelper is used to output pagination controls such as page numbers -and next/previous links. - -See also :doc:`/controllers/pagination` for information on how to -create paginated datasets and do paginated queries. - -Setting the paginated resultset -------------------------------- - -.. php:method:: setPaginated($paginated, $options) - -By default the helper uses the first instance of ``Cake\Datasource\Paging\PaginatedInterface`` -it finds in the view variables. (Generally the result of ``Controller::paginate()``). - -You can use ``PaginatorHelper::setPaginated()`` to explicitly set the paginated -resultset that the helper should use. - -.. _paginator-templates: - -PaginatorHelper Templates -========================= - -Internally PaginatorHelper uses a series of simple HTML templates to generate -markup. You can modify these templates to customize the HTML generated by the -PaginatorHelper. - -Templates use ``{{var}}`` style placeholders. It is important to not add any -spaces around the ``{{}}`` or the replacements will not work. - -Loading Templates from a File ------------------------------ - -When adding the PaginatorHelper in your controller, you can define the -'templates' setting to define a template file to load. This allows you to -customize multiple templates and keep your code DRY:: - - // In your AppView.php - public function initialize(): void - { - ... - $this->loadHelper('Paginator', ['templates' => 'paginator-templates']); - } - -This will load the file located at **config/paginator-templates.php**. See the -example below for how the file should look like. You can also load templates -from a plugin using :term:`plugin syntax`:: - - // In your AppView.php - public function initialize(): void - { - ... - $this->loadHelper('Paginator', ['templates' => 'MyPlugin.paginator-templates']); - } - -Whether your templates are in the primary application or a plugin, your -templates file should look something like:: - - return [ - 'number' => '{{text}}', - ]; - -Changing Templates at Run-time ------------------------------- - -.. php:method:: setTemplates($templates) - -This method allows you to change the templates used by PaginatorHelper at -runtime. This can be useful when you want to customize templates for a -particular method call:: - - // Read the current template value. - $result = $this->Paginator->getTemplates('number'); - - // Change a template - $this->Paginator->setTemplates([ - 'number' => '{{text}}' - ]); - -.. warning:: - - Template strings containing a percentage sign (``%``) need special - attention, you should prefix this character with another percentage so it - looks like ``%%``. The reason is that internally templates are compiled to - be used with ``sprintf()``. - Example: '
      {{content}}
      ' - -Template Names --------------- - -PaginatorHelper uses the following templates: - -- ``nextActive`` The active state for a link generated by next(). -- ``nextDisabled`` The disabled state for next(). -- ``prevActive`` The active state for a link generated by prev(). -- ``prevDisabled`` The disabled state for prev() -- ``counterRange`` The template counter() uses when format == range. -- ``counterPages`` The template counter() uses when format == pages. -- ``first`` The template used for a link generated by first(). -- ``last`` The template used for a link generated by last() -- ``number`` The template used for a link generated by numbers(). -- ``current`` The template used for the current page. -- ``ellipsis`` The template used for ellipses generated by numbers(). -- ``sort`` The template for a sort link with no direction. -- ``sortAsc`` The template for a sort link with an ascending direction. -- ``sortDesc`` The template for a sort link with a descending direction. - -Creating Sort Links -=================== - -.. php:method:: sort($key, $title = null, $options = []) - - :param string $key: The name of the column that the recordset should be sorted. - :param string $title: Title for the link. If $title is null, $key will be - used converted to "Title Case" format and used as the title. - :param array $options: Options for sorting link. - -Generates a sorting link. Sets querystring parameters for the sort and -direction. Links will default to sorting by asc. After the first click, links -generated with ``sort()`` will handle direction switching automatically. If the -resultset is sorted 'asc' by the specified key the returned link will sort by -'desc'. Uses the ``sort``, ``sortAsc``, ``sortDesc``, ``sortAscLocked`` and -``sortDescLocked`` templates. - -Accepted keys for ``$options``: - -* ``escape`` Whether you want the contents HTML entity encoded, defaults to - ``true``. -* ``direction`` The default direction to use when this link isn't active. -* ``lock`` Lock direction. Will only use the default direction then, defaults to ``false``. - -Assuming you are paginating some posts, and are on page one:: - - echo $this->Paginator->sort('user_id'); - -Output: - -.. code-block:: html - - User Id - -You can use the title parameter to create custom text for your link:: - - echo $this->Paginator->sort('user_id', 'User account'); - -Output: - -.. code-block:: html - - User account - -If you are using HTML like images in your links remember to set escaping off:: - - echo $this->Paginator->sort( - 'user_id', - 'User account', - ['escape' => false] - ); - -Output: - -.. code-block:: html - - User account - -The direction option can be used to set the default direction for a link. Once a -link is active, it will automatically switch directions like normal:: - - echo $this->Paginator->sort('user_id', null, ['direction' => 'desc']); - -Output: - -.. code-block:: html - - User Id - -The lock option can be used to lock sorting into the specified direction:: - - echo $this->Paginator->sort('user_id', null, ['direction' => 'asc', 'lock' => true]); - -.. php:method:: sortDir(string $model = null, mixed $options = []) - - Gets the current direction the recordset is sorted. - -.. php:method:: sortKey(string $model = null, mixed $options = []) - - Gets the current key by which the recordset is sorted. - -Creating Page Number Links -========================== - -.. php:method:: numbers($options = []) - -Returns a set of numbers for the paged result set. Uses a modulus to -decide how many numbers to show on each side of the current page By default -8 links on either side of the current page will be created if those pages exist. -Links will not be generated for pages that do not exist. The current page is -also not a link. The ``number``, ``current`` and ``ellipsis`` templates will be -used. - -Supported options are: - -* ``before`` Content to be inserted before the numbers. -* ``after`` Content to be inserted after the numbers. -* ``modulus`` how many numbers to include on either side of the current page, - defaults to 8. -* ``first`` Whether you want first links generated, set to an integer to - define the number of 'first' links to generate. Defaults to ``false``. If a - string is set a link to the first page will be generated with the value as the - title:: - - echo $this->Paginator->numbers(['first' => 'First page']); - -* ``last`` Whether you want last links generated, set to an integer to define - the number of 'last' links to generate. Defaults to ``false``. Follows the same - logic as the ``first`` option. There is a - :php:meth:`~PaginatorHelper::last()` method to be used separately as well if - you wish. - -While this method allows a lot of customization for its output. It is -also ok to just call the method without any parameters. :: - - echo $this->Paginator->numbers(); - -Using the first and last options you can create links to the beginning -and end of the page set. The following would create a set of page links that -include links to the first 2 and last 2 pages in the paged results:: - - echo $this->Paginator->numbers(['first' => 2, 'last' => 2]); - -Creating Jump Links -=================== - -In addition to generating links that go directly to specific page numbers, -you'll often want links that go to the previous and next links, first and last -pages in the paged data set. - -.. php:method:: prev($title = '<< Previous', $options = []) - - :param string $title: Title for the link. - :param mixed $options: Options for pagination link. - - Generates a link to the previous page in a set of paged records. Uses - the ``prevActive`` and ``prevDisabled`` templates. - - ``$options`` supports the following keys: - - * ``escape`` Whether you want the contents HTML entity encoded, - defaults to ``true``. - * ``disabledTitle`` The text to use when the link is disabled. Defaults to - the ``$title`` parameter. - - A simple example would be:: - - echo $this->Paginator->prev(' << ' . __('previous')); - - If you were currently on the second page of posts, you would get the following: - - .. code-block:: html - - - - If there were no previous pages you would get: - - .. code-block:: html - - - - To change the templates used by this method see :ref:`paginator-templates`. - -.. php:method:: next($title = 'Next >>', $options = []) - - This method is identical to :php:meth:`~PaginatorHelper::prev()` with a few exceptions. It - creates links pointing to the next page instead of the previous one. It also - uses ``next`` as the rel attribute value instead of ``prev``. Uses the - ``nextActive`` and ``nextDisabled`` templates. - -.. php:method:: first($first = '<< first', $options = []) - - Returns a first or set of numbers for the first pages. If a string is given, - then only a link to the first page with the provided text will be created:: - - echo $this->Paginator->first('< first'); - - The above creates a single link for the first page. Will output nothing if you - are on the first page. You can also use an integer to indicate how many first - paging links you want generated:: - - echo $this->Paginator->first(3); - - The above will create links for the first 3 pages, once you get to the third or - greater page. Prior to that nothing will be output. Uses the ``first`` - template. - - The options parameter accepts the following: - - - ``escape`` Whether or not the text should be escaped. Set to ``false`` if your - content contains HTML. - -.. php:method:: last($last = 'last >>', $options = []) - - This method works very much like the :php:meth:`~PaginatorHelper::first()` - method. It has a few differences though. It will not generate any links if you - are on the last page for a string values of ``$last``. For an integer value of - ``$last`` no links will be generated once the user is inside the range of last - pages. Uses the ``last`` template. - -Creating Header Link Tags -========================= - -PaginatorHelper can be used to create pagination link tags in your page -```` elements:: - - // Create next/prev links for the current model. - echo $this->Paginator->meta(); - - // Create next/prev & first/last links for the current model. - echo $this->Paginator->meta(['first' => true, 'last' => true]); - -Checking the Pagination State -============================= - -.. php:method:: current() - - Gets the current page of the recordset:: - - // Our URL is: /comments?page=3 - echo $this->Paginator->current(); - // Output is 3 - - Uses the ``current`` template. - -.. php:method:: hasNext(string $model = null) - - Returns ``true`` if the given result set is not at the last page. - -.. php:method:: hasPrev() - - Returns ``true`` if the given result set is not at the first page. - -.. php:method:: hasPage(int $page = 1) - - Returns ``true`` if the given result set has the page number given by ``$page``. - -.. php:method:: total() - - Returns the total number of pages for the provided model. - -Creating a Page Counter -======================= - -.. php:method:: counter(string $format = 'pages', array $options = []) - -Returns a counter string for the paged result set. Using a provided format -string and a number of options you can create localized and application -specific indicators of where a user is in the paged data set. Uses the -``counterRange``, and ``counterPages`` templates. - -Supported formats are 'range', 'pages' and custom. Defaults to pages which would -output like '1 of 10'. In the custom mode the supplied string is parsed and -tokens are replaced with actual values. The available tokens are: - -- ``{{page}}`` - the current page displayed. -- ``{{pages}}`` - total number of pages. -- ``{{current}}`` - current number of records being shown. -- ``{{count}}`` - the total number of records in the result set. -- ``{{start}}`` - number of the first record being displayed. -- ``{{end}}`` - number of the last record being displayed. -- ``{{model}}`` - The pluralized human form of the model name. - If your model was 'RecipePage', ``{{model}}`` would be 'recipe pages'. - -You could also supply only a string to the counter method using the tokens -available. For example:: - - echo $this->Paginator->counter( - 'Page {{page}} of {{pages}}, showing {{current}} records out of - {{count}} total, starting on record {{start}}, ending on {{end}}' - ); - -Setting 'format' to range would output like '1 - 3 of 13':: - - echo $this->Paginator->counter('range'); - -Generating Pagination URLs -========================== - -.. php:method:: generateUrl(array $options = [], ?string $model = null, array $url = [], array $urlOptions = []) - -By default returns a full pagination URL string for use in non-standard contexts -(i.e. JavaScript). :: - - // Generates a URL similar to: /articles?sort=title&page=2 - echo $this->Paginator->generateUrl(['sort' => 'title']); - - // Generates a URL for a different model - echo $this->Paginator->generateUrl(['sort' => 'title'], 'Comments'); - - // Generates a URL to a different controller. - echo $this->Paginator->generateUrl( - ['sort' => 'title'], - null, - ['controller' => 'Comments'] - ); - -Creating a Limit Selectbox Control -================================== - -.. php:method:: limitControl(array $limits = [], $default = null, array $options = []) - -Create a dropdown control that changes the ``limit`` query parameter:: - - // Use the defaults. - echo $this->Paginator->limitControl(); - - // Define which limit options you want. - echo $this->Paginator->limitControl([25 => 25, 50 => 50]); - - // Custom limits and set the selected option - echo $this->Paginator->limitControl([25 => 25, 50 => 50], $user->perPage); - -The generated form and control will automatically submit on change. - -Configuring Pagination Options -============================== - -.. php:method:: options($options = []) - -Sets all the options for the PaginatorHelper. Supported options are: - -* ``url`` The URL of the paginating action. - - The option allows your to set/override any element for URLs generated by - the helper:: - - $this->Paginator->options([ - 'url' => [ - 'lang' => 'en', - '?' => [ - 'sort' => 'email', - 'direction' => 'desc', - 'page' => 6, - ], - ] - ]); - - The example above adds the ``en`` route parameter to all links the helper will - generate. It will also create links with specific sort, direction and page - values. By default ``PaginatorHelper`` will merge in all of the current passed - arguments and query string parameters. - -* ``escape`` Defines if the title field for links should be HTML escaped. - Defaults to ``true``. - -Example Usage -============= - -It's up to you to decide how to show records to the user, but most often this -will be done inside HTML tables. The examples below assume a tabular layout, but -the PaginatorHelper available in views doesn't always need to be restricted as -such. - -See the details on -`PaginatorHelper `_ in -the API. As mentioned, the PaginatorHelper also offers sorting features which -can be integrated into your table column headers: - -.. code-block:: php - - -
      DateTitleActive
      DateTitleActive
      idNameDate
      -attributes. :: - - echo $this->Html->tableCells([ - ['Jul 7th, 2007', 'Best Brownies', 'Yes'], - ['Jun 21st, 2007', 'Smart Cookies', 'Yes'], - ['Aug 1st, 2006', 'Anti-Java Cake', 'No'], - ]); - -Output: - -.. code-block:: html - -
      Jul 7th, 2007Best BrowniesYes
      Jun 21st, 2007Smart CookiesYes
      Aug 1st, 2006Anti-Java CakeNo
      - Jul 7th, 2007 - - Best Brownies - - Yes -
      - Jun 21st, 2007 - - Smart Cookies - - Yes -
      - Aug 1st, 2006 - - Anti-Java Cake - - No -
      RedApple
      OrangeOrange
      YellowBanana
      - - - - - - - - - - -
      Paginator->sort('id', 'ID') ?>Paginator->sort('title', 'Title') ?>
      id ?> title) ?>
      - -The links output from the ``sort()`` method of the ``PaginatorHelper`` allow -users to click on table headers to toggle the sorting of the data by a given -field. - -It is also possible to sort a column based on associations: - -.. code-block:: php - - - - - - - - - - - - -
      Paginator->sort('title', 'Title') ?>Paginator->sort('Authors.name', 'Author') ?>
      title) ?> name) ?>
      - -.. note:: - - Sorting by columns in associated models requires setting these in the - ``PaginationComponent::paginate`` property. Using the example above, the - controller handling the pagination would need to set its ``sortableFields`` - key as follows: - - .. code-block:: php - - $this->paginate = [ - 'sortableFields' => [ - 'Posts.title', - 'Authors.name', - ], - ]; - - For more information on using the ``sortableFields`` option, please see - :ref:`control-which-fields-used-for-ordering`. - -The final ingredient to pagination display in views is the addition of page -navigation, also supplied by the PaginationHelper:: - - // Shows the page numbers - Paginator->numbers() ?> - - // Shows the next and previous links - Paginator->prev('« Previous') ?> - Paginator->next('Next »') ?> - - // Prints X of Y, where X is current page and Y is number of pages - Paginator->counter() ?> - -The wording output by the counter() method can also be customized using special -markers:: - - Paginator->counter([ - 'format' => 'Page {{page}} of {{pages}}, showing {{current}} records out of - {{count}} total, starting on record {{start}}, ending on {{end}}' - ]) ?> - -.. _paginator-helper-multiple: - -Paginating Multiple Results -=========================== - -If you are :ref:`paginating multiple queries ` -you'll need to use ``PaginatorHelper::setPaginated()`` first before calling -other methods of the helper, so that they generate expected output. - -``PaginatorHelper`` will automatically use the ``scope`` defined in when the -query was paginated. To set additional URL parameters for multiple pagination -you can include the scope names in ``options()``:: - - $this->Paginator->options([ - 'url' => [ - // Additional URL parameters for the 'articles' scope - 'articles' => [ - '?' => ['articles' => 'yes'] - ], - // Additional URL parameters for the 'comments' scope - 'comments' => [ - 'articleId' => 1234, - ], - ], - ]); - -.. meta:: - :title lang=en: PaginatorHelper - :description lang=en: The PaginatorHelper is used to output pagination controls such as page numbers and next/previous links. - :keywords lang=en: paginator helper,pagination,sort,page number links,pagination in views,prev link,next link,last link,first link,page counter diff --git a/en/views/helpers/text.rst b/en/views/helpers/text.rst deleted file mode 100644 index 08f73f5362..0000000000 --- a/en/views/helpers/text.rst +++ /dev/null @@ -1,94 +0,0 @@ -Text -#### - -.. php:namespace:: Cake\View\Helper - -.. php:class:: TextHelper(View $view, array $config = []) - -The TextHelper contains methods to make text more usable and -friendly in your views. It aids in enabling links, formatting URLs, -creating excerpts of text around chosen words or phrases, -highlighting key words in blocks of text, and gracefully -truncating long stretches of text. - -Linking Email addresses -======================= - -.. php:method:: autoLinkEmails(string $text, array $options = []) - -Adds links to the well-formed email addresses in $text, according -to any options defined in ``$options`` (see -:php:meth:`HtmlHelper::link()`). :: - - $myText = 'For more information regarding our world-famous ' . - 'pastries and desserts, contact info@example.com'; - $linkedText = $this->Text->autoLinkEmails($myText); - -Output:: - - For more information regarding our world-famous pastries and desserts, - contact info@example.com - -This method automatically escapes its input. Use the ``escape`` -option to disable this if necessary. - -Linking URLs -============ - -.. php:method:: autoLinkUrls(string $text, array $options = []) - -Same as ``autoLinkEmails()``, only this method searches for -strings that start with https, http, ftp, or nntp and links them -appropriately. - -This method automatically escapes its input. Use the ``escape`` -option to disable this if necessary. - -Linking Both URLs and Email Addresses -===================================== - -.. php:method:: autoLink(string $text, array $options = []) - -Performs the functionality in both ``autoLinkUrls()`` and -``autoLinkEmails()`` on the supplied ``$text``. All URLs and emails -are linked appropriately given the supplied ``$options``. - -This method automatically escapes its input. Use the ``escape`` -option to disable this if necessary. - -Further options: - -* ``stripProtocol``: Strips ``http://`` and ``https://`` from the beginning of - the link label. Default off. -* ``maxLength``: The maximum length of the link label. Default off. -* ``ellipsis``: The string to append to the end of the link label. Defaults to - UTF8 ellipsis. - -Converting Text into Paragraphs -=============================== - -.. php:method:: autoParagraph(string $text) - -Adds proper

      around text where double-line returns are found, and
      where -single-line returns are found. :: - - $myText = 'For more information - regarding our world-famous pastries and desserts. - - contact info@example.com'; - $formattedText = $this->Text->autoParagraph($myText); - -Output:: - -

      For more information
      - regarding our world-famous pastries and desserts.

      -

      contact info@example.com

      - -.. include:: /core-libraries/text.rst - :start-after: start-text - :end-before: end-text - -.. meta:: - :title lang=en: TextHelper - :description lang=en: The TextHelper contains methods to make text more usable and friendly in your views. - :keywords lang=en: text helper,autoLinkEmails,autoLinkUrls,autoLink,excerpt,highlight,stripLinks,truncate,string text diff --git a/en/views/helpers/time.rst b/en/views/helpers/time.rst deleted file mode 100644 index b1ba1c9d9e..0000000000 --- a/en/views/helpers/time.rst +++ /dev/null @@ -1,51 +0,0 @@ -Time -#### - -.. php:namespace:: Cake\View\Helper - -.. php:class:: TimeHelper(View $view, array $config = []) - -The TimeHelper allows for the quick processing of time related information. -The TimeHelper has two main tasks that it can perform: - -#. It can format time strings. -#. It can test time. - -Using the Helper -================ - -A common use of the TimeHelper is to offset the date and time to match a -user's time zone. Lets use a forum as an example. Your forum has many users who -may post messages at any time from any part of the world. A way to -manage the time is to save all dates and times as GMT+0 or UTC. Uncomment the -line ``date_default_timezone_set('UTC');`` in **config/bootstrap.php** to ensure -your application's time zone is set to GMT+0. - -Next add a time zone field to your users table and make the necessary -modifications to allow your users to set their time zone. Now that we know -the time zone of the logged in user we can correct the date and time on our -posts using the TimeHelper:: - - echo $this->Time->format( - $post->created, - \IntlDateFormatter::FULL, - false, - $user->time_zone - ); - // Will display 'Saturday, August 22, 2011 at 11:53:00 PM GMT' - // for a user in GMT+0. While displaying, - // 'Saturday, August 22, 2011 at 03:53 PM GMT-8:00' - // for a user in GMT-8 - -Most of TimeHelper's features are intended as backwards compatible interfaces -for applications that are upgrading from older versions of CakePHP. Because the -ORM returns :php:class:`Cake\\I18n\\Time` instances for every ``timestamp`` -and ``datetime`` column, you can use the methods there to do most tasks. -For example, to read about the accepted formatting strings take a look at the -`Cake\\I18n\\Time::i18nFormat() -`_ method. - -.. meta:: - :title lang=en: TimeHelper - :description lang=en: The TimeHelper will help you format time and test time. - :keywords lang=en: time helper,format time,timezone,unix epoch,time strings,time zone offset,utc,gmt diff --git a/en/views/helpers/url.rst b/en/views/helpers/url.rst deleted file mode 100644 index 58d389f40b..0000000000 --- a/en/views/helpers/url.rst +++ /dev/null @@ -1,163 +0,0 @@ -Url -### - -.. php:namespace:: Cake\View\Helper - -.. php:class:: UrlHelper(View $view, array $config = []) - -The UrlHelper helps you to generate URLs from your other helpers. -It also gives you a single place to customize how URLs are generated by -overriding the core helper with an application one. See the -:ref:`aliasing-helpers` section for how to do this. - -Generating URLs -=============== - -.. php:method:: build($url = null, array $options = []) - -Returns a URL pointing to a combination of controller and action. -If ``$url`` is empty, it returns the ``REQUEST_URI``, otherwise it -generates the URL for the controller and action combo. If ``fullBase`` is -``true``, the full base URL will be prepended to the result:: - - echo $this->Url->build([ - 'controller' => 'Posts', - 'action' => 'view', - 'bar', - ]); - - // Output - /posts/view/bar - -Here are a few more usage examples: - -URL with extension:: - - echo $this->Url->build([ - 'controller' => 'Posts', - 'action' => 'list', - '_ext' => 'rss', - ]); - - // Output - /posts/list.rss - -URL with prefix:: - - echo $this->Url->build([ - 'controller' => 'Posts', - 'action' => 'list', - 'prefix' => 'Admin', - ]); - - // Output - /admin/posts/list - -URL (starting with '/') with the full base URL prepended:: - - echo $this->Url->build('/posts', ['fullBase' => true]); - - // Output - http://somedomain.com/posts - -URL with GET parameters and fragment anchor:: - - echo $this->Url->build([ - 'controller' => 'Posts', - 'action' => 'search', - '?' => ['foo' => 'bar'], - '#' => 'first', - ]); - - // Output - /posts/search?foo=bar#first - -The above example uses the ``?`` special key for specifying query string -parameters and ``#`` key for URL fragment. - -URL for named route:: - - // Assuming a route is setup as a named route: - // $router->connect( - // '/products/{slug}', - // [ - // 'controller' => 'Products', - // 'action' => 'view', - // ], - // [ - // '_name' => 'product-page', - // ] - // ); - - echo $this->Url->build(['_name' => 'product-page', 'slug' => 'i-m-slug']); - // Will result in: - /products/i-m-slug - -The 2nd parameter allows you to define options controlling HTML escaping, and -whether or not the base path should be added:: - - $this->Url->build('/posts', [ - 'escape' => false, - 'fullBase' => true, - ]); - -.. php:method:: buildFromPath(string $path, array $params = [], array $options = []) - -If you want to use route path strings, you can do that using this method:: - - echo $this->Url->buildFromPath('Articles::index'); - // outputs: /articles - - echo $this->Url->buildFromPath('MyBackend.Admin/Articles::view', [3]); - // outputs: /admin/my-backend/articles/view/3 - -URL with asset timestamp wrapped by a ````, here pre-loading -a font. Note: The file must exist and ``Configure::read('Asset.timestamp')`` -must return ``true`` or ``'force'`` for the timestamp to be appended:: - - echo $this->Html->meta([ - 'rel' => 'preload', - 'href' => $this->Url->assetUrl( - '/assets/fonts/your-font-pack/your-font-name.woff2' - ), - 'as' => 'font', - ]); - -If you are generating URLs for CSS, Javascript or image files there are helper -methods for each of these asset types:: - - // Outputs /img/icon.png - $this->Url->image('icon.png'); - - // Outputs /js/app.js - $this->Url->script('app.js'); - - // Outputs /css/app.css - $this->Url->css('app.css'); - - // Force timestamps for one method call. - $this->Url->css('app.css', ['timestamp' => 'force']); - - // Or disable timestamps for one method call. - $this->Url->css('app.css', ['timestamp' => false]); - -Customizing Asset URL generation -================================ - -If you need to customize how asset URLs are generated, or want to use custom -asset cache busting parameters you can use the ``assetUrlClassName`` option:: - - // In view initialize - $this->loadHelper('Url', ['assetUrlClassName' => AppAsset::class]); - -When using the ``assetUrlClassName`` you must implement the same methods as -``Cake\Routing\Asset`` does. - -For further information check -`Router::url `_ -in the API. - -.. meta:: - :title lang=en: UrlHelper - :description lang=en: The role of the UrlHelper in CakePHP is to help build urls. - :keywords lang=en: url helper,url diff --git a/en/views/json-and-xml-views.rst b/en/views/json-and-xml-views.rst deleted file mode 100644 index 7aa2d88efd..0000000000 --- a/en/views/json-and-xml-views.rst +++ /dev/null @@ -1,250 +0,0 @@ -JSON and XML views -################## - -The ``JsonView`` and ``XmlView`` integration with CakePHP's -:ref:`controller-viewclasses` features and let you create JSON and XML responses. - -These view classes are most commonly used alongside :php:meth:`Cake\\Controller\\Controller::viewClasses()`. - -There are two ways you can generate data views. The first is by using the -``serialize`` option, and the second is by creating normal template files. - -Defining View Classes to Negotiate With -======================================= - -In your ``AppController`` or in an individual controller you can implement the -``viewClasses()`` method and provide all of the views you want to support:: - - use Cake\View\JsonView; - use Cake\View\XmlView; - - public function viewClasses(): array - { - return [JsonView::class, XmlView::class]; - } - -You can optionally enable the json and/or xml extensions with -:ref:`file-extensions`. This will allow you to access the ``JSON``, ``XML`` or -any other special format views by using a custom URL ending with the name of the -response type as a file extension such as ``http://example.com/articles.json``. - -By default, when not enabling :ref:`file-extensions`, the ``Accept`` -header in the request is used for selecting which type of format should be rendered to the -user. An example ``Accept`` format that is used to render ``JSON`` responses is -``application/json``. - -Using Data Views with the Serialize Key -======================================= - -The ``serialize`` option indicates which view variable(s) should be -serialized when using a data view. This lets you skip defining template files -for your controller actions if you don't need to do any custom formatting before -your data is converted into json/xml. - -If you need to do any formatting or manipulation of your view variables before -generating the response, you should use template files. The value of -``serialize`` can be either a string or an array of view variables to -serialize:: - - - namespace App\Controller; - - use Cake\View\JsonView; - - class ArticlesController extends AppController - { - public function viewClasses(): array - { - return [JsonView::class]; - } - - public function index() - { - // Set the view vars - $this->set('articles', $this->paginate()); - // Specify which view vars JsonView should serialize. - $this->viewBuilder()->setOption('serialize', 'articles'); - } - } - -You can also define ``serialize`` as an array of view variables to combine:: - - namespace App\Controller; - - use Cake\View\JsonView; - - class ArticlesController extends AppController - { - public function viewClasses(): array - { - return [JsonView::class]; - } - - public function index() - { - // Some code that created $articles and $comments - - // Set the view vars - $this->set(compact('articles', 'comments')); - - // Specify which view vars JsonView should serialize. - $this->viewBuilder()->setOption('serialize', ['articles', 'comments']); - } - } - -Defining ``serialize`` as an array has added the benefit of automatically -appending a top-level ```` element when using :php:class:`XmlView`. -If you use a string value for ``serialize`` and XmlView, make sure that your -view variable has a single top-level element. Without a single top-level -element the Xml will fail to generate. - -Using a Data View with Template Files -===================================== - -You should use template files if you need to manipulate your view -content before creating the final output. For example, if we had articles with a field containing generated HTML, we would probably want to omit that from a -JSON response. This is a situation where a view file would be useful:: - - // Controller code - class ArticlesController extends AppController - { - public function index() - { - $articles = $this->paginate('Articles'); - $this->set(compact('articles')); - } - } - - // View code - templates/Articles/json/index.php - foreach ($articles as $article) { - unset($article->generated_html); - } - echo json_encode(compact('articles')); - -You can do more complex manipulations, or use helpers to do formatting as well. -The data view classes don't support layouts. They assume that the view file will -output the serialized content. - -Creating XML Views -================== - -.. php:class:: XmlView - -By default when using ``serialize`` the XmlView will wrap your serialized -view variables with a ```` node. You can set a custom name for -this node using the ``rootNode`` option. - -The XmlView class supports the ``xmlOptions`` option that allows you to -customize the options, such as ``tags`` or ``attributes``, used to generate XML. - -An example of using ``XmlView`` would be to generate a `sitemap.xml -`_. This document type requires that you -change ``rootNode`` and set attributes. Attributes are defined using the ``@`` -prefix:: - - use Cake\View\XmlView; - - public function viewClasses(): array - { - return [XmlView::class]; - } - - public function sitemap() - { - $pages = $this->Pages->find()->all(); - $urls = []; - foreach ($pages as $page) { - $urls[] = [ - 'loc' => Router::url(['controller' => 'Pages', 'action' => 'view', $page->slug, '_full' => true]), - 'lastmod' => $page->modified->format('Y-m-d'), - 'changefreq' => 'daily', - 'priority' => '0.5', - ]; - } - - // Define a custom root node in the generated document. - $this->viewBuilder() - ->setOption('rootNode', 'urlset') - ->setOption('serialize', ['@xmlns', 'url']); - $this->set([ - // Define an attribute on the root node. - '@xmlns' => 'http://www.sitemaps.org/schemas/sitemap/0.9', - 'url' => $urls, - ]); - } - -Creating JSON Views -=================== - -.. php:class:: JsonView - -The JsonView class supports the ``jsonOptions`` option that allows you to -customize the bit-mask used to generate JSON. See the -`json_encode `_ documentation for the valid -values of this option. - -For example, to serialize validation error output of CakePHP entities in a consistent form of JSON do:: - - // In your controller's action when saving failed - $this->set('errors', $articles->errors()); - $this->viewBuilder() - ->setOption('serialize', ['errors']) - ->setOption('jsonOptions', JSON_FORCE_OBJECT); - -JSONP Responses ---------------- - -When using ``JsonView`` you can use the special view variable ``jsonp`` to -enable returning a JSONP response. Setting it to ``true`` makes the view class -check if query string parameter named "callback" is set and if so wrap the json -response in the function name provided. If you want to use a custom query string -parameter name instead of "callback" set ``jsonp`` to required name instead of -``true``. - -Choosing a View Class -===================== - -While you can use the ``viewClasses`` hook method most of the time, if you want -total control over view class selection you can directly choose the view class:: - - // src/Controller/VideosController.php - namespace App\Controller; - - use App\Controller\AppController; - use Cake\Http\Exception\NotFoundException; - - class VideosController extends AppController - { - public function export($format = '') - { - $format = strtolower($format); - - // Format to view mapping - $formats = [ - 'xml' => 'Xml', - 'json' => 'Json', - ]; - - // Error on unknown type - if (!isset($formats[$format])) { - throw new NotFoundException(__('Unknown format.')); - } - - // Set Out Format View - $this->viewBuilder()->setClassName($formats[$format]); - - // Get data - $videos = $this->Videos->find('latest')->all(); - - // Set Data View - $this->set(compact('videos')); - $this->viewBuilder()->setOption('serialize', ['videos']); - - // Set Force Download - return $this->response->withDownload('report-' . date('YmdHis') . '.' . $format); - } - } - -.. meta:: - :title lang=en: JSON and XML views - :keywords lang=en: json,xml,presentation layer,view,ajax,logic,syntax,templates,cakephp diff --git a/en/views/themes.rst b/en/views/themes.rst deleted file mode 100644 index 75fcec0925..0000000000 --- a/en/views/themes.rst +++ /dev/null @@ -1,71 +0,0 @@ -Themes -###### - -Themes in CakePHP are simply plugins that focus on providing template files. -See the section on :ref:`plugin-create-your-own`. -You can take advantage of themes, allowing you to switch the look and feel of -your page quickly. In addition to template files, they can also provide helpers -and cells if your theming requires that. When using cells and helpers from your -theme, you will need to continue using the :term:`plugin syntax`. - -First ensure your theme plugin is loaded in your application's ``bootstrap`` -method. For example:: - - // Load our plugin theme residing in the folder /plugins/Modern - $this->addPlugin('Modern'); - -To use themes, set the theme name in your controller's action or -``beforeRender()`` callback:: - - class ExamplesController extends AppController - { - public function beforeRender(\Cake\Event\EventInterface $event) - { - $this->viewBuilder()->setTheme('Modern'); - } - } - -Theme template files need to be within a plugin with the same name. For example, -the above theme would be found in **plugins/Modern/templates**. -It's important to remember that CakePHP expects PascalCase plugin/theme names. Beyond -that, the folder structure within the **plugins/Modern/templates** folder is -exactly the same as **templates/**. - -For example, the view file for an edit action of a Posts controller would reside -at **plugins/Modern/templates/Posts/edit.php**. Layout files would reside in -**plugins/Modern/templates/layout/**. You can provide customized templates -for plugins with a theme as well. If you had a plugin named 'Cms', that -contained a TagsController, the Modern theme could provide -**plugins/Modern/templates/plugin/Cms/Tags/edit.php** to replace the edit -template in the plugin. - -If a view file can't be found in the theme, CakePHP will try to locate the view -file in the **templates/** folder. This way, you can create master template files -and simply override them on a case-by-case basis within your theme folder. - -Theme Assets -============ - -Because themes are standard CakePHP plugins, they can include any necessary -assets in their webroot directory. This allows for packaging and -distribution of themes. Whilst in development, requests for theme assets will be -handled by :php:class:`Cake\Routing\Middleware\AssetMiddleware` (which is loaded -by default in cakephp/app ``Application::middleware()``). To improve -performance for production environments, it's recommended that you :ref:`symlink-assets`. - -All of CakePHP's built-in helpers are aware of themes and will create the -correct paths automatically. Like template files, if a file isn't in the theme -folder, it will default to the main webroot folder:: - - // When in a theme with the name of 'purple_cupcake' - $this->Html->css('main.css'); - - // creates a path like - /purple_cupcake/css/main.css - - // and links to - plugins/PurpleCupcake/webroot/css/main.css - -.. meta:: - :title lang=en: Themes - :keywords lang=en: production environments,theme folder,layout files,development requests,callback functions,folder structure,default view,dispatcher,symlink,case basis,layouts,assets,cakephp,themes,advantage diff --git a/es/404.rst b/es/404.rst deleted file mode 100644 index ea46426b1d..0000000000 --- a/es/404.rst +++ /dev/null @@ -1,6 +0,0 @@ -:orphan: True - -No encontrado -############# - -No se ha podido encontrar la página que estabas buscando. Prueba a utilizar la barra de búsqueda para encontar lo que estás buscando. \ No newline at end of file diff --git a/es/Makefile b/es/Makefile deleted file mode 100644 index ad4fde2697..0000000000 --- a/es/Makefile +++ /dev/null @@ -1,133 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = ../build -CONFDIR = ../config -PYTHON = python3 -SPHINX_LANG = es - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees/$(SPHINX_LANG) -c $(CONFDIR) -D language=$(SPHINX_LANG) $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html -D "exclude_patterns=*-contents.rst" $(ALLSPHINXOPTS) $(BUILDDIR)/html/$(SPHINX_LANG) - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html/$(SPHINX_LANG)." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp/$(SPHINX_LANG) - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp/$(SPHINX_LANG)." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/CakePHPCookbook.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/CakePHPCookbook.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/CakePHPCookbook" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/CakePHPCookbook" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub -D master_doc='epub-contents' $(ALLSPHINXOPTS) $(BUILDDIR)/epub/$(SPHINX_LANG) - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub/$(SPHINX_LANG)." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex/$(SPHINX_LANG) - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex/$(SPHINX_LANG)." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex/$(SPHINX_LANG) - @echo "Running LaTeX files through pdflatex..." - make -C $(BUILDDIR)/latex/$(SPHINX_LANG) all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex/$(SPHINX_LANG)." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/es/_static/img/middleware-request.png b/es/_static/img/middleware-request.png deleted file mode 100644 index 7301b09033..0000000000 Binary files a/es/_static/img/middleware-request.png and /dev/null differ diff --git a/es/_static/img/middleware-setup.png b/es/_static/img/middleware-setup.png deleted file mode 100644 index 5e65f5c499..0000000000 Binary files a/es/_static/img/middleware-setup.png and /dev/null differ diff --git a/es/_static/img/read-the-book.jpg b/es/_static/img/read-the-book.jpg deleted file mode 100644 index cb3bd7cd78..0000000000 Binary files a/es/_static/img/read-the-book.jpg and /dev/null differ diff --git a/es/_static/img/save-cycle.png b/es/_static/img/save-cycle.png deleted file mode 100644 index 8c133e5f6a..0000000000 Binary files a/es/_static/img/save-cycle.png and /dev/null differ diff --git a/es/_static/img/typical-cake-request.png b/es/_static/img/typical-cake-request.png deleted file mode 100644 index 15c2ed501c..0000000000 Binary files a/es/_static/img/typical-cake-request.png and /dev/null differ diff --git a/es/_static/img/validation-cycle.png b/es/_static/img/validation-cycle.png deleted file mode 100644 index 85ee172442..0000000000 Binary files a/es/_static/img/validation-cycle.png and /dev/null differ diff --git a/es/appendices.rst b/es/appendices.rst deleted file mode 100644 index 5c2ef86c3c..0000000000 --- a/es/appendices.rst +++ /dev/null @@ -1,43 +0,0 @@ -Apéndices -######### - -En los apéndices encontrarás información relacionada con las nuevas características -introducidas en cada versión, así como también las guías de migración entre versiones. - -Guía de Migración a 5.x -======================= - -.. toctree:: - :maxdepth: 1 - - appendices/5-0-upgrade-guide - appendices/5-0-migration-guide - -Retrocompatiblidad -=================== - -Si quieres utilizar funcionalidades de 3.x o 4.x o ir migrando poco a poco -el `Plugin Shim `__ puede ayudarte a mitigar algunos -de los cambios que rompen la compatibilidad. - -Antecompatiblidad -================= - -La Antecompatibilidad puede preparar tu aplicación 4.x para la próxima versión 5.x. - -Si quieres utilizar una funcionalidad de 5.x en 4.x, revisa el `Plugin Shim -`__. Este plugin te ayuda a mitigar los -problemas de compatibilidad y a llevar funcionalidades de 5.x a 4.x. - -General Information -=================== - -.. toctree:: - :maxdepth: 1 - - appendices/cakephp-development-process - appendices/glossary - -.. meta:: - :title lang=es: Apéndices - :keywords lang=es: migration guide,migration path,new features,glossary diff --git a/es/appendices/5-0-migration-guide.rst b/es/appendices/5-0-migration-guide.rst deleted file mode 100644 index 034972f6af..0000000000 --- a/es/appendices/5-0-migration-guide.rst +++ /dev/null @@ -1,414 +0,0 @@ -5.0 Guía de migración -##################### - -CakePHP 5.0 contiene cambios importantes, y no es compatible con versiones anteriores -de 4.x. Antes de intentar actualizar a la version 5.0, primero actualice a la version 4.5 y resuelva -todas las advertencias de obsolescencia. - -Consulte :doc:`/appendices/5-0-upgrade-guide` para obtener instrucciones paso a paso de -como actualizar a la versión 5.0. - -Características obsoletas eliminadas -==================================== - -Todos los métodos, propiedades y funcionalidades que emitían advertencias de obsolencias -a partir de la versión 4.5 se han eliminado. - -Cambios importantes -=================== - -Además de la eliminación de características obsoletas, se han realizado -cambios importantes: - -Global ------- - -- Se han añadido declaraciones de tipo a todos los parámetros de función y devoluciones siempre que ha sido posible. Estos - están pensados para que coincidan con las anotaciones de docblock, pero incluyen correcciones para anotaciones incorrectas. -- Se han añadido declaraciones de tipo a todas las propiedades de clase siempre que ha sido posible. También se han corregido - algunas anotaciones incorrectas. -- Se han eliminado las constantes ``SECOND``, ``MINUTE``, ``HOUR``, ``DAY``, ``WEEK``, ``MONTH``, ``YEAR``. -- Las funciones globales son ahora opcionales. Si tu aplicación utiliza alias de funciones globales, asegúrase - de añadir ``require CAKE . 'functions.php'`` al ``config/bootstrap.php`` de tu aplicación. -- Se ha eliminado el uso de ``#[\AllowDynamicProperties]`` en todas las partes. Se utilizaba para las siguientes clases: - - ``Command/Command`` - - ``Console/Shell`` - - ``Controller/Component`` - - ``Controller/Controller`` - - ``Mailer/Mailer`` - - ``View/Cell`` - - ``View/Helper`` - - ``View/View`` -- Se han actualizado las versiones compatibles del motor de base de datos: - - MySQL (5.7 o superior) - - MariaDB (10.1 o superior) - - PostgreSQL (9.6 o superior) - - Microsoft SQL Server (2012 o superior) - - SQLite 3 - -Auth ----- - -- `Auth` ha sido eliminado. Usa los plugins `cakephp/authentication `__ y - `cakephp/authorization `__ en su lugar. - -Cache ------ - -- El motor ``Wincache`` ha sido eliminado. La extension wincache no es compatible - con PHP 8. - -Consola -------- - -- ``BaseCommand::__construct()`` ha sido eliminado. -- Se ha eliminado ``ConsoleIntegrationTestTrait::useCommandRunner()`` porque ya no es necesario. -- ``Shell`` Ha sido eliminado y debe ser sustituido por `Command `__ -- Ahora ``BaseCommand`` emite los eventos ``Command.beforeExecute`` and ``Command.afterExecute`` - cuando el método ``execute()`` del comando es invocado por el framework. - -Connection ----------- - -- Se ha eliminado ``Connection::prepare()``. En su lugar, puede utilizar ``Connection::execute()`` - para ejecutar una consulta SQL especificando en la cadena SQL los parámetros y los tipos en una sola llamada. -- Se ha eliminado ``Connection::enableQueryLogging()``. Si no ha habilitado el registro - a través de la configuración de conexión, puedes configurar más adelante la instancia del registrador para que - el controlador habilite el registro de consultas ``$connection->getDriver()->setLogger()``. - -Controlador ------------ - -- La firma del método para ``Controller::__construct()`` ha cambiado. - Por lo tanto, tienes que ajustar el código en consecuencia si estás sobreescribiendo el constructor. -- Después de la carga, los componentes ya no se establecen como propiedades dinámicas. En su lugar - ``Controller`` usa ``__get()`` para proporcionar acceso a las propiedades de los componentes. Este - cambio puede afectar a las aplicaciones que usan ``property_exists()`` en los componentes. -- Se ha renombrado la devolución de llamada del evento ``Controller.shutdown`` de los componentes de - ``shutdown`` a ``afterFilter`` para que coincida con el del controlador. Esto hace que las devoluciones de llamada - sean más coherentes. -- ``PaginatorComponent`` ha sido eliminado y tienes que reemplazarlo llamando a ``$this->paginate()`` en tu controlador o - usando ``Cake\Datasource\Paging\NumericPaginator`` directamente. -- ``RequestHandlerComponent`` ha sido eliminado. Consulte la guía `4.4 migration `__ para saber como actualizarlo. -- Se ha eliminado ``SecurityComponent``. Usa ``FormProtectionComponent`` para la protección contra la manipulación de formularios - o ``HttpsEnforcerMiddleware`` para forzar el uso de solicitudes HTTPS en su lugar. -- ``Controller::paginate()`` ya no acepta opciones de consulta como ``contain`` para su - argumento ``$settings``. En su lugar debes usar la opción ``finder`` - ``$this->paginate($this->Articles, ['finder' => 'published'])``. O puede - crear la consulta requerida de antemano y luego pasarla a ``paginate()`` - ``$query = $this->Articles->find()->where(['is_published' => true]); $this->paginate($query);``. - -Core ----- - -- La función ``getTypeName()`` ha sido desechada. En su lugar usa ``get_debug_type()`` de PHP. -- La dependencia de ``league/container`` se actualizó a ``4.x``. Esto requerirá - la adición de typehints a tus implementaciones de ``ServiceProvider``. -- ``deprecationWarning()`` ahora tiene un parámetro ``$version``. -- La opción de configuración ``App.uploadedFilesAsObjects`` se ha eliminado - junto con el soporte para arrays con forma carga de archivos PHP en todo el framework. -- ``ClassLoader`` ha sido eliminado. En su lugar, utiliza composer para generar archivos de carga automática. - -Base de datos -------------- - -- ``DateTimeType`` y ``DateType`` ahora siempre devuelven objetos inmutables. - Además, la interfaz para los objetos ``Date`` refleja la interfaz ``ChronosDate`` - que carece de todos los métodos relacionados con el tiempo que estaban presentes en CakePHP 4.x. -- ``DateType::setLocaleFormat()`` ya no acepta array. -- ``Query`` ahora solo acepta parámetros ``\Closure`` en lugar de ``callable``. Los callables se pueden convertir - a closures usando la nueva sintaxis de array de primera clase de PHP 8.1. -- ``Query::execute()`` ya no ejecuta el resultado de la ejeción de la consulta. Debe utilizar ``Query::all()`` en su lugar. -- ``TableSchemaAwareInterface`` fue eliminado. -- ``Driver::quote()`` fue eliminado. En su lugar, utiliza declaraciones preparadas. -- ``Query::orderBy()`` fue añadido para reemplazar ``Query::order()``. -- ``Query::groupBy()`` fue añadido para reemplazar ``Query::group()``. -- ``SqlDialectTrait`` se ha eliminado y toda su funcionalidad se ha movido a la propia clase ``Driver``. -- ``CaseExpression`` ha sido eliminado y debe ser reemplazado por - ``QueryExpression::case()`` o ``CaseStatementExpression`` -- ``Connection::connect()`` ha sido eliminado. Usar ``$connection->getDriver()->connect()`` en su lugar. -- ``Connection::disconnect()`` ha sido eliminado. Usar ``$connection->getDriver()->disconnect()`` en su lugar. -- ``cake.database.queries`` ha sido añadido como alternativa al scope ``queriesLog``. - -Datasource ----------- - -- El método ``getAccessible()`` ha sido añadido a ``EntityInterface``. Las implementaciones que no son ORM - tienen que implementar este método ahora. -- El método ``aliasField()`` ha sido añadido a ``RepositoryInterface``. Las implementaciones que no son ORM - tienen que implementar este método ahora. - -Eventos -------- - -- Las cargas útiles de eventos deben ser un array. Otros objetos como ``ArrayAccess`` ya no se convierten en array y ahora lanzarán un ``TypeError``. -- Se recomienda ajustar los handlers de eventos para que sean métodos void y usar ``$event->setResult()`` en lugar de devolver el resultado. - -Error ------ - -- ``ErrorHandler`` y ``ConsoleErrorHandler`` han sido eliminados. Consulte la guía `4.4 migration `__ para saber como actualizarlo. -- ``ExceptionRenderer`` ha sido eliminado y debe ser reemplazado por ``WebExceptionRenderer`` -- ``ErrorLoggerInterface::log()`` ha sido eliminado y debe ser reemplazado por ``ErrorLoggerInterface::logException()`` -- ``ErrorLoggerInterface::logMessage()`` ha sido eliminado y debe ser reemplazado por ``ErrorLoggerInterface::logError()`` - -Filesystem ----------- - -- El paquete de Filesystem se ha eliminado, y la clase ``Filesystem`` se ha movido al paquete de Utility. - -Http ----- - -- ``ServerRequest`` ya no es compatible con ``files`` como arrays. Este - behavior se ha deshabilitado de forma predeterminada desde la version 4.1.0. Los datos ``files`` - ahora siempre contendrán objetos ``UploadedFileInterfaces``. - -I18n ----- - -- Se cambió el nombre de ``FrozenDate`` a `Date` y el de ``FrozenTime`` a `DateTime`. -- ``Time`` ahora extiende de ``Cake\Chronos\ChronosTime`` y. por lo tanto, es inmutable. -- ``Date::parseDateTime()`` ha sido eliminado. -- ``Date::parseTime()`` ha sido eliminado. -- ``Date::setToStringFormat()`` y ``Date::setJsonEncodeFormat()`` ya no aceptan un array. -- ``Date::i18nFormat()`` y ``Date::nice()`` ya no aceptan un parámetro de zona horaria. -- Los archivos de traducción en la carpeta de vendor con prefijo como (``FooBar/Awesome``) ahora tendrán - ese prefijo en el nombre del archivo de traducción, por ejemplo, ``foo_bar_awesome.po`` para evitar colisiones - con otro fichero ``awesome.po`` correspondiente con el plugin (``Awesome``). - -Log ---- - -- La configuración del motor de registros ahora utiliza ``null`` en lugar de ``false`` para desactivar los scopes. - Así que en lugar de ``'scopes' => false`` necesitas usar ``'scopes' => null`` en la configuración de tu log. - -Mailer ------- - -- Se ha eliminado ``Email``. Usar `Mailer `__ en su lugar. -- ``cake.mailer`` se ha añadido como alternativa al scope ``email``. - -ORM ---- - -- ``EntityTrait::has()`` ahora devuelve ``true`` cuando existe un atributo y es estable - en ``null``. En versiones anteriores de CakePHP esto devolvía ``false``. - Consulte las notas de la version 4.5.0 para saber como adoptar este comportamiento en 4.x. -- ``EntityTrait::extractOriginal()`` ahora devuelve solo los campos existentes, similar a ``extractOriginalChanged()``. -- Ahora se requiere que los argumentos de un `Finder` sean arrays asociativos, como siempre se esperó que fueran. -- ``TranslateBehavior`` ahora tiene como valor predeterminado la estrategia ``ShadowTable``. Si está - utilizando la estrategia ``Eav`` deberá actualizar la configuración de tu behavior para conservar - el comportamiento anterior. -- La opción ``allowMultipleNulls`` para la regla ``isUnique`` ahora es true de forma predeterminada, - coincidiendo con el comportamiento original de 3.x. -- ``Table::query()`` se ha eliminado en favor de funciones específicas de tipo de consulta. -- ``Table::updateQuery()``, ``Table::selectQuery()``, ``Table::insertQuery()``, y - ``Table::deleteQuery()`` se añadieron y ahora devuelven los nuevos objetos de consulta de tipo específico. -- Se añadieron ``SelectQuery``, ``InsertQuery``, ``UpdateQuery`` y ``DeleteQuery`` que representan - solo un tipo de consulta y no permiten cambiar entre tipos de consulta, sin llamar a funciones no relacionadas - con el tipo de consulta especifico. -- ``Table::_initializeSchema()`` ha sido eliminado y debe ser reemplazado llamando a - ``$this->getSchema()`` dentro del método ``initialize()``. -- ``SaveOptionsBuilder`` ha sido eliminado. En su lugar, utilice un array normal para las opciones. - -Enrutamiento ------------- - -- Los métodos estáticos ``connect()``, ``prefix()``, ``scope()`` y ``plugin()`` del ``Router`` han sido eliminados y - deben ser reemplazados llamando a sus variantes de método no estáticos a través de la instancia ``RouteBuilder``. -- ``RedirectException`` ha sido eliminado. Usar ``\Cake\Http\Exception\RedirectException`` en su lugar. - -TestSuite ---------- - -- ``TestSuite`` fue eliminado. En su lugar, los usuarios deben usar variables de entorno - para personalizar la configuración de las pruebas unitarias. -- ``TestListenerTrait`` fue eliminado. PHPUnit dejó de dar soporte a estos listeners. - Ver documentación :doc:`/appendices/phpunit10` -- ``IntegrationTestTrait::configRequest()`` ahora fusiona la configuración cuando se llama varias - veces en lugar de reemplazar la configuración actualmente presente. - -Validaciones ------------- - -- ``Validation::isEmpty()`` ya no es compatible con la subida de ficheros en forma - arrays. El soporte para la subida de ficheros en forma de array también se ha eliminado de - ``ServerRequest`` por lo que no debería ver esto como un problema fuera de las pruebas. -- Anteriormente, la mayoría de los mensajes de error de validacion de datos eran simplemente ``El valor proporcionado no es válido``. - Ahora, los mensajes de error de validación de datos están redactados con mayor precisión. - Por ejemplo, ``El valor proporcionado debe ser mayor o igual que \`5\```. - -Vistas ------- - -- Las opciones de ``ViewBuilder`` ahora son verdaderamente asociativas (string keys). -- ``NumberHelper`` y ``TextHelper`` ya no aceptan la configuración de ``engine``. -- ``ViewBuilder::setHelpers()`` el parámetro ``$merge`` fue eliminado. Usar ``ViewBuilder::addHelpers()`` en su lugar. -- Dentro ``View::initialize()``, preferentemente usar ``addHelper()`` en lugar de ``loadHelper()``. - De todas formas, todas las configuraciones de helpers se cargarán después. -- ``View\Widget\FileWidget`` ya no es compatible con la subida de ficheros en forma - arrays. Esto está alineado con los cambios en ``ServerRequest`` y ``Validation``. -- ``FormHelper`` ya no estable ``autocomplete=off`` en los campos de token CSRF. Esto - fue una solución para un error de Safari que no es relevante. - -Obsolescencias -============== - -A continuación se muestra una lista de métodos, propiedades y comportamientos en desuso. Estas -características seguirán funcionando en la versión 5.x y se eliminarán en la versión 6.0. - -Base de datos -------------- - -- ``Query::order()`` ha quedado obsoleto. Utiliza ``Query::orderBy()`` en su lugar - ahora que los métodos ``Connection`` ya no son proxy. Esto alinea el nombre de la función - con la instrucción SQL. -- ``Query::group()`` ha quedado obsoleto. Utiliza ``Query::groupBy()`` en su lugar - ahora que los métodos ``Connection`` ya no son proxy. Esto alinea el nombre de la función - con la instrucción SQL. - -ORM ---- - -- Llamar a ``Table::find()`` con opciones de array está obsoleto. Utiliza `named arguments `__ - en su lugar. Por ejemplo, en lugar de ``find('all', ['conditions' => $array])`` usar - ``find('all', conditions: $array)``. De manera similar, para las opciones de finders personalizados, en lugar - de ``find('list', ['valueField' => 'name'])`` usar ``find('list', valueField: 'name')`` - o varios argumentos como ``find(type: 'list', valueField: 'name', conditions: $array)``. - -Nuevas características -====================== - -Comprobación de tipos mejorada ------------------------------- - -CakePHP 5 aprovecha la función de sistema de tipos expandidos disponible en PHP 8.1+. -CakePHP también usa ``assert()`` para proporcionar mensajes de error mejorados y una solidez de tipo adicional. -En el modo de producción, puede configurar PHP para que no genere código para ``assert()`` lo que mejora el rendimiento de la aplicación. -Consulte :ref:`symlink-assets` para saber cómo hacerlo. - -Colecciones ------------ - -- Se añadió ``unique()`` que filtra el valor duplicado especificado por la devolución de llamada proporcionada. -- ``reject()`` ahora soporta una devolución de llamada predeterminada que filtra los valores verdaderos, - que es el inverso del comportamiento predeterminado de ``filter()`` - -Core ----- - -- El método ``services()`` se añadió a ``PluginInterface``. -- ``PluginCollection::addFromConfig()`` se ha añadido a :ref:`simplify plugin loading `. - -Base de datos -------------- - -- ``ConnectionManager`` ahora soporta roles de conexión de lectura y escritura. Los roles se pueden configurar - con claves de ``read`` y ``write`` en la configuración de conexión que anulan la configuración compartida. -- Se añadió ``Query::all()`` que ejecuta devoluciones de llamada del decorador de resultados y devuelve un conjunto de resultados para consultas seleccionadas. -- Se añadió ``Query::comment()`` para agregar un comentario SQL a la consulta ejecutada. Esto facilita la depuración de consultas. -- ``EnumType`` fue añadido para permitir el mapeo entre enumeraciones respaldadas por PHP y una cadena o columna entera. -- ``getMaxAliasLength()`` y ``getConnectionRetries()`` se añadieron a ``DriverInterface``. -- Los drivers compatibles ahora agregan automáticamente el incremento automático solo a las claves primarias enteras denominadas "id" - en lugar de a todas las claves primarias enteras. Si se establece 'autoIncrement' como false, siempre se deshabilita en todos los drivers compatibles. - -Http ----- - -- Se ha añadido soporte para 'factories interface' `PSR-17 `__. - Esto permite ``cakephp/http`` proporcionar una implementación de cliente a - bibliotecas que permiten la resolución automática de interfaces como php-http. -- Se añadieron ``CookieCollection::__get()`` y ``CookieCollection::__isset()`` para añadir - formas ergonómicas de acceder a las cookies sin excepciones. - -ORM ---- - -Campos de entidad obligatorios ------------------------------- - -Las entidades tienen una nueva funcionalidad de opt-in que permite hacer que las entidades manejen -propiedades de manera más estricta. El nuevo comportamiento se denomina 'required fields'. Cuando -es habilitado, el acceso a las propiedades que no están definidas en la entidad generará -excepciones. Esto afecta a los siguientes usos:: - - $entity->get(); - $entity->has(); - $entity->getOriginal(); - isset($entity->attribute); - $entity->attribute; - -Los campos se consideran definidos si pasan ``array_key_exists``. Esto incluye -valores nulos. Debido a que esta puede ser una característica tediosa de habilitar, se aplazó a -5.0. Nos gustaría recibir cualquier comentario que tenga sobre esta función, -ya que estamos considerando hacer que este sea el comportamiento predeterminado en el futuro. - - -Typed Finder Parameters ------------------------ - -Los finders de las tablas ahora pueden tener argumentos escritos según sea necesario en lugar de un array de opciones. -Por ejemplo, un finder para obtener publicaciones por categoría o usuario:: - - public function findByCategoryOrUser(SelectQuery $query, array $options) - { - if (isset($options['categoryId'])) { - $query->where(['category_id' => $options['categoryId']]); - } - if (isset($options['userId'])) { - $query->where(['user_id' => $options['userId']]); - } - - return $query; - } - -Ahora se pueden escribir como:: - - public function findByCategoryOrUser(SelectQuery $query, ?int $categoryId = null, ?int $userId = null) - { - if ($categoryId) { - $query->where(['category_id' => $categoryId]); - } - if ($userId) { - $query->where(['user_id' => $userId]); - } - - return $query; - } - -El finder puede ser llamado como ``find('byCategoryOrUser', userId: $somevar)``. -Incluso puedes incluir los argumentos con nombre especial para establecer cláusulas de consulta. -``find('byCategoryOrUser', userId: $somevar, conditions: ['enabled' => true])``. - -Un cambio similar se ha aplicado al método ``RepositoryInterface::get()``:: - - public function view(int $id) - { - $author = $this->Authors->get($id, [ - 'contain' => ['Books'], - 'finder' => 'latest', - ]); - } - -Ahora se pueden escribir como:: - - public function view(int $id) - { - $author = $this->Authors->get($id, contain: ['Books'], finder: 'latest'); - } - -TestSuite ---------- - -- Se ha añadido ``IntegrationTestTrait::requestAsJson()`` para establecer encabezados JSON para la siguiente solicitud. - -Instalador de plugins ---------------------- -- El instalador de plugins se ha actualizado para manejar automáticamente la carga automática de clases para los plugins - de tu aplicación. Por lo tanto, puedes eliminar el espacio de nombres para las asignaciones de rutas de - tus plugins del ``composer.json`` y simplemente ejecutar ``composer dumpautoload``. - -.. meta:: - :title lang=es: 5.0 Guía de migración - :keywords lang=es: maintenance branch,community interaction,community feature,necessary feature,stable release,ticket system,advanced feature,power users,feature set,chat irc,leading edge,router,new features,members,attempt,development branches,branch development diff --git a/es/appendices/5-0-upgrade-guide.rst b/es/appendices/5-0-upgrade-guide.rst deleted file mode 100644 index 921bf05820..0000000000 --- a/es/appendices/5-0-upgrade-guide.rst +++ /dev/null @@ -1,77 +0,0 @@ -5.0 Guía de actualización -######################### - -En primer lugar, compruebe que su aplicación se está ejecutando en la última versión de CakePHP 4.x. - -Arreglar avisos de obsolescencia -================================ - -Una vez que su aplicación se ejecuta en la última version de CakePHP 4.x, activar advertencias de obsoletos en **config/app.php**:: - - 'Error' => [ - 'errorLevel' => E_ALL, - ] - -Ahora que puede ver todas las advertencias, asegúrese de que están corregidas antes de proceder con la actualización. - -Algunas obsolescencia potencialmente impactantes que debes asegurarte de haber abordado -son: - -- ``Table::query()`` was deprecated in 4.5.0. Use ``selectQuery()``, - ``updateQuery()``, ``insertQuery()`` and ``deleteQuery()`` instead. - -Actualiza a PHP 8.1 -=================== - -Si no estas ejecutando en **PHP 8.1 o superior**, tendrás que actualizar PHP antes de actualizar CakePHP. - -.. note:: - CakePHP 5.0 requiere **un mínimo de PHP 8.1**. - -.. _upgrade-tool-use: - -Usar la herramienta de actualización -==================================== - -.. note:: - La herramienta de actualización sólo funciona en aplicaciones que se ejecutan en cakePHP 4.x. No puedes ejecutar la herramienta de actualización después de actualizar a CakePHP 5.0. - -Debido a que CakePHP 5 aprovecha los tipos de unión y ``mixed``, existen muchos -cambios incompatibles con versiones anteriores relativas a las definiciones de los métodos y cambios de nombre archivos. -Para ayudar a acelerar los arreglos de estos cambios tediosos, existe una herramienta CLI de actualización: - -.. code-block:: console - - # Instalar la herramienta de actualización - git clone https://github.com/cakephp/upgrade - cd upgrade - git checkout 5.x - composer install --no-dev - -Con la herramienta de actualización instalada, ahora puedes ejecutarla en su aplicación o -plugin:: - - bin/cake upgrade rector --rules cakephp50 - bin/cake upgrade rector --rules chronos3 - -Actualizar dependencias de CakePHP -================================== - -Después de aplicar las refactorizaciones de Rector necesitas actualizar CakePHP, sus plugins, PHPUnit -y tal vez otras dependencias en el ``composer.json``. -Este proceso depende de gran medida de tu aplicación por lo que te recomendamos que compares el -``composer.json`` con el que está presente en `cakephp/app -`__. - -After the version strings are adjusted in your ``composer.json`` execute -``composer update -W`` and check its output. - -Actualiza los archivos de la aplicación basándose en las últimas plantillas -=========================================================================== - -A continuación, asegúrate de que el resto de tu aplicación esté actualizado basándose en la última version de `cakephp/app -`__. - -.. meta:: - :title lang=es: 5.0 Guía de actualización - :keywords lang=es: maintenance branch,community interaction,community feature,necessary feature,stable release,ticket system,advanced feature,power users,feature set,chat irc,leading edge,router,new features,members,attempt,development branches,branch development diff --git a/es/appendices/cakephp-development-process.rst b/es/appendices/cakephp-development-process.rst deleted file mode 100644 index b3e4c24174..0000000000 --- a/es/appendices/cakephp-development-process.rst +++ /dev/null @@ -1,60 +0,0 @@ -CakePHP Development Process -########################### - -Los proyectos de CakePHP en general siguen `semver `__. Ésto significa que: - -- Las versiones se numeran en el formato **A.B.C** -- Las versiones **A** son *lanzamientos principales*. Contienen cambios importantes y - requerirán una cantidad significativa de trabajo para actualizar desde una version **A** inferior. -- Las versiones **A.B** son *lanzamientos de mejoras*. Cada versión será compatible con - las anteriores, pero puede marcar algunas características como **obsoletas**. Si es absolutamente - necesario realizar un cambio que rompa la compatibilidad, se indicará en la guía de migración para ese lanzamiento. -- Las versiones **A.B.C** son *lanzamientos de parches*. Deben ser compatibles con el lanzamiento de parche anterior. La excepción - a esta regla es si se descubre un problema de seguridad y la única solución es romper una API existente. - -Consulta el :doc:/contributing/backwards-compatibility para ver lo que consideramos como compatible con versiones previas y cambios que rompen la compatibilidad. - -Lanzamientos Principales -========================= - -Los lanzamientos principales introducen nuevas características y pueden eliminar funcionalidades que se hayan -marcado como obsoletas en un lanzamiento anterior. Estos lanzamientos se encuentran en las ramas ``next`` -que coinciden con su número de versión, como ``5.next``. Una vez que se lanzan, se promocionan a la rama -``master`` y luego la rama ``5.next`` se utiliza para futuros lanzamientos de características. - -Lanzamientos de Mejoras -======================== - -Los lanzamientos de mejoras son donde se envían nuevas funcionalidades o extensiones a las funcionalidades -existentes. Cada serie de lanzamientos que recibe actualizaciones tendrá una rama ``next``, por ejemplo, ``4.next``. -Si deseas contribuir con una nueva característica, por favor dirígete a estas ramas. - -Lanzamientos de Parches -======================== - -Los lanzamientos de parches corrigen errores en el código/documentación existente y siempre deben ser compatibles -con los lanzamientos de parches anteriores de la misma serie. Estos lanzamientos -se crean a partir de las ramas estables. Las ramas estables a menudo se nombran según la serie de lanzamientos, como ``3.x``. - -Frecuencia de Lanzamiento -========================== - -- Los *Lanzamientos Principales* se entregan aproximadamente cada dos o tres años. Este período de tiempo nos obliga a - ser deliberados y considerados con los cambios que rompen la compatibilidad, y brinda tiempo a la comunidad para - ponerse al día sin sentir que se están quedando atrás. -- Los *Lanzamientos de Mejoras* se entregan cada cinco a ocho meses. -- Los *Lanzamientos de Parches* se entregan inicialmente cada dos semanas. A medida que un lanzamiento de características madura, esta frecuencia se relaja a una entrega mensual. - -Política de Obsolescencia -========================== - -Antes de que una característica pueda ser eliminada en un lanzamiento principal, necesita ser marcada como obsoleta. Cuando -una funcionalidad se marca como obsoleta en el lanzamiento **A.x**, seguirá funcionando durante el resto de todos los lanzamientos -**A.x**. Las obsolescencias generalmente se indican mediante advertencias en PHP. Puedes habilitar las advertencias de obsolescencia -agregando ``E_USER_DEPRECATED`` al valor de ``Error.level`` de tu aplicación. - -El comportamiento marcado como obsoleto no se elimina hasta el próximo lanzamiento principal. Por ejemplo, un comportamiento marcado como obsoleto en ``4.1`` se eliminará en ``5.0``. - -.. meta:: - :title lang=es: Proceso de Desarrollo CakePHP - :keywords lang=en: maintenance branch,community interaction,community feature,necessary feature,stable release,ticket system,advanced feature,power users,feature set,chat irc,leading edge,router,new features,members,attempt,development branches,branch development diff --git a/es/appendices/glossary.rst b/es/appendices/glossary.rst deleted file mode 100644 index 644cab68c2..0000000000 --- a/es/appendices/glossary.rst +++ /dev/null @@ -1,88 +0,0 @@ -Glosario -######## - -.. glossary:: - - arreglo de enrutamiento - Un arreglo de atributos que son pasados a :php:meth:`Router::url()`. - Típicamente se ve algo así:: - - ['controller' => 'Posts', 'action' => 'view', 5] - - Atributos HTML - Un array con claves => valores que son colocados en los atributos HTML. Por ejemplo:: - - // Dado - ['class' => 'mi-clase', 'target' => '_blank'] - - // Generará - class="mi-clase" target="_blank" - - Si una opción puede usar su nombre como valor, entonces puede ser usado ``true``:: - - // Dado - ['checked' => true] - - // Generará - checked="checked" - - Sintaxis de plugin - La sintáxis de plugin se refiere a el punto que separa los nombres de clases indicando - que la clase es parte de un plugin:: - - // El plugin es "DebugKit", y el nombre de la clase es "Toolbar". - 'DebugKit.Toolbar' - - // El plugin es "AcmeCorp/Tools", y el nombre de clase es "Toolbar". - 'AcmeCorp/Tools.Toolbar' - - Notación de punto - La notación de punto define un array de rutas, separando los niveles anidados con ``.`` - Por ejemplo:: - - Cache.default.engine - - Apuntará al siguiente valor:: - - [ - 'Cache' => [ - 'default' => [ - 'engine' => 'File' - ] - ] - ] - - CSRF - *Cross Site Request Forgery*. Previene los ataques de replay o playback, peticiones - duplicadas y peticiones falsificadas desde otros dominios. - - CDN - *Content Delivery Network*. Le puedes pagar a un proveedor para que ayude a distribuir - el contenido a centros de datos alrededor del mundo. Esto ayuda a poner elementos - estáticos más cerca de tus usuarios geográficamente. - - routes.php - Un archivo en el directorio ``config`` que contiene las configuraciones de enrutamiento. - Este archivo se incluye antes de que cada petición sea procesada. - Se deben conectar todas las rutas que necesita tu aplicación para que cada petición sea enrutada - correctamente al controlador + acción. - - DRY - *Don't repeat yourself*. Es un principio de desarrollo de software orientado a - reducir la repetición de la información de todo tipo. En CakePHP, DRY - se utiliza para que se pueda escribir las cosas una vez y reutilizarlos - a través de su aplicación. - - PaaS - *Platform as a Service*. La plataforma como servicio - proporcionará recursos de hosting, bases de datos y almacenamiento - en caché basado en la nube. Algunos proveedores populares incluyen - Heroku, EngineYard y PagodaBox. - - DSN - *Data Source Name*. Una cadena de conexión formateada para que sea como una URI. - CakePHP soporta conexiones DSN para Caché, Base de datos, Registro y de E-mail. - -.. meta:: - :title lang=es: Glosario - :keywords lang=en: html attributes,array class,array controller,glossary,target blank,dot notation,routing configuration,forgery,replay,router,syntax,config,submissions diff --git a/es/appendices/migration-guides.rst b/es/appendices/migration-guides.rst deleted file mode 100644 index ed157548e7..0000000000 --- a/es/appendices/migration-guides.rst +++ /dev/null @@ -1,16 +0,0 @@ -Guías de migración -################## - -Las guías de migración contienen información relativa a las nuevas funcionalidades introducidas en -cada versión y la manera de migrar entre 4.x y 5.x - -.. toctree:: - :maxdepth: 1 - - ./5-0-upgrade-guide - ./5-0-migration-guide - ./phpunit10 - -.. meta:: - :title lang=es: Guías de migración - :keywords lang=es: maintenance branch,community interaction,community feature,necessary feature,stable release,ticket system,advanced feature,power users,feature set,chat irc,leading edge,router,new features,members,attempt,development branches,branch development diff --git a/es/appendices/phpunit10.rst b/es/appendices/phpunit10.rst deleted file mode 100644 index eb07323054..0000000000 --- a/es/appendices/phpunit10.rst +++ /dev/null @@ -1,69 +0,0 @@ -Actualización a PHPUnit 10 -########################## - -Con CakePHP 5 la version mínima de PHPUnit ha cambiado de ``^8.5 || ^9.3`` a ``^10.1``. -Esto introduce algunos cambios importantes tanto por parte de PHPUnit como por parte de CakePHP. - -Ajustes de phpunit.xml -====================== - -Se recomienda dejar que PHPUnit actualice su archivo de configuración a través del siguiente comando:: - - vendor/bin/phpunit --migrate-configuration - -.. note:: - - ¡Asegúrese de que ya está en PHPUnit 10 a través de ``vendor/bin/phpunit --version`` antes de ejecutar este comando! - -Una vez hayas ejecutado este comando, tu ``phpunit.xml`` tendrá mayoría de los cambios recomendados. - -Nuevo sistema de eventos ------------------------- - -PHPUnit 10 eliminó el antiguo sistema de hook e introdujo un nuevo `Sistema de eventos -`_ -Lo que requiere que se ajuste el siguiente código en su ``phpunit.xml`` desde:: - - - - - -a:: - - - - - -``->withConsecutive()`` ha sido eliminado -========================================= - -Puedes convertir el metodo ``->withConsecutive()`` eliminado -en una solución provisional que funcione como puede ver aquí:: - - ->withConsecutive(['firstCallArg'], ['secondCallArg']) - -debe convertirse a:: - - ->with( - ...self::withConsecutive(['firstCallArg'], ['secondCallArg']) - ) - -se ha añadido el método estático ``self::withConsecutive()`` a través del método ``Cake\TestSuite\PHPUnitConsecutiveTrait`` -a la clase base ``Cake\TestSuite\TestCase`` para que no tenga que agregar manualmente este trait a tus clases de TestCase. - -Los proveedores de datos tienen que ser estáticos -================================================= - -Si tus testcases aprovechan la función de proveedor de datos de PHPUnit entonces -tienes que ajustar tus proveedores de datos para que sean estáticos:: - - public function myProvider(): array - -debe convertirse en:: - - public static function myProvider(): array - - -.. meta:: - :title lang=es: Actualización a PHPUnit 10 - :keywords lang=es: maintenance branch,community interaction,community feature,necessary feature,stable release,ticket system,advanced feature,power users,feature set,chat irc,leading edge,router,new features,members,attempt,development branches,branch development diff --git a/es/bake.rst b/es/bake.rst deleted file mode 100644 index 73c8e9696d..0000000000 --- a/es/bake.rst +++ /dev/null @@ -1,4 +0,0 @@ -Consola bake -############ - -Esta página se ha `movido `__. diff --git a/es/bake/development.rst b/es/bake/development.rst deleted file mode 100644 index e1a57d1f9b..0000000000 --- a/es/bake/development.rst +++ /dev/null @@ -1,17 +0,0 @@ -Extending Bake -############## - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. - -.. _creating-a-bake-theme: - -.. meta:: - :title lang=es: Extending Bake - :keywords lang=es: command line interface,development,bake view, bake template syntax,erb tags,asp tags,percent tags diff --git a/es/bake/usage.rst b/es/bake/usage.rst deleted file mode 100644 index f8b37225ef..0000000000 --- a/es/bake/usage.rst +++ /dev/null @@ -1,4 +0,0 @@ -Crear código con Bake -##################### - -Esta página se ha `movido `__. diff --git a/es/chronos.rst b/es/chronos.rst deleted file mode 100644 index 65772e58dd..0000000000 --- a/es/chronos.rst +++ /dev/null @@ -1,11 +0,0 @@ -Chronos -======= - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. diff --git a/es/console-commands.rst b/es/console-commands.rst deleted file mode 100644 index a7b47f1436..0000000000 --- a/es/console-commands.rst +++ /dev/null @@ -1,176 +0,0 @@ -Comandos de Consola -################### - -.. php:namespace:: Cake\Console - -Además de ser un `framework` web, CakePHP también ofrece un `framework` -de consola para crear herramientas y aplicaciones de línea de comandos. Las aplicaciones -de consola son ideales para gestionar una variedad de tareas de mantenimiento -que aprovechan la configuración existente de tu aplicación, modelos, complementos y lógica de dominio. - -CakePHP proporciona varias herramientas de consola para interactuar con sus características, como i18n -y enrutamiento, lo que te permite inspeccionar tu aplicación y generar archivos relacionados. - -La Consola CakePHP -=================== - -La Console CakePHP utiliza un sistema de tipo `dispatcher` para cargar comandos, analizar sus -argumentos e invocar el comando correcto. Aunque los ejemplos a continuación usan bash, el -console de CakePHP es compatible con cualquier shell de Unix (\*nix) y Windows. - -Una aplicación CakePHP tiene un directorio **src/Command** que contiene sus comandos. También incluye -un ejecutable en el directorio **bin** - -.. code-block:: console - - $ cd /path/to/app - $ bin/cake - -.. note:: - - Para Windows, el comando es ``bin\cake`` (note el `backslash`) - -Ejecutar la console sin argumentos listará todos los comandos disponibles. Tú -puedes, de esta manera, ejecutar cualquiera de los comandos listados usando su nombre: - -.. code-block:: console - - # ejecutar el comando server - bin/cake server - - # ejecutar el comando migrations - bin/cake migrations -h - - # ejecutar bake (con un prefijo de `plugin`) - bin/cake bake.bake -h - -Los comandos de los `plugins` pueden ser invocados sin un prefijo de `plugin` si el nombre del -comando no coincide con un comando de la aplicación o del `framework`. En el caso de que dos `plugins` -proporcionen un comando con el mismo nombre, el `plugin` que se ha cargado primero obtendrá el alias corto. Siempre -puedes utilizar el formato ``plugin.command`` para hacer referencia de manera inequívoca a un comando. - - -Aplicaciones de Consola -======================= - -Por defecto, CakePHP descubrirá automáticamente todos los comandos en tu aplicación y sus complementos. Puede que -desees reducir el número de comandos expuestos al construir aplicaciones de consola independientes. Puedes utilizar -el método ``console()`` de tu clase ``Application`` para limitar qué comandos se exponen y renombrar los comandos que se exponen:: - - // en src/Application.php - namespace App; - - use App\Command\UserCommand; - use App\Command\VersionCommand; - use Cake\Console\CommandCollection; - use Cake\Http\BaseApplication; - - class Application extends BaseApplication - { - public function console(CommandCollection $commands): CommandCollection - { - // Agregar por clase - $commands->add('user', UserCommand::class); - - // Agregar instancia - $commands->add('version', new VersionCommand()); - - return $commands; - } - } - -En el ejemplo anterior, los únicos comandos disponibles serían ``help``, ``version`` y ``user``. -Revisa la sección :ref:`plugin-commands` sobre como agregar comandos en los `plugins`. - -.. note:: - - Cuando agregas múltiples comandos que usan la misma clase, el comando ``help`` mostrará la opción más corta. - -.. _renaming-commands: -.. index:: nested commands, subcommands - -Renombrando Comandos -==================== - -Hay casos en los cuales querrás renombrar comandos para crear comandos anidados o subcomandos. Mientras que -el descubrimiento automático de comandos no hará esto, tu pueds registrar tus comandos para darles el nombre -que desees. - -Puedes personalizar los nombre de los comandos definiéndolo en tu método ``console()``:: - - public function console(CommandCollection $commands): CommandCollection - { - // Agregar comandos anidados (subcomandos) - $commands->add('user dump', UserDumpCommand::class); - $commands->add('user:show', UserShowCommand::class); - - // Renombrar un comando completamente - $commands->add('lazer', UserDeleteCommand::class); - - return $commands; - } - -Cuando utilizas el método ``console()`` en tu aplicación, recuerda llamar -``$commands->autoDiscover()`` para agregar los comandos de CakePHP, de tu aplicación y -de tus `plugins`. - -Si necesitas renombrar/eliminar cualquier comando disponible, puedes usar el evento ``Console.buildCommands`` en -tu manejador de eventos para modificarlos. - -Comandos -======== - -Echa un vistazo al capítulo :doc:`/console-commands/commands` sobre como crear tu primer -comando. Luego aprende más sobre comandos. - -.. toctree:: - :maxdepth: 1 - - console-commands/commands - console-commands/input-output - console-commands/option-parsers - console-commands/cron-jobs - -Comandos provistos por CakePHP -============================== - -.. toctree:: - :maxdepth: 1 - - console-commands/cache - console-commands/completion - console-commands/i18n - console-commands/plugin - console-commands/schema-cache - console-commands/routes - console-commands/server - console-commands/repl - -Enrutando en el ambiente de consola -=================================== - -In command-line interface (CLI), specifically your console commands, -``env('HTTP_HOST')`` and other webbrowser specific environment variables are not -set. - -If you generate reports or send emails that make use of ``Router::url()`` those -will contain the default host ``http://localhost/`` and thus resulting in -invalid URLs. In this case you need to specify the domain manually. -You can do that using the Configure value ``App.fullBaseUrl`` from your -bootstrap or config, for example. - -For sending emails, you should provide Email class with the host you want to -send the email with:: - - use Cake\Mailer\Email; - - $email = new Email(); - $email->setDomain('www.example.org'); - -This asserts that the generated message IDs are valid and fit to the domain the -emails are sent from. - - -.. meta:: - :title lang=es: Comandos, Tareas & Herramientas de Consola - :keywords lang=es: shell scripts,system shell,application classes,background tasks,line script,cron job,request response,system path,acl,new projects,commands,specifics,parameters,i18n,cakephp,directory,maintenance,ideal,applications,mvc diff --git a/es/console-commands/cache.rst b/es/console-commands/cache.rst deleted file mode 100644 index a44a002be6..0000000000 --- a/es/console-commands/cache.rst +++ /dev/null @@ -1,14 +0,0 @@ -Herramienta de caché -#################### - -Para ayudarlo a administrar mejor los datos almacenados en caché desde un entorno CLI, un comando de consola -está disponible para borrar los datos almacenados en caché que tiene su aplicación:: - - // Borrar una configuración de caché - bin/cake cache clear - - // Borrar todas las configuraciones de caché - bin/cake cache clear_all - - // Borrar un grupo de caché - bin/cake cache clear_group diff --git a/es/console-commands/commands.rst b/es/console-commands/commands.rst deleted file mode 100644 index 480123ad83..0000000000 --- a/es/console-commands/commands.rst +++ /dev/null @@ -1,548 +0,0 @@ -Objetos de comando -################## - -.. php:namespace:: Cake\Console -.. php:class:: Command - -CakePHP viene con una serie de comandos integrados para acelerar tu desarrollo y automatización de tareas rutinarias. -Puede utilizar estas mismas bibliotecas para crear comandos para su aplicación y complementos. - -Creando un comando -================== - -Creemos nuestro primer comando. Para este ejemplo, crearemos un comando simple Hola mundo. En el directorio -**src/Command** de su aplicación, cree **HelloCommand.php**. Coloca el siguiente código dentro:: - - out('Hello world.'); - - return static::CODE_SUCCESS; - } - } - -Las clases de comando deben implementar un método ``execute()`` que haga la mayor parte del trabajo. Este método se -llama cuando se invoca un comando. Llamemos a nuestro primer comando de aplicación, ejecute:: - -.. code-block:: console - - bin/cake hello - -Debería ver el siguiente resultado:: - - Hello world. - -Nuestro método ``execute()`` no es muy interesante, leamos algunas entradas desde la línea de comando:: - - addArgument('name', [ - 'help' => 'What is your name', - ]); - - return $parser; - } - - public function execute(Arguments $args, ConsoleIo $io): int - { - $name = $args->getArgument('name'); - $io->out("Hello {$name}."); - - return static::CODE_SUCCESS; - } - } - - -Después de guardar este archivo, debería poder ejecutar el siguiente comando:: - -.. code-block:: console - - bin/cake hello jillian - - # Outputs - Hello jillian - -Cambiar el nombre del comando predeterminado -============================================ - -CakePHP usará convenciones para generar el nombre que usan sus comandos en la línea de comando. Si desea sobrescribir -el nombre generado, implemente el método ``defaultName()`` en tu comando:: - - public static function defaultName(): string - { - return 'oh_hi'; - } - -Lo anterior haría que nuestro ``HelloCommand`` fuera accesible mediante ``cake oh_hi`` en lugar de ``cake hello``. - -Definición de argumentos y opciones -=================================== - -Como vimos en el último ejemplo, podemos usar el método ``buildOptionParser()`` para definir argumentos. También -podemos definir opciones. Por ejemplo, podríamos agregar una opción ``yell`` a nuestro ``HelloCommand``:: - - // ... - protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser - { - $parser - ->addArgument('name', [ - 'help' => 'What is your name', - ]) - ->addOption('yell', [ - 'help' => 'Shout the name', - 'boolean' => true, - ]); - - return $parser; - } - - public function execute(Arguments $args, ConsoleIo $io): int - { - $name = $args->getArgument('name'); - if ($args->getOption('yell')) { - $name = mb_strtoupper($name); - } - $io->out("Hello {$name}."); - - return static::CODE_SUCCESS; - } - -Consulte la sección :doc:`/console-commands/option-parsers` para obtener más información. - -Creando la salida -================= - -Los comandos proporcionan la instancia ``ConsoleIo`` cuando se ejecutan. Este objeto le permite interactuar con -``stdout``, ``stderr`` y crear archivos. Consulte la sección :doc:`/console-commands/input-output` para obtener -más información. - -Usar modelos en comandos -======================== - -Utilice modelos en comandos. A menudo necesitará acceso a la lógica de negocio de su aplicación en los comandos -de la consola. Puede cargar modelos en comandos, tal como lo haría en un controlador usando ``$this->fetchTable()`` -ya que el comando usa ``LocatorAwareTrait``:: - - addArgument('name', [ - 'help' => 'What is your name' - ]); - - return $parser; - } - - public function execute(Arguments $args, ConsoleIo $io): int - { - $name = $args->getArgument('name'); - $user = $this->fetchTable()->findByUsername($name)->first(); - - $io->out(print_r($user, true)); - - return static::CODE_SUCCESS; - } - } - -El comando anterior buscará un usuario por nombre de usuario y mostrará la información almacenada en la base de datos. - -Códigos de salida y detención de la ejecución -============================================= - -Cuando sus comandos alcanzan un error irrecuperable, puede utilizar el método ``abort()`` para finalizar la ejecución:: - - // ... - public function execute(Arguments $args, ConsoleIo $io): int - { - $name = $args->getArgument('name'); - if (strlen($name) < 5) { - // Detener la ejecución, enviar a stderr y establecer el código de salida en 1 - $io->error('Name must be at least 4 characters long.'); - $this->abort(); - } - - return static::CODE_SUCCESS; - } - -También puedes usar ``abort()`` en el objeto ``$io`` para emitir un mensaje y código:: - - public function execute(Arguments $args, ConsoleIo $io): int - { - $name = $args->getArgument('name'); - if (strlen($name) < 5) { - // Detener la ejecución, enviar a stderr y establecer el código de salida en 99 - $io->abort('Name must be at least 4 characters long.', 99); - } - - return static::CODE_SUCCESS; - } - -Puede pasar cualquier código de salida que desee a ``abort()``. - -.. tip:: - - Evite los códigos de salida 64 - 78, ya que tienen significados específicos descritos por ``sysexits.h``. - Evite los códigos de salida superiores a 127, ya que se utilizan para indicar la salida del proceso mediante - una señal, como SIGKILL o SIGSEGV. - - Puede leer más sobre los códigos de salida convencionales en la página del manual de sysexit en la mayoría de - los sistemas Unix (``man sysexits``), o en la página de ayuda ``Códigos de error del sistema`` en Windows. - -Llamar a otros comandos -======================== - -Es posible que necesite llamar a otros comandos desde tu comando. Puedes usar ``executeCommand`` para hacer eso:: - - // Puede pasar una variedad de opciones y argumentos de CLI. - $this->executeCommand(OtherCommand::class, ['--verbose', 'deploy']); - - // Puede pasar una instancia del comando si tiene argumentos de constructor - $command = new OtherCommand($otherArgs); - $this->executeCommand($command, ['--verbose', 'deploy']); - -.. note:: - - Al llamar a ``executeCommand()`` en un bucle, se recomienda pasar la instancia ``ConsoleIo`` del comando principal - como tercer argumento opcional para evitar un posible límite de "archivos abiertos" que podría ocurrir en algunos - entornos. - -Configurando descripción del comando -==================================== - -Es posible que desee establecer una descripción de comando a través de:: - - class UserCommand extends Command - { - public static function getDescription(): string - { - return 'My custom description'; - } - } - -Esto mostrará la descripción en Cake CLI: - -.. code-block:: console - - bin/cake - - App: - - user - └─── My custom description - -Así como en la sección de ayuda de tu comando: - -.. code-block:: console - - cake user --help - My custom description - - Usage: - cake user [-h] [-q] [-v] - -.. _console-integration-testing: - -Pruebas de comandos -=================== - -Para facilitar las pruebas de aplicaciones de consola, CakePHP viene con un rasgo (trait) -``ConsoleIntegrationTestTrait`` que puede usarse para probar aplicaciones de consola y comparar sus resultados. - -Para comenzar a probar su aplicación de consola, cree un caso de prueba que utilice el rasgo -``Cake\TestSuite\ConsoleIntegrationTestTrait``. Este rasgo contiene un método ``exec()`` que se utiliza -para ejecutar su comando. Puede pasar la misma cadena que usaría en la CLI a este método. - -.. note:: - - Para CakePHP 4.4 en adelante, se debe utilizar el espacio de nombres - ``Cake\Console\TestSuite\ConsoleIntegrationTestTrait``. - -Comencemos con un comando muy simple, ubicado en **src/Command/UpdateTableCommand.php**:: - - namespace App\Command; - - use Cake\Command\Command; - use Cake\Console\Arguments; - use Cake\Console\ConsoleIo; - use Cake\Console\ConsoleOptionParser; - - class UpdateTableCommand extends Command - { - protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser - { - $parser->setDescription('My cool console app'); - - return $parser; - } - } - -Para escribir una prueba de integración para este comando, crearíamos un caso de prueba en -**tests/TestCase/Command/UpdateTableTest.php** que use el rasgo ``Cake\TestSuite\ConsoleIntegrationTestTrait``. -Este comando no hace mucho por el momento, pero probemos que la descripción de nuestro comando -se muestre en ``stdout``:: - - namespace App\Test\TestCase\Command; - - use Cake\TestSuite\ConsoleIntegrationTestTrait; - use Cake\TestSuite\TestCase; - - class UpdateTableCommandTest extends TestCase - { - use ConsoleIntegrationTestTrait; - - public function testDescriptionOutput() - { - $this->exec('update_table --help'); - $this->assertOutputContains('My cool console app'); - } - } - -¡Nuestra prueba pasa! Si bien este es un ejemplo muy trivial, muestra que la creación de un caso de -prueba de integración para aplicaciones de consola puede seguir las convenciones de la línea de comandos. -Sigamos agregando más lógica a nuestro comando:: - - namespace App\Command; - - use Cake\Command\Command; - use Cake\Console\Arguments; - use Cake\Console\ConsoleIo; - use Cake\Console\ConsoleOptionParser; - use Cake\I18n\DateTime; - - class UpdateTableCommand extends Command - { - protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser - { - $parser - ->setDescription('My cool console app') - ->addArgument('table', [ - 'help' => 'Table to update', - 'required' => true - ]); - - return $parser; - } - - public function execute(Arguments $args, ConsoleIo $io): int - { - $table = $args->getArgument('table'); - $this->fetchTable($table)->updateQuery() - ->set([ - 'modified' => new DateTime() - ]) - ->execute(); - - return static::CODE_SUCCESS; - } - } - -Este es un comando más completo que tiene opciones requeridas y lógica relevante. -Modifique su caso de prueba al siguiente fragmento de código:: - - namespace Cake\Test\TestCase\Command; - - use Cake\Command\Command; - use Cake\I18n\DateTime; - use Cake\TestSuite\ConsoleIntegrationTestTrait; - use Cake\TestSuite\TestCase; - - class UpdateTableCommandTest extends TestCase - { - use ConsoleIntegrationTestTrait; - - protected $fixtures = [ - // Se supone que tienes un UsersFixture - 'app.Users', - ]; - - public function testDescriptionOutput() - { - $this->exec('update_table --help'); - $this->assertOutputContains('My cool console app'); - } - - public function testUpdateModified() - { - $now = new DateTime('2017-01-01 00:00:00'); - DateTime::setTestNow($now); - - $this->loadFixtures('Users'); - - $this->exec('update_table Users'); - $this->assertExitCode(Command::CODE_SUCCESS); - - $user = $this->getTableLocator()->get('Users')->get(1); - $this->assertSame($user->modified->timestamp, $now->timestamp); - - DateTime::setTestNow(null); - } - } - -Como puede ver en el método ``testUpdateModified``, estamos probando que nuestro comando actualice -la tabla que pasamos como primer argumento. Primero, afirmamos que el comando salió con el código -de estado adecuado, ``0``. Luego verificamos que nuestro comando hizo su trabajo, es decir, actualizamos -la tabla que proporcionamos y configuramos la columna ``modificada`` a la hora actual. - -Recuerde, ``exec()`` tomará la misma cadena que escriba en su CLI, por lo que puede incluir -opciones y argumentos en su cadena de comando. - -Prueba de shells interactivos ------------------------------ - -Las consolas suelen ser interactivas. Probar comandos interactivos con el rasgo -``Cake\TestSuite\ConsoleIntegrationTestTrait`` solo requiere pasar las entradas que espera como segundo -parámetro de ``exec()``. Deben incluirse como una matriz en el orden esperado. - -Continuando con nuestro comando de ejemplo, agreguemos una confirmación interactiva. -Actualice la clase de comando a lo siguiente:: - - namespace App\Command; - - use Cake\Command\Command; - use Cake\Console\Arguments; - use Cake\Console\ConsoleIo; - use Cake\Console\ConsoleOptionParser; - use Cake\I18n\DateTime; - - class UpdateTableCommand extends Command - { - protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser - { - $parser - ->setDescription('My cool console app') - ->addArgument('table', [ - 'help' => 'Table to update', - 'required' => true - ]); - - return $parser; - } - - public function execute(Arguments $args, ConsoleIo $io): int - { - $table = $args->getArgument('table'); - if ($io->ask('Are you sure?', 'n', ['y', 'n']) !== 'y') { - $io->error('You need to be sure.'); - $this->abort(); - } - $this->fetchTable($table)->updateQuery() - ->set([ - 'modified' => new DateTime() - ]) - ->execute(); - - return static::CODE_SUCCESS; - } - } - -Ahora que tenemos un subcomando interactivo, podemos agregar un caso de prueba que pruebe que recibimos -la respuesta adecuada y otro que pruebe que recibimos una respuesta incorrecta. -Elimine el método ``testUpdateModified`` y agregue los siguientes métodos a -**tests/TestCase/Command/UpdateTableCommandTest.php**:: - - - public function testUpdateModifiedSure() - { - $now = new DateTime('2017-01-01 00:00:00'); - DateTime::setTestNow($now); - - $this->loadFixtures('Users'); - - $this->exec('update_table Users', ['y']); - $this->assertExitCode(Command::CODE_SUCCESS); - - $user = $this->getTableLocator()->get('Users')->get(1); - $this->assertSame($user->modified->timestamp, $now->timestamp); - - DateTime::setTestNow(null); - } - - public function testUpdateModifiedUnsure() - { - $user = $this->getTableLocator()->get('Users')->get(1); - $original = $user->modified->timestamp; - - $this->exec('my_console best_framework', ['n']); - $this->assertExitCode(Command::CODE_ERROR); - $this->assertErrorContains('You need to be sure.'); - - $user = $this->getTableLocator()->get('Users')->get(1); - $this->assertSame($original, $user->timestamp); - } - -En el primer caso de prueba, confirmamos la pregunta y se actualizan los registros. En la segunda prueba -no confirmamos y los registros no se actualizan, y podemos verificar que nuestro mensaje de error -fue escrito en ``stderr``. - -Metodos de aserción -------------------- - -El rasgo ``Cake\TestSuite\ConsoleIntegrationTestTrait`` proporciona una serie de métodos de aserción -que ayudan a afirmar contra la salida de la consola:: - - // afirmar que el comando salió con éxito - $this->assertExitSuccess(); - - // afirmar que el comando salió como un error - $this->assertExitError(); - - // afirmar que el comando salió con el código esperado - $this->assertExitCode($expected); - - // afirmar que la salida estándar contiene un texto - $this->assertOutputContains($expected); - - // afirmar que stderr contiene una texto - $this->assertErrorContains($expected); - - // afirmar que la salida estándar coincide con una expresión regular - $this->assertOutputRegExp($expected); - - // afirmar que stderr coincide con una expresión regular - $this->assertErrorRegExp($expected); - -Ciclo de vida de las devoluciones de llamada -============================================= - -Al igual que los controladores, los comandos ofrecen eventos de ciclo de vida que le permiten observar -el marco que llama al código de su aplicación. Los comandos tienen: - -- ``Command.beforeExecute`` Se llama antes que el método ``execute()`` de un comando. - Al evento se le pasa el parámetro ``ConsoleArguments`` como ``args``. - Este evento no se puede detener ni reemplazar su resultado. -- ``Command.afterExecute`` Se llama después de que se completa el método ``execute()`` - de un comando. El evento contiene ``ConsoleArguments`` como ``args`` y el resultado - del comando como ``result``. Este evento no se puede detener ni reemplazar su resultado. diff --git a/es/console-commands/completion.rst b/es/console-commands/completion.rst deleted file mode 100644 index 351841f754..0000000000 --- a/es/console-commands/completion.rst +++ /dev/null @@ -1,182 +0,0 @@ -Herramienta de completación -########################### - -Trabajar con la consola le brinda al desarrollador muchas posibilidades, pero tener que conocer y escribir -completamente esos comandos puede resultar tedioso. Especialmente cuando se desarrollan nuevos shells donde -los comandos difieren por minuto de iteración. Completion Shells ayuda en este asunto al proporcionar una -API para escribir scripts de completación para shells como bash, zsh, fish, etc. - -Sub Comandos -============ - -El Shell de completación consta de varios subcomandos para ayudar al desarrollador a crear su script -de finalización. Cada uno para un paso diferente en el proceso de autocompletar. - -Comandos --------- - -Para los comandos del primer paso, se generan los comandos de Shell disponibles, incluido el nombre del -complemento cuando corresponda. (Todas las posibilidades devueltas, para este y otros subcomandos, están -separadas por un espacio). Por ejemplo:: - - bin/cake Completion commands - -Regresará:: - - acl api bake command_list completion console i18n schema server test testsuite upgrade - -Su secuencia de comandos de completación puede seleccionar los comandos relevantes de esa lista para continuar. -(Para este y los siguientes subcomandos). - -subcomandos ------------ - -Una vez que se ha elegido el comando preferido, los subCommands entran como segundo paso y generan -el posible subcomando para el comando de shell dado. Por ejemplo:: - - bin/cake Completion subcommands bake - -Regresará:: - - controller db_config fixture model plugin project test view - -opciones --------- - -Como tercera y última opción, genera opciones para el (sub)comando dado, tal como se establece en ``getOptionParser``. -(Incluidas las opciones predeterminadas heredadas de Shell). -Por ejemplo:: - - bin/cake Completion options bake - -Regresará:: - - --help -h --verbose -v --quiet -q --everything --connection -c --force -f --plugin -p --prefix --theme -t - -También puede pasar un argumento adicional que sea el subcomando del shell: generará las opciones -específicas de este subcomando. - -Cómo habilitar el autocompletado de Bash para la consola CakePHP -================================================================ - -Primero, asegúrese de que la biblioteca **bash-completion** esté instalada. -Si no, lo haces con el siguiente comando:: - - apt-get install bash-completion - -Cree un archivo llamado **cake** en **/etc/bash_completion.d/** y coloque el -:ref:`bash-completion-file-content` dentro de él. - -Guarde el archivo y luego reinicie su consola. - -.. note:: - - Si está utilizando MacOS X, puede instalar la biblioteca **bash-completion** usando **homebrew** - con el comando ``brew install bash-completion``. - El directorio de destino para el archivo **cake** será **/usr/local/etc/bash_completion.d/**. - -.. _bash-completion-file-content: - -Contenido del archivo de completación de Bash ----------------------------------------------- - -Este es el código que debes colocar dentro del archivo **cake** en la ubicación correcta para obtener el autocompletado al usar la consola CakePHP: - -.. code-block:: bash - - # - # Bash completion file for CakePHP console - # - - _cake() - { - local cur prev opts cake - COMPREPLY=() - cake="${COMP_WORDS[0]}" - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" - - if [[ "$cur" == -* ]] ; then - if [[ ${COMP_CWORD} = 1 ]] ; then - opts=$(${cake} Completion options) - elif [[ ${COMP_CWORD} = 2 ]] ; then - opts=$(${cake} Completion options "${COMP_WORDS[1]}") - else - opts=$(${cake} Completion options "${COMP_WORDS[1]}" "${COMP_WORDS[2]}") - fi - - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - return 0 - fi - - if [[ ${COMP_CWORD} = 1 ]] ; then - opts=$(${cake} Completion commands) - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - return 0 - fi - - if [[ ${COMP_CWORD} = 2 ]] ; then - opts=$(${cake} Completion subcommands $prev) - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - if [[ $COMPREPLY = "" ]] ; then - _filedir - return 0 - fi - return 0 - fi - - opts=$(${cake} Completion fuzzy "${COMP_WORDS[@]:1}") - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - if [[ $COMPREPLY = "" ]] ; then - _filedir - return 0 - fi - return 0; - } - - complete -F _cake cake bin/cake - -Usando el autocompletado -======================== - -Una vez habilitado, el autocompletado se puede usar de la misma manera que para otros comandos integrados, -usando la tecla **TAB**. -Se proporcionan tres tipos de autocompletado. El siguiente resultado proviene de una nueva instalación de CakePHP. - -Comandos --------- - -Salida de muestra para comandos de autocompletar: - -.. code-block:: console - - $ bin/cake - bake i18n schema_cache routes - console migrations plugin server - -Subcomandos ------------ - -Salida de muestra para el autocompletado de subcomandos: - -.. code-block:: console - - $ bin/cake bake - behavior helper command - cell mailer command_helper - component migration template - controller migration_snapshot test - fixture model - form plugin - -Opciones --------- - -Salida de muestra para el autocompletado de opciones de subcomandos: - -.. code-block:: console - - $ bin/cake bake - - -c --everything --force --help --plugin -q -t -v - --connection -f -h -p --prefix --quiet --theme --verbose - diff --git a/es/console-commands/cron-jobs.rst b/es/console-commands/cron-jobs.rst deleted file mode 100644 index e521ca01bc..0000000000 --- a/es/console-commands/cron-jobs.rst +++ /dev/null @@ -1,49 +0,0 @@ -Ejecutar shells como trabajos cron -################################## - -Una cosa común que se puede hacer con un shell es ejecutarlo como un cronjob para limpiar la base de datos -de vez en cuando o enviar boletines. Esto es trivial de configurar, por ejemplo:: - - */5 * * * * cd /full/path/to/root && bin/cake myshell myparam - # * * * * * command to execute - # │ │ │ │ │ - # │ │ │ │ │ - # │ │ │ │ \───── day of week (0 - 6) (0 to 6 are Sunday to Saturday, - # | | | | or use names) - # │ │ │ \────────── month (1 - 12) - # │ │ \─────────────── day of month (1 - 31) - # │ \──────────────────── hour (0 - 23) - # \───────────────────────── min (0 - 59) - -Puedes ver más información aquí: https://es.wikipedia.org/wiki/Cron_(Unix) - -.. tip:: - - Utilice ``-q`` (o `--quiet`) para silenciar cualquier salida de cronjobs. - -Trabajos cron en hosting compartido ------------------------------------ - -En algunos servidores compartidos ``cd /full/path/to/root && bin/cake mycommand myparam`` -Puede que no funcione. En su lugar puedes usar -``php /full/path/to/root/bin/cake.php mycomando myparam``. - -.. note:: - - register_argc_argv has to be turned on by including ``register_argc_argv - = 1`` in your php.ini. If you cannot change register_argc_argv globally, - you can tell the cron job to use your own configuration by - specifying it with ``-d register_argc_argv=1`` parameter. Example: ``php - -d register_argc_argv=1 /full/path/to/root/bin/cake.php myshell - myparam`` - - register_argc_argv debe activarse incluyendo ``register_argc_argv = 1`` - en su php.ini. Si no puede cambiar register_argc_argv globalmente, puede - indicarle al trabajo cron que use su propia configuración especificándola - con el parámetro ``-d register_argc_argv=1``. - Ejemplo: ``php -d register_argc_argv=1 /full/path/to/root/bin/cake.php myshell - myparam`` - -.. meta:: - :title lang=es: Ejecutar shells como trabajos cron - :keywords lang=es: cronjob,bash script,crontab diff --git a/es/console-commands/i18n.rst b/es/console-commands/i18n.rst deleted file mode 100644 index 1710c4a546..0000000000 --- a/es/console-commands/i18n.rst +++ /dev/null @@ -1,98 +0,0 @@ -Herramienta de internacionalización (i18n) -########################################## - -Las características i18n de CakePHP usan `archivos po `_ -como fuente de traducción. Los archivos PO se integran con herramientas de traducción de uso común -como `Poedit `_. - -Los comandos i18n proporcionan una forma rápida de generar archivos de plantilla po. -Estos archivos de plantilla luego se pueden entregar a los traductores para que puedan -traducir los textos en su aplicación. Una vez que haya terminado las traducciones, -los archivos pueden ser fusionados con traducciones existentes para ayudar a actualizar sus traducciones. - -Generando archivos POT -====================== - -POT files can be generated for an existing application using the ``extract`` -command. This command will scan your entire application for ``__()`` style -function calls, and extract the message string. Each unique string in your -application will be combined into a single POT file: - -Los archivos POT se pueden generar para una aplicación existente usando el -comando ``extract``. Este comando escaneará toda su aplicación en busca de -llamadas a funciones de estilo ``__()`` y extraerá la cadena del mensaje. -Cada cadena única en su aplicación se combinará en un único archivo POT: - -.. code-block:: console - - bin/cake i18n extract - -Lo anterior ejecutará el comando de extracción. El resultado de este comando será -el archivo **resources/locales/default.pot**. Utilice el archivo pot como plantilla -para crear archivos po. Si está creando archivos po manualmente a partir del -archivo pot, asegúrese de configurar correctamente la línea de encabezado ``Plural-Forms``. - -Generando archivos POT para complementos ------------------------------------------ - -Puede generar un archivo POT para un complemento específico usando: - -.. code-block:: console - - bin/cake i18n extract --plugin - -Esto generará los archivos POT necesarios utilizados en los complementos. - -Extraer de varias carpetas a la vez ------------------------------------ - -A veces, es posible que necesites extraer textos de más de un directorio de tu -aplicación. Por ejemplo, si está definiendo algunas cadenas en el directorio -``config`` de su aplicación, probablemente desee extraer textos de este directorio -así como del directorio ``src``. Puedes hacerlo usando la opción ``--paths``. -Se necesita una lista de rutas absolutas separadas por comas para extraer: - -.. code-block:: console - - bin/cake i18n extract --paths /var/www/app/config,/var/www/app/src - -Excluyendo carpetas -------------------- - -Puede pasar una lista separada por comas de las carpetas que desea excluir. -Se ignorará cualquier ruta que contenga un segmento de ruta con los valores -proporcionados: - -.. code-block:: console - - bin/cake i18n extract --exclude vendor,tests - -Omitir advertencias de sobrescritura para archivos POT existentes ------------------------------------------------------------------ - -Al agregar ``--overwrite``, el script de shell ya no le advertirá si ya existe -un archivo POT y lo sobrescribirá de forma predeterminada: - -.. code-block:: console - - bin/cake i18n extract --overwrite - -Extracción de mensajes de las bibliotecas principales de CakePHP ----------------------------------------------------------------- - -De forma predeterminada, el script de extracción le preguntará si desea extraer -los mensajes utilizados en el código de CakePHP. -Configura ``--extract-core`` en ``yes`` o ``no`` para establecer el comportamiento -predeterminado: - -.. code-block:: console - - bin/cake i18n extract --extract-core yes - - // or - - bin/cake i18n extract --extract-core no - -.. meta:: - :title lang=es: Herramienta de internacionalización (i18n) - :keywords lang=es: pot files,locale default,translation tools,message string,app locale,php class,validation,i18n,translations,command,models diff --git a/es/console-commands/input-output.rst b/es/console-commands/input-output.rst deleted file mode 100644 index 685bef35ab..0000000000 --- a/es/console-commands/input-output.rst +++ /dev/null @@ -1,369 +0,0 @@ -Comandos Entrada/Salida (Input/Output) -###################################### - -.. php:namespace:: Cake\Console -.. php:class:: ConsoleIo - -CakePHP proporciona el objeto ``ConsoleIo`` a los comandos para que puedan -leer interactivamente la información de entrada y salida del usuario. - -.. _command-helpers: - -Ayudantes de comando (Helpers) -============================== - -Se puede acceder y utilizar los ayudantes (helpers) de comandos desde cualquier comando:: - - // Generar algunos datos como una tabla.. - $io->helper('Table')->output($data); - - // Obtenga una ayuda de un complemento. - $io->helper('Plugin.HelperName')->output($data); - -También puede obtener instancias de ayudantes y llamar a cualquier método público sobre ellos:: - - // Obtenga y utilice Progress Helper. - $progress = $io->helper('Progress'); - $progress->increment(10); - $progress->draw(); - -Creando ayudantes -================= - -Si bien CakePHP viene con algunos comandos auxiliares, puedes crear más en tu -aplicación o complementos. Como ejemplo, crearemos un asistente simple para -generar encabezados sofisticados. Primero cree -**src/Command/Helper/HeadingHelper.php** y coloque lo siguiente en él:: - - _io->out($marker . ' ' . $args[0] . ' ' . $marker); - } - } - -Luego podemos usar este nuevo asistente en uno de nuestros comandos de -shell llamándolo:: - - // Con ### a cada lado - $this->helper('Heading')->output(['It works!']); - - // Con ~~~~ a cada lado - $this->helper('Heading')->output(['It works!', '~', 4]); - -Los ayudantes generalmente implementan el método ``output()`` que toma una serie -de parámetros. Sin embargo, debido a que los Console Helpers son clases básicas, -pueden implementar métodos adicionales que toman cualquier forma de argumentos. - -.. note:: - Los ayudantes también pueden vivir en ``src/Shell/Helper`` para - compatibilidad con versiones anteriores. - -Ayudantes incorporados -====================== - -Table Helper ------------- - -TableHelper ayuda a crear tablas artísticas ASCII bien formateadas. -Usarlo es bastante simple:: - - $data = [ - ['Header 1', 'Header', 'Long Header'], - ['short', 'Longish thing', 'short'], - ['Longer thing', 'short', 'Longest Value'], - ]; - $io->helper('Table')->output($data); - - // Outputs - +--------------+---------------+---------------+ - | Header 1 | Header | Long Header | - +--------------+---------------+---------------+ - | short | Longish thing | short | - | Longer thing | short | Longest Value | - +--------------+---------------+---------------+ - -Puede utilizar la etiqueta de formato ```` en las tablas para -alinear el contenido a la derecha:: - - $data = [ - ['Name', 'Total Price'], - ['Cake Mix', '1.50'], - ]; - $io->helper('Table')->output($data); - - // Outputs - +----------+-------------+ - | Name 1 | Total Price | - +----------+-------------+ - | Cake Mix | 1.50 | - +----------+-------------+ - -Progress Helper ---------------- - -ProgressHelper se puede utilizar de dos maneras diferentes. El modo simple -le permite proporcionar una devolución de llamada que se invoca hasta que -se completa el progreso:: - - $io->helper('Progress')->output(['callback' => function ($progress) { - // Funciona aqui - $progress->increment(20); - $progress->draw(); - }]); - -Puede controlar más la barra de progreso proporcionando opciones adicionales: - -- ``total`` El número total de elementos en la barra de progreso. - El valor predeterminado es 100. -- ``width`` El ancho de la barra de progreso. El valor predeterminado es 80. -- ``callback`` La devolución de llamada que se llamará en un bucle para avanzar - en la barra de progreso. - -Un ejemplo de todas las opciones en uso sería:: - - $io->helper('Progress')->output([ - 'total' => 10, - 'width' => 20, - 'callback' => function ($progress) { - $progress->increment(2); - $progress->draw(); - } - ]); - -El asistente de progreso también se puede utilizar manualmente para incrementar -y volver a representar la barra de progreso según sea necesario:: - - $progress = $io->helper('Progress'); - $progress->init([ - 'total' => 10, - 'width' => 20, - ]); - - $progress->increment(4); - $progress->draw(); - - -Obtener información del usuario -=============================== - -.. php:method:: ask($question, $choices = null, $default = null) - -Al crear aplicaciones de consola interactivas, necesitará obtener información -del usuario. CakePHP proporciona una manera de hacer esto:: - - // Obtenga texto arbitrario del usuario. - $color = $io->ask('What color do you like?'); - - // Obtenga una opción del usuario. - $selection = $io->askChoice('Red or Green?', ['R', 'G'], 'R'); - -La validación de la selección no distingue entre mayúsculas y minúsculas. - -Creando archivos -================ - -.. php:method:: createFile($path, $contents) - -La creación de archivos suele ser una parte importante de muchos comandos de -consola que ayudan a automatizar el desarrollo y la implementación. -El método ``createFile()`` le brinda una interfaz simple para crear archivos -con confirmación interactiva:: - - // Crear un archivo con confirmación de sobrescritura - $io->createFile('bower.json', $stuff); - - // Forzar sobrescritura sin preguntar - $io->createFile('bower.json', $stuff, true); - -Creando salidas (Output) -======================== - -.. php:method:out($message, $newlines, $level) -.. php:method:err($message, $newlines) - -Escribir en ``stdout`` y ``stderr`` es otra operación común en CakePHP:: - - // Escribir a stdout - $io->out('Normal message'); - - // Escribir a stderr - $io->err('Error message'); - -Además de los métodos de salida básicos, CakePHP proporciona métodos envolventes -que diseñan la salida con colores ANSI apropiados: - - // Texto verde en stdout - $io->success('Success message'); - - // Texto cian en stdout - $io->info('Informational text'); - - // Texto azul en stdout - $io->comment('Additional context'); - - // Texto rojo en stderr - $io->error('Error text'); - - // Texto amarillo en stderr - $io->warning('Warning text'); - -El formato de color se desactivará automáticamente si ``posix_isatty`` devuelve -verdadero o si se establece la variable de entorno ``NO_COLOR``. - -``ConsoleIo`` proporciona dos métodos convenientes con respecto al nivel -de salida:: - - // Solo aparecerá cuando la salida detallada esté habilitada (-v) - $io->verbose('Verbose message'); - - // Aparecería en todos los niveles. - $io->quiet('Quiet message'); - -También puedes crear líneas en blanco o dibujar líneas de guiones:: - - // Salida 2 nuevas líneas - $io->out($io->nl(2)); - - // Dibuja una línea horizontal - $io->hr(); - -Por último, puede actualizar la línea de texto actual en la pantalla:: - - $io->out('Counting down'); - $io->out('10', 0); - for ($i = 9; $i > 0; $i--) { - sleep(1); - $io->overwrite($i, 0, 2); - } - -.. note:: - Es importante recordar que no puede sobrescribir texto una vez - que se ha generado una nueva línea. - -.. _shell-output-level: - -Niveles de salida -================= - -Las aplicaciones de consola a menudo necesitan diferentes niveles de detalle. -Por ejemplo, cuando se ejecuta como una tarea cron, la mayor parte del resultado -es innecesario. Puede utilizar niveles de salida para marcar la salida de forma -adecuada. El usuario del shell puede entonces decidir qué nivel de detalle le -interesa configurando el indicador correcto al llamar al comando. Hay 3 niveles: - -* ``QUIET`` - Sólo la información absolutamente importante debe marcarse para - una salida silenciosa. -* ``NORMAL`` - El nivel predeterminado y el uso normal. -* ``VERBOSE`` - Marque los mensajes que pueden ser demasiado ruidosos para el - uso diario, pero útiles para la depuración como ``VERBOSE``. - -Puede marcar la salida de la siguiente manera:: - - // Aparecería en todos los niveles. - $io->out('Quiet message', 1, ConsoleIo::QUIET); - $io->quiet('Quiet message'); - - // No aparecería cuando se alterna la salida silenciosa. - $io->out('normal message', 1, ConsoleIo::NORMAL); - $io->out('loud message', 1, ConsoleIo::VERBOSE); - $io->verbose('Verbose output'); - - // Solo aparecería cuando la salida detallada esté habilitada. - $io->out('extra message', 1, ConsoleIo::VERBOSE); - $io->verbose('Verbose output'); - -Puede controlar el nivel de salida de los comandos utilizando las opciones -``--quiet`` y ``--verbose``. Estas opciones se agregan de forma predeterminada -y le permiten controlar consistentemente los niveles de salida dentro de sus -comandos de CakePHP. - -Las opciones ``--quiet`` y ``--verbose`` también controlan cómo se envían los -datos de registro a stdout/stderr. Normalmente, la información y los mensajes de -registro superiores se envían a stdout/stderr. Cuando se utiliza ``--verbose``, -los registros de depuración se enviarán a la salida estándar. Cuando se usa -``--quiet``, solo se enviarán a stderr mensajes de advertencia y de registro -superiores. - -Estilos en salidas -================== - -El estilo de la salida se logra incluyendo etiquetas, al igual que HTML, en la -salida. Estas etiquetas se reemplazarán con la secuencia de códigos ansi -correcta o se eliminarán si estás en una consola que no admite códigos ansi. -Hay varios estilos integrados y puedes crear más. Los incorporados son - -* ``success`` Mensajes de éxito. Texto verde. -* ``error`` Mensajes de error. Texto rojo. -* ``warning`` Mensajes de advertencia. Texto amarillo. -* ``info`` Mensajes informativos. Texto cian. -* ``comment`` Texto adicional. Texto azul. -* ``question`` Texto que es una pregunta, agregado automáticamente por shell. - -Puede crear estilos adicionales usando ``$io->setStyle()``. Para declarar un -nuevo estilo de salida que podrías hacer:: - - $io->setStyle('flashy', ['text' => 'magenta', 'blink' => true]); - -This would then allow you to use a ```` tag in your shell output, and if -ansi colours are enabled, the following would be rendered as blinking magenta -text ``$this->out('Whoooa Something went wrong');``. When -defining styles you can use the following colours for the ``text`` and -``background`` attributes: - -Esto le permitiría usar una etiqueta ```` en la salida de su shell, y si -los colores ansi están habilitados, lo siguiente se representaría como texto -magenta parpadeante ``$this->out('Whoooa Algo salió mal');``. -Al definir estilos, puede utilizar los siguientes colores para los atributos -``text`` y ``background``: - -* black -* blue -* cyan -* green -* magenta -* red -* white -* yellow - -También puede utilizar las siguientes opciones como modificadores booleanos, -configurarlas en un valor verdadero las habilita. - -* blink -* bold -* reverse -* underline - -Agregar un estilo también lo hace disponible en todas las instancias de -ConsoleOutput, por lo que no es necesario volver a declarar estilos para los -objetos stdout y stderr. - -Desactivar la coloración -========================= - -Aunque el color es bonito, puede haber ocasiones en las que desees apagarlo o forzarlo: - - $io->outputAs(ConsoleOutput::RAW); - -Lo anterior pondrá el objeto de salida en modo de salida sin formato. En el modo -de salida sin formato, no se aplica ningún estilo. Hay tres modos que puedes -usar. - -* ``ConsoleOutput::COLOR`` - Salida con códigos de escape de color implementados. -* ``ConsoleOutput::PLAIN`` - Salida de texto sin formato, las etiquetas de - estilo conocidas se eliminarán de la salida. -* ``ConsoleOutput::RAW`` - Salida sin formato, no se realizará ningún estilo ni - formato. Este es un buen modo para usar si está generando XML o desea depurar - por qué su estilo no funciona. - -De forma predeterminada, en los sistemas \*nix, los objetos ConsoleOutput tienen -una salida de color predeterminada. En los sistemas Windows, la salida simple es -la predeterminada a menos que la variable de entorno ``ANSICON`` esté presente. diff --git a/es/console-commands/option-parsers.rst b/es/console-commands/option-parsers.rst deleted file mode 100644 index 9c2b96d30b..0000000000 --- a/es/console-commands/option-parsers.rst +++ /dev/null @@ -1,379 +0,0 @@ -Opcion de analizadores (Parsers) -################################# - -.. php:namespace:: Cake\Console -.. php:class:: ConsoleOptionParser - -Las aplicaciones de consola normalmente toman opciones y argumentos como la -forma principal de obtener información del terminal en sus comandos. - -Definición de un OptionParser -============================= - -Los comandos y shells proporcionan un método de enlace -``buildOptionParser($parser)`` que puede utilizar para definir las opciones y -argumentos de sus comandos:: - - protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser - { - // Defina sus opciones y argumentos. - - // Devolver el analizador completo - return $parser; - } - -Las clases Shell usan el método de enlace ``getOptionParser()`` para definir su analizador de opciones:: - - public function getOptionParser() - { - // Obtenga un analizador vacío del marco. - $parser = parent::getOptionParser(); - - // Defina sus opciones y argumentos. - - // Devolver el analizador completo - return $parser; - } - - -Usando argumentos -================= - -.. php:method:: addArgument($name, $params = []) - -Los argumentos posicionales se utilizan con frecuencia en herramientas de línea -de comandos, y ``ConsoleOptionParser`` le permite definir argumentos -posicionales así como hacerlos obligatorios. Puede agregar argumentos uno a la -vez con ``$parser->addArgument();`` o varios a la vez con -``$parser->addArguments();``:: - - $parser->addArgument('model', ['help' => 'The model to bake']); - -Puede utilizar las siguientes opciones al crear un argumento: - -* ``help`` El texto de ayuda que se mostrará para este argumento. -* ``required`` Si este parámetro es necesario. -* ``index`` El índice del argumento, si no se define, el argumento se colocará - al final de los argumentos. Si define el mismo índice dos veces, se - sobrescribirá la primera opción. -* ``choices`` Una serie de opciones válidas para este argumento. Si se deja - vacío, todos los valores son válidos. Se generará una excepción cuando parse() - encuentre un valor no válido. - -Los argumentos que se han marcado como obligatorios generarán una excepción al -analizar el comando si se han omitido. Entonces no tienes que manejar eso en tu -shell. - -Agregar múltiples argumentos ----------------------------- - -.. php:method:: addArguments(array $args) - -Si tiene una matriz con múltiples argumentos, puede usar -``$parser->addArguments()`` para agregar múltiples argumentos a la vez. :: - - $parser->addArguments([ - 'node' => ['help' => 'The node to create', 'required' => true], - 'parent' => ['help' => 'The parent node', 'required' => true], - ]); - -Al igual que con todos los métodos de creación de ConsoleOptionParser, -addArguments se puede utilizar como parte de una cadena de métodos fluida. - -Validación de Argumentos ------------------------- - -Al crear argumentos posicionales, puede utilizar el indicador ``required`` para -indicar que un argumento debe estar presente cuando se llama a un shell. Además, -puedes usar ``choices`` para forzar que un argumento provenga de una lista de -opciones válidas:: - - $parser->addArgument('type', [ - 'help' => 'The type of node to interact with.', - 'required' => true, - 'choices' => ['aro', 'aco'], - ]); - -Lo anterior creará un argumento que es obligatorio y tiene validación en la -entrada. Si falta el argumento o tiene un valor incorrecto, se generará una -excepción y se detendrá el shell. - -Usando opciones -=============== - -.. php:method:: addOption($name, array $options = []) - -Las opciones o indicadores se utilizan en las herramientas de línea de comandos -para proporcionar argumentos clave/valor desordenados para sus comandos. Las -opciones pueden definir alias tanto detallados como cortos. Pueden aceptar un -valor (por ejemplo, ``--connection=default``) o ser opciones booleanas(por -ejemplo, ``--verbose``). Las opciones se definen con el método ``addOption()``:: - - $parser->addOption('connection', [ - 'short' => 'c', - 'help' => 'connection', - 'default' => 'default', - ]); - -Lo anterior le permitiría usar ``cake myshell --connection=other``, -``cake myshell --connection other`` o ``cake myshell -c other`` -al invocar el shell. - -Los modificadores booleanos no aceptan ni consumen valores, y su presencia -simplemente los habilita en los parámetros analizados:: - - $parser->addOption('no-commit', ['boolean' => true]); - -Esta opción, cuando se usa como ``cake mycommand --no-commit something``, -tendría un valor de ``true`` y 'something' se trataría como un argumento posicional. - -Al crear opciones, puede utilizar las siguientes opciones para definir el -comportamiento de la opción: - -* ``short`` - La variante de una sola letra para esta opción, déjela sin definir - para ninguna. -* ``help`` - Texto de ayuda para esta opción. Se utiliza al generar ayuda para - la opción. -* ``default`` - El valor predeterminado para esta opción. Si no se define, el - valor predeterminado será ``true``. -* ``boolean`` - La opción no utiliza ningún valor, es solo un modificador booleano. - El valor predeterminado es ``false``. -* ``multiple`` - La opción se puede proporcionar varias veces. La opción - analizada será una matriz de valores cuando esta opción esté habilitada. -* ``choices`` - Una serie de opciones válidas para esta opción. Si se deja - vacío, todos los valores son válidos. Se generará una excepción cuando parse() - encuentre un valor no válido. - -Agregar múltiples opciones --------------------------- - -.. php:method:: addOptions(array $options) - -Si tiene una matriz con múltiples opciones, puede usar ``$parser->addOptions()`` -para agregar múltiples opciones a la vez. :: - - $parser->addOptions([ - 'node' => ['short' => 'n', 'help' => 'The node to create'], - 'parent' => ['short' => 'p', 'help' => 'The parent node'], - ]); - -Al igual que con todos los métodos de creación de ConsoleOptionParser, -addOptions se puede utilizar como parte de una cadena de métodos fluida. - -Validación de Opciones ----------------------- - -Las opciones pueden contar con un conjunto de opciones de manera muy similar a -como lo pueden ser los argumentos posicionales. Cuando una opción tiene opciones -definidas, esas son las únicas opciones válidas para una opción. Todos los demás -valores generarán una ``InvalidArgumentException``:: - - $parser->addOption('accept', [ - 'help' => 'What version to accept.', - 'choices' => ['working', 'theirs', 'mine'], - ]); - -Usando opciones booleanas -------------------------- - -Las opciones se pueden definir como opciones booleanas, que son útiles cuando -necesitas crear algunas opciones de bandera. Al igual que las opciones con -valores predeterminados, las opciones booleanas siempre se incluyen en los -parámetros analizados. Cuando las banderas están presentes, se establecen -``true``; cuando están ausentes, se establecen en ``false``:: - - $parser->addOption('verbose', [ - 'help' => 'Enable verbose output.', - 'boolean' => true - ]); - -La siguiente opción siempre tendría un valor en el parámetro analizado. Cuando -no se incluye, su valor predeterminado será ``false`` y, cuando se defina, -será ``true``. - -Construyendo una consola OptionParser a partir de una matriz ------------------------------------------------------------- - -.. php:method:: buildFromArray($spec) - -Como se mencionó anteriormente, al crear analizadores de opciones de subcomando, -puede definir la especificación del analizador como una matriz para ese método. -Esto puede ayudar a facilitar la creación de analizadores de subcomandos, ya que -todo es una matriz:: - - $parser->addSubcommand('check', [ - 'help' => __('Check the permissions between an ACO and ARO.'), - 'parser' => [ - 'description' => [ - __("Use this command to grant ACL permissions. Once executed, the "), - __("ARO specified (and its children, if any) will have ALLOW access "), - __("to the specified ACO action (and the ACO's children, if any)."), - ], - 'arguments' => [ - 'aro' => ['help' => __('ARO to check.'), 'required' => true], - 'aco' => ['help' => __('ACO to check.'), 'required' => true], - 'action' => ['help' => __('Action to check')], - ], - ], - ]); - -Dentro de las especificaciones del analizador, puede definir claves para -``arguments``, ``options``, ``description`` y ``epilog``. No se pueden definir -``subcommands`` dentro de un generador de estilos de matriz. Los valores de los -argumentos y las opciones deben seguir el formato :php:func:`Cake\\Console\\ConsoleOptionParser::addArguments()` y -:php:func:`Cake\\Console\\ConsoleOptionParser::addOptions( )`. También puede -utilizar buildFromArray por sí solo para crear un analizador de opciones:: - - public function getOptionParser() - { - return ConsoleOptionParser::buildFromArray([ - 'description' => [ - __("Use this command to grant ACL permissions. Once executed, the "), - __("ARO specified (and its children, if any) will have ALLOW access "), - __("to the specified ACO action (and the ACO's children, if any).") - ], - 'arguments' => [ - 'aro' => ['help' => __('ARO to check.'), 'required' => true], - 'aco' => ['help' => __('ACO to check.'), 'required' => true], - 'action' => ['help' => __('Action to check')], - ], - ]); - } - -Fusionar analizadores de opciones ---------------------------------- - -.. php:method:: merge($spec) - -Al crear un comando de grupo, es posible que desee combinar varios analizadores -para esto:: - - $parser->merge($anotherParser); - -Tenga en cuenta que el orden de los argumentos para cada analizador debe ser el -mismo y que las opciones también deben ser compatibles para que funcione. Así -que no utilices claves para cosas diferentes. - -Obtener ayuda de comandos -========================== - -Al definir sus opciones y argumentos con el analizador de opciones, CakePHP -puede generar automáticamente información de ayuda rudimentaria y agregar -``--help`` y ``-h`` a cada uno de sus comandos. El uso de una de estas opciones -le permitirá ver el contenido de ayuda generado: - -.. code-block:: console - - bin/cake bake --help - bin/cake bake -h - -Ambos generarían la ayuda para hornear. También puede obtener ayuda para -comandos anidados: - -.. code-block:: console - - bin/cake bake model --help - bin/cake bake model -h - -Lo anterior le brindará ayuda específica para el comando ``bake model``. - -Obtener ayuda como XML ----------------------- - -Al crear herramientas automatizadas o herramientas de desarrollo que necesitan -interactuar con los comandos de CakePHP, es bueno tener ayuda disponible en un -formato que la máquina pueda analizar. Al proporcionar la opción ``xml`` al -solicitar ayuda, puede obtener el contenido de la ayuda como XML: - -.. code-block:: console - - cake bake --help xml - cake bake -h xml - -Lo anterior devolvería un documento XML con la ayuda, opciones, argumentos y -subcomandos generados para el shell seleccionado. Un documento XML de muestra -se vería así: - -.. code-block:: xml - - - - bake fixture - Generate fixtures for use with the test suite. You can use - `bake fixture all` to bake all fixtures. - - Omitting all arguments and options will enter into an interactive - mode. - - - - - - - - - - - - - - - - - -Personalización de salida de la ayuda -===================================== - -Puede enriquecer aún más el contenido de ayuda generado agregando una -descripción y un epílogo. - -Establecer la descripción -------------------------- - -.. php:method:: setDescription($text) - -La descripción se muestra encima de la información del argumento y la opción. -Al pasar una matriz o una cadena, puede establecer el valor de la descripción:: - - // Establecer varias líneas a la vez - $parser->setDescription(['line one', 'line two']); - - // Leer el valor actual - $parser->getDescription(); - -Establecer el epílogo ---------------------- - -.. php:method:: setEpilog($text) - -Obtiene o establece el epílogo del analizador de opciones. El epílogo se muestra -después de la información del argumento y la opción. Al pasar una matriz o una -cadena, puede establecer el valor del epílogo:: - - // Establecer varias líneas a la vez - $parser->setEpilog(['line one', 'line two']); - - // Leer el valor actual - $parser->getEpilog(); diff --git a/es/console-commands/plugin.rst b/es/console-commands/plugin.rst deleted file mode 100644 index 8318caac95..0000000000 --- a/es/console-commands/plugin.rst +++ /dev/null @@ -1,67 +0,0 @@ -.. _plugin-shell: - -Herramienta de complemento (Plugin) -################################### - -La herramienta de complemento le permite cargar y descargar complementos a -través del símbolo del sistema. Si necesita ayuda, ejecute: - -.. code-block:: console - - bin/cake plugin --help - -Cargando complementos ---------------------- - -A través de la tarea ``Load`` puedes cargar complementos en tu -**config/bootstrap.php**. Puedes hacer esto ejecutando: - -.. code-block:: console - - bin/cake plugin load MyPlugin - -Esto agregará lo siguiente a su **src/Application.php**:: - - // En el método bootstrap agregue: - $this->addPlugin('MyPlugin'); - -Descarga de complementos ------------------------- - -Puede descargar un complemento especificando su nombre: - -.. code-block:: console - - bin/cake plugin unload MyPlugin - -Esto eliminará la línea ``$this->addPlugin('MyPlugin',...)`` de -**src/Application.php**. - -Activos del complemento (Assets) ---------------------------------- - -CakePHP sirve de forma predeterminada recursos de complementos utilizando el -middleware ``AssetMiddleware``. Si bien esto es una buena conveniencia, se -recomienda vincular/copiar los activos del complemento en la raíz web de la -aplicación para que el servidor web pueda servirlos directamente sin invocar -PHP. Puedes hacer esto ejecutando: - -.. code-block:: console - - bin/cake plugin assets symlink - -La ejecución del comando anterior vinculará simbólicamente todos los recursos de -los complementos en la raíz web de la aplicación. En Windows, que no admite -enlaces simbólicos, los activos se copiarán en las carpetas respectivas en lugar -de tener enlaces simbólicos. - -Puede vincular simbólicamente los activos de un complemento en particular -especificando su nombre: - -.. code-block:: console - - bin/cake plugin assets symlink MyPlugin - -.. meta:: - :title lang=es: Herramienta de complemento (Plugin) - :keywords lang=es: plugin,assets,tool,load,unload,complemento,activos diff --git a/es/console-commands/repl.rst b/es/console-commands/repl.rst deleted file mode 100644 index 89e17276be..0000000000 --- a/es/console-commands/repl.rst +++ /dev/null @@ -1,46 +0,0 @@ -Consola interactiva (REPL) -########################## - -CakePHP ofrece el complemento -`REPL (Read Eval Print Loop) `__ para -permitirle explorar algo de CakePHP y su aplicación en una consola interactiva. - -Puede iniciar la consola interactiva usando: - -.. code-block:: console - - bin/cake console - -Esto iniciará su aplicación e iniciará una consola interactiva. En este punto, -puede interactuar con el código de su aplicación y ejecutar consultas utilizando -los modelos de su aplicación: - -.. code-block:: console - - bin/cake console - - >>> $articles = Cake\Datasource\FactoryLocator::get('Table')->get('Articles'); - // object(Cake\ORM\Table)( - // - // ) - >>> $articles->find()->all(); - -Dado que su aplicación ha sido iniciada, también puede probar el enrutamiento -usando REPL:: - - >>> Cake\Routing\Router::parse('/articles/view/1'); - // [ - // 'controller' => 'Articles', - // 'action' => 'view', - // 'pass' => [ - // 0 => '1' - // ], - // 'plugin' => NULL - // ] - -También puedes probar la generación de URL:: - - >>> Cake\Routing\Router::url(['controller' => 'Articles', 'action' => 'edit', 99]); - // '/articles/edit/99' - -Para salir de REPL, puede usar ``CTRL-C`` o escribir ``exit``. diff --git a/es/console-commands/routes.rst b/es/console-commands/routes.rst deleted file mode 100644 index 57540214b8..0000000000 --- a/es/console-commands/routes.rst +++ /dev/null @@ -1,39 +0,0 @@ -Herramienta de enrutamiento (Routes) -#################################### - -La herramienta de rutas proporciona una interfaz CLI fácil de usar para probar -y depurar rutas. Puede usarlo para probar cómo se analizan las rutas y qué -parámetros de enrutamiento de URL generarán. - -Obtener una lista de todas las rutas ------------------------------------- - -.. code-block:: console - - bin/cake routes - -Prueba de análisis de URL -------------------------- - -Puedes ver rápidamente cómo se analizará una URL usando el método ``check``: - -.. code-block:: console - - bin/cake routes check /articles/edit/1 - -Si su ruta contiene algún parámetro de cadena de consulta, recuerde encerrar -la URL entre comillas: - -.. code-block:: console - - bin/cake routes check "/articles/?page=1&sort=title&direction=desc" - -Prueba de generación de URL ---------------------------- - -Puede ver la URL que generará :term:`arreglo de enrutamiento` usando el método ``generar``: - -.. code-block:: console - - bin/cake routes generate controller:Articles action:edit 1 - diff --git a/es/console-commands/schema-cache.rst b/es/console-commands/schema-cache.rst deleted file mode 100644 index a66620f13a..0000000000 --- a/es/console-commands/schema-cache.rst +++ /dev/null @@ -1,30 +0,0 @@ -Herramienta de esquemas de caché (Schema Cache) -################################################ - -SchemaCacheShell proporciona una herramienta CLI sencilla para administrar las -cachés de metadatos de su aplicación. En situaciones de implementación, resulta -útil reconstruir la caché de metadatos in situ sin borrar los datos de la caché -existente. Puedes hacer esto ejecutando: - -.. code-block:: console - - bin/cake schema_cache build --connection default - -Esto reconstruirá el caché de metadatos para todas las tablas en la conexión -``default``. Si solo necesita reconstruir una única tabla, puede hacerlo -proporcionando su nombre: - -.. code-block:: console - - bin/cake schema_cache build --connection default articles - -Además de crear datos almacenados en caché, también puede utilizar -SchemaCacheShell para eliminar metadatos almacenados en caché: - -.. code-block:: console - - # Borrar todos los metadatos - bin/cake schema_cache clear - - # Limpiar una sola tabla - bin/cake schema_cache clear articles diff --git a/es/console-commands/server.rst b/es/console-commands/server.rst deleted file mode 100644 index ce9b051f33..0000000000 --- a/es/console-commands/server.rst +++ /dev/null @@ -1,30 +0,0 @@ -Herramienta de servidor -####################### - -El ``ServerCommand`` te permite crear un servidor web simple utilizando el -servidor web PHP integrado. Si bien este servidor *no* está diseñado para uso -en producción, puede ser útil en el desarrollo cuando desea probar rápidamente -una idea y no quiere perder tiempo configurando Apache o Nginx. Puedes iniciar -el servidor con el comando: - -.. code-block:: console - - bin/cake server - -Deberías ver que el servidor arranca y se conecta al puerto 8765. Puedes visitar -el servidor CLI visitando ``http://localhost:8765`` en su navegador web. -Para cerrar el servidor presiona ``CTRL-C`` en tu terminal. - -.. note:: - - Prueba ``bin/cake server -H 0.0.0.0`` si no se puede acceder al servidor - desde otros hosts. - -Cambiar el puerto y la raíz del documento -========================================= - -Puedes personalizar el puerto y el directorio raíz usando las opciones: - -.. code-block:: console - - bin/cake server --port 8080 --document_root path/to/app diff --git a/es/contents.rst b/es/contents.rst deleted file mode 100644 index 58d174f95b..0000000000 --- a/es/contents.rst +++ /dev/null @@ -1,106 +0,0 @@ -Contenidos -########## - -.. toctree:: - :hidden: - - index - -.. toctree:: - :caption: Prefacio - - intro - quickstart - appendices/migration-guides - tutorials-and-examples - contributing - release-policy - -.. toctree:: - :caption: Comenzando - - installation - development/configuration - development/application - development/dependency-injection - development/routing - controllers/request-response - controllers/middleware - controllers - views - orm - -.. toctree:: - :caption: Usando CakePHP - - core-libraries/caching - console-commands - development/debugging - deployment - core-libraries/email - development/errors - core-libraries/events - core-libraries/internationalization-and-localization - core-libraries/logging - core-libraries/form - controllers/pagination - plugins - development/rest - security - development/sessions - development/testing - core-libraries/validation - -.. toctree:: - :caption: Clases de Utilidad - - core-libraries/app - core-libraries/collections - core-libraries/hash - core-libraries/httpclient - core-libraries/inflector - core-libraries/number - core-libraries/plugin - core-libraries/registry-objects - core-libraries/text - core-libraries/time - core-libraries/xml - -.. toctree:: - :caption: Plugins & Paquetes - - standalone-packages - Authentication - Authorization - Bake - Debug Kit - Migrations - Elasticsearch - Phinx - Chronos - Queue - -.. toctree:: - :caption: Otros - - core-libraries/global-constants-and-functions - appendices - -.. toctree:: - :hidden: - - topics - chronos - debug-kit - elasticsearch - bake - bake/development - bake/usage - migrations - phinx - -.. todolist:: - -.. meta:: - :title lang=es: Contenidos - :keywords lang=es: librerías,búsqueda por referencia,consolas,despliegue,apéndices,glosarios,modelos diff --git a/es/contributing.rst b/es/contributing.rst deleted file mode 100644 index b72d2a30fb..0000000000 --- a/es/contributing.rst +++ /dev/null @@ -1,17 +0,0 @@ -Contribuir -########## - -Existen diversas maneras con las que puedes contribuir a CakePHP: - -.. toctree:: - :maxdepth: 1 - - contributing/documentation - contributing/tickets - contributing/code - contributing/cakephp-coding-conventions - contributing/backwards-compatibility - -.. meta:: - :title lang=es: Contribuir -   :keywords lang=es: conveniones de código,documentación,maxdepth diff --git a/es/contributing/backwards-compatibility.rst b/es/contributing/backwards-compatibility.rst deleted file mode 100644 index f63cfe12c9..0000000000 --- a/es/contributing/backwards-compatibility.rst +++ /dev/null @@ -1,176 +0,0 @@ -Guía de compatibilidad hacia atrás -################################## - -Asegurar que puedas actualizar tus aplicaciones fácilmente es importante para -nosotros. Por ello sólo rompemos la compatibilidad en las liberaciones de -versiones ``major``. Puedes familiarizarte con el `versionado semántico -`_, el cual utilizamos en todos los proyectos -de CakePHP. Pero resumiendo, el versionado semántico significa que sólo las -liberaciones de versiones ``major`` (tales como 2.0, 3.0, 4.0) pueden romper la -compatibilidad hacia atrás. Las liberaciones ``minor`` (tales como 2.1, 3.1, 3.2) -pueden introducir nuevas funcionalidades, pero no pueden romper la compatibilidad. -Los lanzamientos de correcciones de errores (tales como 3.0.1) no añaden nuevas -funcionaliades, sólo correcciones de errores o mejoras de rendimiento. - -.. note:: - - CakePHP empezó a seguir el versionado semántico a partir de la 2.0.0. Estas - reglas no se aplican en las versiones 1.x. - -Para aclarar que cambios puedes esperar de cada nivel de lanzamiento tenemos -más información detallada para desarrolladores que utilizan CakePHP y que -trabajan en él que ayudan a aclarar que puede hacerse en liberaciones ``minor``. -Las liberaciones ``major`` pueden tener tantas rupturas de compatibilidad como -sean necesarias. - -Guías de migración -================== - -Para cada liberación ``major`` y ``minor`` el equipo de CakePHP facilitará una -guía de migración. Estas guías explican las nuevas funcionaliades y cualquier -ruptura de compatibilidad que haya en cada lanzamiento. Pueden encontrarse en -la sección :doc:`/appendices` del ``cookbook``. - -Usar CakePHP -============ - -Si estás desarrollando tu aplicación con CakePHP las siguientes pautas -explican la estabilidad que puedes esperar. - -Interfaces ----------- - -Con excepción de las liberaciones ``major``, las interfaces que provee CakePHP -**no** tendrán ningún cambio en los métodos existentes. Podrán añadirse nuevos -métodos, pero no habrá cambios en los ya existentes. - -Clases ------- - -Las clases que proporciona CakePHP pueden estar construidas y tener sus métodos y -propiedades públicos usados por el código de la aplicación y, a excepción -de las liberaciones ``major``, la compatibilidad hacia atrás está garantizada. - -.. note:: - - Algunas clases en CakePHP están marcadas con la etiqueta API doc ``@internal``. - Estas clases **no** son estables y no garantizan la compatibilidad hacia atrás. - -En liberaciones ``minor`` pueden añadirse nuevos métodos a las clases y a los ya -existentes nuevos argumentos. Cualquier argumento nuevo tendrá un valor por -defecto, pero si sobreescribes métodos con una firma diferente puedes encontrar -``fatal errors``. Los métodos con nuevos argumentos estarán documentados en las -guías de migración.. - -La siguiente tabla esboza varios casos de uso y qué compatibilidad puedes esperar -de CakePHP: - -+---------------------------------------+--------------------------+ -| Si tu... | ¿Compatible hacia atrás? | -+=======================================+==========================+ -| Tipificas contra la clase | Si | -+---------------------------------------+--------------------------+ -| Creas una nueva instancia | Si | -+---------------------------------------+--------------------------+ -| Extiendes la clase | Si | -+---------------------------------------+--------------------------+ -| Accedes a una propiedad pública | Si | -+---------------------------------------+--------------------------+ -| Llamas un método público | Si | -+---------------------------------------+--------------------------+ -| **Extiendes una clase y...** | -+---------------------------------------+--------------------------+ -| Sobrescribes una propiedad pública | Si | -+---------------------------------------+--------------------------+ -| Accedes a una propiedad protegida | No [1]_ | -+---------------------------------------+--------------------------+ -| Sobreescribes una propiedad protegida | No [1]_ | -+---------------------------------------+--------------------------+ -| Sobreescribes un método protegido | No [1]_ | -+---------------------------------------+--------------------------+ -| Llamas a un método protegido | No [1]_ | -+---------------------------------------+--------------------------+ -| Añades una propiedad pública | No | -+---------------------------------------+--------------------------+ -| Añades un método público | No | -+---------------------------------------+--------------------------+ -| Añades un argumento | No [1]_ | -| a un método sobreescrito | | -+---------------------------------------+--------------------------+ -| Añades un valor por defecto | Si | -| a un argumento de método | | -| existente | | -+---------------------------------------+--------------------------+ - -Trabajando en CakePHP -===================== - -Si estás ayudando a que CakePHP sea aún mejor, por favor, ten en mente las -siguientes pautas cuando añadas/cambies funcionalidades: - -En una liberación ``minor`` puedes: - -+---------------------------------------+--------------------------+ -| En una liberación ``minor`` puedes... | -+=======================================+==========================+ -| **Clases** | -+---------------------------------------+--------------------------+ -| Eliminar una clase | No | -+---------------------------------------+--------------------------+ -| Eliminar una interfaz | No | -+---------------------------------------+--------------------------+ -| Eliminar un ``trait`` | No | -+---------------------------------------+--------------------------+ -| Hacer final | No | -+---------------------------------------+--------------------------+ -| Hacer abstract | No | -+---------------------------------------+--------------------------+ -| Cambiar el nombre | Si [2]_ | -+---------------------------------------+--------------------------+ -| **Propiedades** | -+---------------------------------------+--------------------------+ -| Añadir una propiedad pública | Si | -+---------------------------------------+--------------------------+ -| Eliminar una propiedad pública | No | -+---------------------------------------+--------------------------+ -| Añadir una propiedad protegida | Si | -+---------------------------------------+--------------------------+ -| Eliminar una propiedad protegida | Si [3]_ | -+---------------------------------------+--------------------------+ -| **Métodos** | -+---------------------------------------+--------------------------+ -| Añadir un método público | Si | -+---------------------------------------+--------------------------+ -| Eliminar un método público | No | -+---------------------------------------+--------------------------+ -| Añadir un método protegido | Si | -+---------------------------------------+--------------------------+ -| Mover a la clase padre | Si | -+---------------------------------------+--------------------------+ -| Eliminar un método protegido | Si [3]_ | -+---------------------------------------+--------------------------+ -| Reducir visibilidad | No | -+---------------------------------------+--------------------------+ -| Cambiar nombre del método | Si [2]_ | -+---------------------------------------+--------------------------+ -| Añadir un argumento nuevo con | Si | -| valor por defecto | | -+---------------------------------------+--------------------------+ -| Añadir un nuevo argumento obligatorio | No | -| a un método existente | | -+---------------------------------------+--------------------------+ -| Eliminar un valor por defecto de | No | -| un argumento existente | | -+---------------------------------------+--------------------------+ - -.. [1] Tu código *puede* romperse en lanzamientos ``minor``. - Comprueba la guía de migración para más detalles. -.. [2] Puedes cambiar el nombre de una clase/método siempre y cuando el - antiguo nombre se mantenga disponible. Esto es evitado generalmente a - menos que el cambio de nombre sea significativamente beneficioso. -.. [3] Evitarlo cuando sea posible. Cualquier borrado tendrá que ser documentado - en la guía de migración. - -.. meta:: - :title lang=es: Guía de compatibilidad hacia atrás - :keywords lang=es: diff --git a/es/contributing/cakephp-coding-conventions.rst b/es/contributing/cakephp-coding-conventions.rst deleted file mode 100644 index d6118b2b17..0000000000 --- a/es/contributing/cakephp-coding-conventions.rst +++ /dev/null @@ -1,614 +0,0 @@ -Estándares de codificación -########################## - -Los desarrolladores de CakePHP deberán utilizar la `Guia de estilo de codificación PSR-12 -`_ además de las siguientes normas como estándares -de codificación. - -Es recomendable que otos *CakeIngredients* que se desarrollen sigan los mismos -estándares. - -Puedes utilizar el `CakePHP Code Sniffer `_ -para comprobar que tu código siga los estándares requeridos. - -Añadir nuevas funcionalidades -============================= - -Las nuevas funcionalidades no se deberán añadir sin sus propias pruebas, las cuales -deberán ser superadas antes de hacer el *commit* en el repositorio. - -Configuración del *IDE* -======================= - -Asegúrate de que tu *IDE* haga *trim* por la derecha para que no haya espacios -al final de las líneas. - -La mayoría de los *IDE* modernos soportan archivos ``.editorconfig``. El -esqueleto de aplicación de CakePHP viene con él por defecto y contiene las -mejores prácticas de forma predeterminada. - -Tabulación -========== - -Se utilizará cuatro espacios para la tabulación. - -Por lo que debería everse asi:: - - // nivel base - // nivel 1 - // nivel 2 - // nivel 1 - // nivel base - -O también:: - - $booleanVariable = true; - $stringVariable = 'moose'; - if ($booleanVariable) { - echo 'Boolean value is true'; - if ($stringVariable === 'moose') { - echo 'We have encountered a moose'; - } - } - -En los casos donde utilices llamadas de funciones que ocupen más de una línea -usa las siguientes guías: - -* El paréntesis de abertura de la llamada de la función deberá ser lo - último que contenga la línea. -* Sólo se permite un argumento por línea. -* Los paréntesis de cierre deben estar solos y en una línea por separado. - -Por ejemplo, en vez de utilizar el siguiente formato:: - - $matches = array_intersect_key($this->_listeners, - array_flip(preg_grep($matchPattern, - array_keys($this->_listeners), 0))); - -Utiliza éste en su lugar:: - - $matches = array_intersect_key( - $this->_listeners, - array_flip( - preg_grep($matchPattern, array_keys($this->_listeners), 0) - ) - ); - -Tamaño de línea -=============== - -Es recomendable mantener un tamaño de 100 caracteres por línea para una mejor -lectura del código y tratar de no pasarse de los 120. - -En resumen: - -* 100 caracteres es el límite recomendado. -* 120 caracteres es el límite máximo. - -Estructuras de control -====================== - -Las estructuras de control son por ejemplo "``if``", "``for``", "``foreach``", -"``while``", "``switch``" etc. A continuación un ejemplo con "``if``":: - - if ((expr_1) || (expr_2)) { - // accion_1; - } elseif (!(expr_3) && (expr_4)) { - // accion_2; - } else { - // accion_por_defecto; - } - -* En las estructuras de control deberá haber un espacio antes del primer paréntesis - y otro entre el último y la llave de apertura. -* Utiliza siempre las llaves en las estructuras de control incluso si - no son necesarias. Aumentan la legibilidad del código y te proporcionan menos - errores lógicos. -* Las llaves de apertura deberán estar en la misma línea que la estructura de - control, las de cierre en líneas nuevas y el código dentro de las dos llaves - en un nuevo nivel de tabulación. -* No deberán usarse las asignaciones *inline* en las estructras de control. - -:: - - // Incorrecto: sin llaves y declaración mal posicionada - if (expr) statement; - - // Incorrecto: sin llaves - if (expr) - statement; - - // Correcto - if (expr) { - statement; - } - - // Incorrecto = asignación inline - if ($variable = Class::function()) { - statement; - } - - // Correcto - $variable = Class::function(); - if ($variable) { - statement; - } - -Operador ternario ------------------ - -Los operadores ternarios están permitidos cuando toda su declaración cabe en una -sola línea. Operadores más largos deberán ir dentro de una declaración -``if else``. Los operadores ternarios no deberían ir nunca anidados y opcionalmente -pueden utilizarse paréntesis alrededor de las condiciones para dar claridad:: - - // Correcto, sencillo y legible - $variable = isset($options['variable']) ? $options['variable'] : true; - - // Incorrecto, operadores anidados - $variable = isset($options['variable']) ? isset($options['othervar']) ? true : false : false; - -Archivos de plantilla ---------------------- - -En los archivos de plantilla (archivos .php) los desarrolladores deben utilizar -estructuras de control ``keyword`` al ser más fáciles de leer en archivos complejos. Las estructuras de control pueden estar dentro de bloques -de PHP o en etiquetas PHP separadas:: - - Eres el usuario admin.

      '; - endif; - ?> -

      Lo siguiente es aceptado también:

      - -

      Eres el usuario admin.

      - - -Comparación -=========== - -Intenta ser siempre lo más estricto posible. Si una comparación no es estricta -de forma deliberada, puede ser inteligente añadir un comentario para evitar -confundirla con un error. - -Para comprobar si una variable es ``null`` se recomienda utilizar la comprobación -estricta:: - - if ($value === null) { - // ... - } - -El valor contra el que se va a realizar la comparación deberá ir en el lado derecho -de esta:: - - // no recomendado - if (null === $this->foo()) { - // ... - } - - // recomendado - if ($this->foo() === null) { - // ... - } - -Llamadas de funciones -===================== - -Las llamadas a funciones deben realizarse sin espacios entre el nombre de la -función y el parentesis de apertura y entre cada parámetro de la llamada deberá -haber un espacio:: - - $var = foo($bar, $bar2, $bar3); - -Como puedes ver arriba también deberá haber un espacio a ambos lados de los -signos de igual. - -Definición de métodos -===================== - -Ejemplo de definición de un método:: - - public function someFunction($arg1, $arg2 = '') - { - if (expr) { - statement; - } - - return $var; - } - -Parámetros con un valor por defecto deberán ir al final de las definiciones. -Trata que tus funciones devuelvan siempre un resultado, al menos ``true`` o -``false``, para que se pueda determinar cuando la llamada a la función ha sido -correcta:: - - public function connection($dns, $persistent = false) - { - if (is_array($dns)) { - $dnsInfo = $dns; - } else { - $dnsInfo = BD::parseDNS($dns); - } - - if (!($dnsInfo) || !($dnsInfo['phpType'])) { - return $this->addError(); - } - - return true; - } - -Como puedes ver hay un espacio a ambos lados del signo de igual. - -Declaración de tipo -------------------- - -Los argumentos que esperan objetos, arrays o ``callbacks`` pueden ser tipificados. -Solo tipificamos métodos públicos, aunque la tipificación no está libre de costes:: - - /** - * Alguna descripción del método - * - * @param \Cake\ORM\Table $table La clase table a utilizar. - * @param array $array Algún valor array. - * @param callable $callback Algún callback. - * @param bool $boolean Algún valor boolean. - */ - public function foo(Table $table, array $array, callable $callback, $boolean) - { - } - -Aquí ``$table`` debe ser una instancia de ``\Cake\ORM\Table``, ``$array`` debe -ser un ``array`` y ``$callback`` debe ser de tipo ``callable`` (un ``callback`` -válido). - -Fíjate en que si quieres permitir que ``$array`` sea también una instancia de -``\ArrayObject`` no deberías tipificarlo ya que ``array`` acepta únicamente el -tipo primitivo:: - - /** - * Alguna descripción del método. - * - * @param array|\ArrayObject $array Algún valor array. - */ - public function foo($array) - { - } - -Funciones anónimas (``Closures``) ---------------------------------- - -Para definir funciones anónimas sigue la guía de estilo de código -`PSR-12 `_ , donde se declaran con un espacio -después de la palabra ``function`` y antes y después de la palabra ``use``:: - - $closure = function ($arg1, $arg2) use ($var1, $var2) { - // código - }; - -Encadenación de métodos -======================= - -Las encadenaciones de métodos deberán distribuir estos en líneas separadas y -tabulados con cuatro espacios:: - - $email->from('foo@example.com') - ->to('bar@example.com') - ->subject('A great message') - ->send(); - -Comentar el código -================== - -Todos los comentarios deberán ir escritos en inglés y describir de un modo claro -el bloque de código comentado. - -Los comentarios pueden incluir las siguientes etiquetas de -`phpDocumentor `_: - -* `@deprecated `_ - Usando el formato ``@version ``, donde ``version`` - y ``description`` son obligatorios. -* `@example `_ -* `@ignore `_ -* `@internal `_ -* `@link `_ -* `@see `_ -* `@since `_ -* `@version `_ - -Las etiquetas PhpDoc son muy similares a las etiquetas JavaDoc en Java. Las etiquetas -solo son procesadas si son el primer elemento en una línea DocBlock, por ejemplo:: - - /** - * Ejemplo de etiqueta. - * - * @author esta etiqueta es parseada, pero esta @version es ignorada - * @version 1.0 esta etiqueta es parseada también - */ - -:: - - /** - * Ejemplo de etiquetas phpDoc inline. - * - * Esta función trabaja duramente con foo() para manejar el mundo. - * - * @return void - */ - function bar() - { - } - - /** - * Función foo. - * - * @return void - */ - function foo() - { - } - -Los bloques de comentarios, con la excepción del primer bloque en un archivo, -deberán ir siempre precedidos por un salto de línea. - -Tipos de variables ------------------- - -Tipos de variables para utilizar en DocBlocks: - -Tipo - Descripción -mixed - Una variable de tipo indefinido o múltiples tipos. -int - Variable de tipo integer (números enteros). -float - Tipo float (número de coma flotante). -bool - Tipo booleano (true o false). -string - Tipo string (cualquier valor entre " " o ' '). -null - Tipo null. Normalmente usado conjuntamente con otro tipo. -array - Tipo array. -object - Tipo object. Debe usarse un nombre de clase específico si es posible. -resource - Tipo resource (devuelto por ejemplo por mysql\_connect()). - Recuerda que cuando especificas el tipo como mixed deberás indicar - si es desconocido o cuáles son los tipos posibles. -callable - Función Callable. - -Puedes combinar tipos usando el caracter ``|``:: - - int|bool - -Para más de dos tipos normalmente lo mejor es utilizar ``mixed``. - -Cuando se devuelva el propio objeto, por ejemplo para encadenar, deberás utilizar -``$this`` en su lugar:: - - /** - * Función foo. - * - * @return $this - */ - public function foo() - { - return $this; - } - -Incluir archivos -================ - -``include``, ``require``, ``include_once`` y ``require_once`` no tienen paréntesis:: - - // mal = paréntesis - require_once('ClassFileName.php'); - require_once ($class); - - // bien = sin paréntesis - require_once 'ClassFileName.php'; - require_once $class; - -Cuando se incluyan archivos con clases o librerías usa siempre y únicamente la -función `require\_once `_. - -Etiquetas PHP -============= - -Utiliza siempre las etiquetas ```` en lugar de ````. - -La sintaxis abreviada de ``echo`` deberá usarse en los archivos de plantilla -(**.php**) donde proceda. - -Sintaxis abreviada de echo --------------------------- - -La sintaxis abreviada de ``echo`` (`` - - // bien = con espacios y sin punto y coma - - -A partir de la versión 5.4 de PHP la etiqueta (```_ -* FTP: `ftp://ftp.example.com `_ - -El nombre de dominio "example.com" está reservado para ello (ver :rfc:`2606`) -y está recomendado para usar en documentaciones o como ejemplos. - -Archivos --------- - -Los nombres de archivos que no contengan clases deberán ir en minúsculas y -con guiones bajos, por ejemplo:: - - nombre_de_archivo_largo.php - -Hacer ``casts`` ---------------- - -Para hacer ``casts`` usamos: - -Tipo - Descripción -(bool) - Cast a boolean. -(int) - Cast an integer. -(float) - Cast a float. -(string) - Cast a string. -(array) - Cast an array. -(object) - Cast an object. - -Por favor utiliza ``(int)$var`` en lugar de ``intval($var)`` y ``(float)$var`` -en lugar de ``floatval($var)`` cuando aplique. - -Constantes ----------- - -Los nombres de constantes deberán ir en mayúsculas:: - - define('CONSTANTE', 1); - -Si el nombre de una constante se compone de varias palabras deberán ir separadas -por guiones bajos, por ejemplo:: - - define('NOMBRE_DE_CONSTANTE_LARGO', 2); - -Cuidado al usar empty()/isset() -=============================== - -Aunque ``empty()`` es una función sencilla de utilizar, puede enmascarar errores -y causar efectos accidentales cuando se usa con ``'0'`` y ``0``. Cuando -las variables o propiedades están ya definidas el uso de ``empty()`` no es -recomendable. Al trabajar con variables es mejor utilizar la conversión a tipo -booleano en lugar de ``empty()``:: - - function manipulate($var) - { - // No recomendado, $var está definido en el ámbito - if (empty($var)) { - // ... - } - - // Utiliza la conversión a booleano - if (!$var) { - // ... - } - if ($var) { - // ... - } - } - -Cuando trates con propiedades definidas deberías favorecer las comprobaciones -sobre ``null`` en lugar de ``empty()``/``isset()``:: - - class Thing - { - private $property; // Definido - - public function readProperty() - { - // No recomendado al estar definida la propiedad en la clase - if (!isset($this->property)) { - // ... - } - // Recomendado - if ($this->property === null) { - - } - } - } - -Cuando se trabaja con arrays, es mejor hacer ``merge`` de valores por defecto -en vez de hacer comprobaciones con ``empty()``. Haciendo ``merge`` de valores -por defecto puedes asegurarte de que las claves necesarias están definidas:: - - function doWork(array $array) - { - // Hacer merge de valor por defecto para eliminar la necesidad - // de comprobaciones empty - $array += [ - 'key' => null, - ]; - - // No recomendado, la clave ya está seteada - if (isset($array['key'])) { - // ... - } - - // Recomendado - if ($array['key'] !== null) { - // ... - } - } - -.. meta:: - :title lang=es: Estándares de codificación - :keywords lang=es: llaves, nivel de tabulación, errores logicos, estructuras de control, expr, estándares de codificación, paréntesis, foreach, legibilidad,moose,nuevas funcionalidades,repositorio,desarrolladores diff --git a/es/contributing/code.rst b/es/contributing/code.rst deleted file mode 100644 index fd11eb2bc8..0000000000 --- a/es/contributing/code.rst +++ /dev/null @@ -1,153 +0,0 @@ -Código -###### - -Parches y *pull requests* son una manera genial de contribuir con código a CakePHP. -Los *Pull requests* pueden ser creados en Github, preferiblemente a los archivos de -parches en los comentarios de tickets. - -Configuración inicial -===================== - -Antes de trabajar en parches para CakePHP es una buena idea configurar tu entorno -de trabajo. - -Necesitarás los siguientes programas: - -* Git -* PHP |minphpversion| o mayor -* PHPUnit 5.7.0 o mayor - -Configura tu información de usuario con tu nombre/alias y correo electrónico -de trabajo:: - - git config --global user.name 'Bob Barker' - git config --global user.email 'bob.barker@example.com' - -.. note:: - - Si eres nuevo en Git, te recomendamos encarecidamente que leas el maravilloso - y gratuito libro `ProGit `_ - -Clona el código fuente de CakePHP desde GitHub: - -* Si no tienes una cuenta de `GitHub `_ créate una. -* Haz un *fork* del `repositorio CakePHP `_ - haciendo clic en el botón **Fork**. - -Después de haber hecho el fork, clónalo en tu equipo local:: - - git clone git@github.com:TUNOMBRE/cakephp.git - -Añade el repositorio original de CakePHP como respositorio remoto, lo usarás más -adelante para buscar cambios en el repositorio de CakePHP. Esto te mantendrá -actualizado con CakePHP:: - - cd cakephp - git remote add upstream git://github.com/cakephp/cakephp.git - -Ahora que tienes configurado CakePHP deberías poder definir un ``$test`` de -:ref:`conexión de base de datos ` y -:ref:`ejecutar todos los tests `. - -Trabajar en un parche -===================== - -Cada vez que quieras trabajar en un bug, una funcionalidad o en una mejora, -crea una rama específica. - -Tu rama debería ser creada a partir de la versión que quieras arreglar/mejorar. -Por ejemplo, si estás arreglando un error en la versión ``3.x`` deberías utilizar -la rama ``master`` como rama origen. Si tu cambio es para un error de la serie 2.x -deberías usar la rama ``2.x``. Esto hará más adelante tus *merges* más sencillos -al no permitirte Github editar la rama destino:: - - # arreglando un error en 3.x - git fetch upstream - git checkout -b ticket-1234 upstream/master - - # arreglando un error en 2.x - git fetch upstream - git checkout -b ticket-1234 upstream/2.x - -.. tip:: - - Usa un nombre descriptivo para tu rama, referenciar el ticket o nombre de la - característica es una buena convención. P.ej. ticket-1234, nueva-funcionalidad - -Lo anterior creará una rama local basada en la rama *upstream* 2.x (CakePHP) - -Trabaja en tu correción y haz tantos *commits* como necesites, pero ten siempre en mente -lo siguiente: - -* Sigue las :doc:`/contributing/cakephp-coding-conventions`. -* Añade un caso de prueba para mostrar el error arreglado o que la nueva funcionalidad - funciona. -* Mantén lógicos tus commits y escribe comentarios de *commit* bien claros - y concisos. - -Enviar un *Pull Request* -======================== - -Una vez estén hechos tus cambios y estés preparado para hacer el *merge* con CakePHP -tendrás que actualizar tu rama:: - - # Hacer rebase de la corrección en el top de master - git checkout master - git fetch upstream - git merge upstream/master - git checkout - git rebase master - -Esto buscará y hará *merge* de cualquier cambio que haya sucedido en CakePHP desde que -empezaste. Entonces ejecutará *rebase* o replicará tus cambios en el *top* del -actual código. - -Puede que encuentres algún conflicto durante el *rebase*. Si este finaliza -precipitadamente puedes ver qué archivos son conflictivos/*un-merged* con -``git status``. -Resuelve cada conflicto y continúa con el *rebase*:: - - git add # haz esto con cada archivo conflictivo. - git rebase --continue - -Comprueba que todas tus pruebas continúan pasando. Entonces sube tu rama a tu *fork*:: - - git push origin - -Si has vuelto a hacer *rebase* después de hacer el *push* de tu rama necesitarás -forzar el *push*:: - - git push --force origin - -Una vez tu rama esté en GitHub puedes enviar un *pull request* en GitHub. - -Seleccionar donde harán el *merge* tus cambios ----------------------------------------------- - -Cuando hagas *pull requests* deberás asegurarte de seleccionar la rama correcta -como base ya que no podrás editarla una vez creada. - -* Si tus cambios son un *bugfix* (corrección de error) y no introduce ninguna - funcionalidad nueva entonces selecciona **master** como destino del merge. -* Si tu cambio es una *new feature* (nueva funcionalidad) o un añadido al framework - entonces deberías seleccionar la rama con el número de la siguiente versión. Por - ejemplo si la versión estable actualmente es la ``3.2.10``, la rama que estará - aceptando nuevas funcionalidades será la ``3.next``. -* Si tu cambio cesa una funcionalidad existente o de la *API* entonces tendrás - que escojer la versión mayor siguiente. Por ejemplo, si la actual versión estable - es la ``3.2.2`` entonces la siguiente versión en la que se puede cesar es la ``4.x`` - por lo que deberás seleccionar esa rama. - -.. note:: - - Recuerda que todo código que contribuyas a CakePHP será licenciado bajo la - Licencia MIT, y la `Cake Software Foundation `_ - será la propietaria de cualquier código contribuido. Los contribuidores deberán seguir las - `Guías de la comunidad CakePHP `_. - -Todos los *merge* de corrección de errores que se hagan a una rama de mantenimiento -se harán también periódicamente sobre futuros lanzamientos por el equipo central. - -.. meta:: - :title lang=es: Código - :keywords lang=es: código fuente cakephp,parches de código,test ref,nombre descriptivo,bob barker,configuración incial,usuario global,conexión a base de datos,clonar,repositorio,información de usuario,mejora,back patches,checkout diff --git a/es/contributing/documentation.rst b/es/contributing/documentation.rst deleted file mode 100644 index 2773f7e0d0..0000000000 --- a/es/contributing/documentation.rst +++ /dev/null @@ -1,486 +0,0 @@ -Documentación -############# - -Contribuir con la documentación es fácil. Los archivos están hospedados -en https://github.com/cakephp/docs. Siéntete libre de hacer un *fork* del -repositorio, añadir tus cambios, mejoras, traducciones y comenzar a ayudar -a través de un nuevo *pull request*. También puedes editar los archivos de manera -online con GitHub sin la necesidad de descargarlos -- el botón *Improve this Doc* -que aparece en todas las páginas te llevará al editor online de GitHub de esa página. - -La documentación de CakePHP dispone de `integración continua `_ -y se despliega automáticamente tras realizar el *merge* del *pull request*. - -Traducciones -============ - -Envía un email al equipo de documentación (docs *arroba* cakephp *punto* org) o -utiliza IRC (#cakephp en *freenode*) para hablar de cualquier trabajo de -traducción en el que quieras participar. - -Nueva traducción ----------------- - -Nos gustaría poder disponer de traducciones que estén todo lo completas posible. -Sin embargo, hay ocasiones donde un archivo de traducción no está al día, por lo -que debes considerar siempre la versión en inglés como la versión acreditada. - -Si tu idioma no está entre los disponibles, por favor, contacta con nosotros a -través de Github y estudiaremos la posibilidad de crear la estructura de archivos -para ello. - -Las siguientes secciones son las primeras que deberías considerar -traducir, ya que estos archivos no cambian a menudo: - -- index.rst -- intro.rst -- quickstart.rst -- installation.rst -- /intro (carpeta) -- /tutorials-and-examples (carpeta) - -Recordatorio para administradores de documentación --------------------------------------------------- - -La estructura de archivos de todos los idiomas deben seguir la estructura de -la versión en inglés. Si la estructura cambia en esta versión debemos realizar -dichos cambios en los demás idiomas. - -Por ejemplo, si se crea un nuevo archivo en inglés en **en/file.rst** tendremos que: - -- Añadir el archivo en todos los idiomas: **fr/file.rst**, **zh/file.rst**, ... -- Borrar el contenido pero manteniendo el ``title``, ``meta`` información y - ``toc-tree`` que pueda haber. Se añadirá la siguiente nota mientras nadie - traduzca el archivo:: - - File Title - ########## - - .. note:: - The documentation is not currently supported in XX language for this - page. - - Please feel free to send us a pull request on - `Github `_ or use the **Improve This Doc** - button to directly propose your changes. - - You can refer to the English version in the select top menu to have - information about this page's topic. - - // If toc-tree elements are in the English version - .. toctree:: - :maxdepth: 1 - - one-toc-file - other-toc-file - - .. meta:: - :title lang=xx: File Title - :keywords lang=xx: title, description,... - -Consejos para traductores -------------------------- - -- Navega y edita en el idioma al que quieras traducir el contenido - de otra - manera no verás lo que ya está traducido. -- Siéntete libre de bucear en la traducción si ya existe en tu idioma. -- Usa la `Forma informal `_. -- Traduce el título y el contenido a la vez. -- Compara con la versión en inglés antes de subir una corrección (si corriges - algo, pero no indicas una referencia tu subida no será aceptada). -- Si necesitas escribir un término en inglés envuélvelo en etiquetas ````. - E.g. "asdf asdf *Controller* asdf" o "asdf asdf Kontroller - (*Controller*) asfd" como proceda. -- No subas traducciones parciales. -- No edites una sección con cambios pendientes. -- No uses `entidades HTML `_ - para caracteres acentudados, la documentación utiliza UTF-8. -- No cambies significatibamente el etiquetado (HTML) o añadas nuevo contenido. -- Si falta información en el contenido original sube primero una corrección de ello. - -Guía de formato para la documentación -===================================== - -La nueva documentación de CakePHP está escrito con `texto en formato ReST `_. - -ReST (*Re Structured Text*) es una sintaxis de marcado de texto plano similar a -*Markdown* o *Textile*. - -Para mantener la consistencia cuando añadas algo a la documentación de CakePHP -recomendamos que sigas las siguientes líneas guía sobre como dar formato y -estructurar tu texto. - -Tamaño de línea ---------------- - -Las líneas de texto deberían medir como máximo 40 caracteres. Las únicas -excepciones son URL largas y fragmentos de código. - -Cabeceras y secciones ---------------------- - -Las cabeceras de las secciones se crean subrayando el título con caracteres de -puntuación. El subrayado deberá ser por lo menos tan largo como el texto. - -- ``#`` Se utiliza para indicar los títulos de páginas. -- ``=`` Se utiliza para los títulos de las secciones de una página. -- ``-`` Se utiliza para los títulos de subsecciones. -- ``~`` Se utiliza para los títulos de sub-subsecciones. -- ``^`` Se utiliza para los títulos de sub-sub-subsecciones. - -Los encabezados no deben anidarse con más de 5 niveles de profundidad y deben -estar precedidos y seguidos por una línea en blanco. - -Párrafos --------- - -Párrafos son simplemente bloques de texto con todas las líneas al mismo nivel de -indexación. Los párrafos deben separarse por al menos una línea vacía. - -Marcado en línea ----------------- - -* Un asterisco: *texto* en cursiva. - Lo usaremos para enfatizar/destacar de forma general. - - * ``*texto*``. - -* Dos astericos: **texto** en negrita. - Lo usaremos para indicar directorios de trabajo, títulos de listas y nombres - de tablas (excluyendo la palabra *table*). - - * ``**/config/Migrations**``, ``**articulos**``, etc. - -* Dos acentos graves (*``*): ``texto`` para ejemplos de código. - Lo usaramos para nombres de opciones de métodos, columnas de tablas, - objetos (excluyendo la palabra "objeto") y para nombres de métodos y funciones - (incluídos los paréntesis) - - * ````cascadeCallbacks````, ````true````, ````id````, - ````PagesController````, ````config()````, etc. - -Si aparecen asteriscos o acentos graves en el texto y pueden ser confundidos con -los delimitadores de marcado habrá que escaparlos con *\\*. - -Los marcadores en línea tienen algunas restricciones: - -* **No pueden** estar anidados. -* El contenido no puede empezar o acabar con espacios en blanco: ``* texto*`` - está mal. -* El contenido debe separarse del resto del texto por caracteres que no sean - palabras. Utiliza *\\* para escapar un espacio y solucionarlo: ``onelong\ *bolded*\ word``. - -Listas ------- - -El etiquetado de listas es muy parecido a *Markdown*. Las listas no ordenadas se -indican empezando una línea con un asterisco y un espacio. - -Las listas enumeradas pueden crearse con enumeraciones o ``#`` para auto enumeración: - - * Esto es una viñeta - * Esto también, pero esta línea - tiene dos líneas. - - 1. Primera línea - 2. Segunda línea - - #. La enumeración automática - #. Te ahorrará algo de tiempo. - -También se pueden crear listas anidadas tabulando secciones y separándolas con -una línea en blanco:: - - * Primera línea - * Segunda línea - - * Bajando un nivel - * Yeah! - - * Volviendo al primer nivel - -Pueden crearse listas de definiciones haciendo lo siguiente:: - - Término - Definición - CakePHP - Un framework MVC para PHP - -Los términos no pueden ocupar más de una línea, pero las definiciones pueden -ocupar más líneas mientras se aniden consistentemente. - -Enlaces -------- - -Hay diferentes tipos de enlaces, cada uno con sus características. - -Enlaces externos -~~~~~~~~~~~~~~~~ - -Los enlaces a documentos externos pueden hacerse de la siguiente manera:: - - `Enlace externo a php.net `_ - -El resultado debería verse así: `Enlace externo a php.net `_ - -Enlaces a otras páginas -~~~~~~~~~~~~~~~~~~~~~~~ - -.. rst:role:: doc - - Puedes crear enlaces a otras páginas de la documentación usando la función - ``::doc:``. Puedes enlazar a un archivo específico empleando rutas relativas - o absolutas omitiendo la extensión ``.rst``. Por ejemplo: si apareciese - ``:doc:`form``` en el documento ``core-helpers/html``, el enlace haría - referencia a ``core-helpers/form``. Si la referencia fuese ``:doc:`/core-helpers``` - el enlace sería siempre a ``/core-helpers`` sin importar donde se utilice. - -Enlaces a referencias cruzadas -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. rst:role:: ref - - Puedes hacer referncia cruzada a cualquier título de cualquier documento - usando la función ``:ref:``. Los enlaces a etiquetas de destino deben ser - únicos a lo largo de toda la documentación. Cuando se crean etiquetas para - métodos de clase lo mejor es usar ``clase-método`` como formato para tu - etiqueta de destino. - - El uso más habitual de etiquetas es encima de un título. Ejemplo:: - - .. _nombre-etiqueta: - - Título sección - -------------- - - Resto del contenido. - - En otro sitio podrías enlazar a la sección de arriba usando ``:ref:`nombre-etiqueta```. - El texto del enlace será el título al que precede el enlace pero puedes - personalizarlo usando ``:ref:`Texto del enlace ```. - -Evitar alertas de Sphinx -~~~~~~~~~~~~~~~~~~~~~~~~ - -Sphinx mostrará avisos si un archivo no es referenciado en un *toc-tree*. Es una -buena manera de asegurarse de que todos los archivos tienen un enlace dirigido -a ellos. Pero a veces no necesitas introducir un enlace a un archivo, p.ej. para -nuestros archivos *epub-contents* y *pdf-contents*. En esos casos puedes añadir -``:orphan:`` al inicio del archivo para eliminar las alertas de que el archivo -no está en el *toc-tree* - -Describir clases y sus contenidos ---------------------------------- - -La documentación de CakePHP usa el `phpdomain -`_ para proveer directivas -personalizadas para describir objetos PHP y constructores. El uso de estas -directivas y funciones es necesario para una correcta indexación y uso de las -herramientas de referenciación cruzada. - -Describir clases y constructores --------------------------------- - -Cada directiva introduce el contenido del índice y/o índice del *namespace*. - -.. rst:directive:: .. php:global:: nombre - - Esta directiva declara una nueva variable PHP global. - -.. rst:directive:: .. php:function:: nombre(firma) - - Define una nueva función global fuera de una clase. - -.. rst:directive:: .. php:const:: nombre - - Esta directiva declara una nueva constante PHP, puedes usarla también anidada - dentro de una directiva de clase para crear constantes de clase. - -.. rst:directive:: .. php:exception:: nombre - - Esta directiva declara una nueva excepción en el *namespace* actual. La firma - puede incluir argumentos de constructor. - -.. rst:directive:: .. php:class:: nombre - - Describe una clase. Métodos, atributos y atributos que pertenezcan a la clase - deberán ir dentro del cuerpo de la directiva:: - - .. php:class:: MyClass - - Descripción de la clase - - .. php:method:: method($argument) - - Descripción del método - - Atributos, métodos y constantes no necesitan estar anidados, pueden seguir - la siguiente declaración de clase:: - - .. php:class:: MyClass - - Texto sobre la clase - - .. php:method:: methodName() - - Texto sobre el método - - .. ver también:: :rst:dir:`php:method`, :rst:dir:`php:attr`, :rst:dir:`php:const` - -.. rst:directive:: .. php:method:: nombre(firma) - - Describe un método de clase, sus argumentos, salida y excepciones:: - - .. php:method:: instanceMethod($one, $two) - - :param string $one: El primer parámetro. - :param string $two: El segundo parámetro. - :returns: Un array de cosas - :throws: InvalidArgumentException - - Esto es una instancia de método. - -.. rst:directive:: .. php:staticmethod:: ClassName::nombreMetodo(firma) - - Describe un método estático, sus argumentos, salida y excepciones, - ver :rst:dir:`php:method` para opciones. - -.. rst:directive:: .. php:attr:: nombre - - Describe una propiedad/atributo en una clase. - -Evitar avisos de Sphinx -~~~~~~~~~~~~~~~~~~~~~~~ - -Sphinx mostrará avisos si una función es referenciada en múltiples archivos. Es -una buena manera de asegurarse de que no añades una función dos veces, pero algunas -veces puedes querer escribir una función en dos o más archivos, p.ej. *'debug object'* -es referenciado en *`/development/debugging`* y *`/core-libraries/global-constants-and-functions`*. -En este caso tu puedes añadir ``:noindex:`` debajo de la función *debug* para eliminar -los avisos. Mantén únicamente una referencia **sin** ``:no-index:`` para seguir -teniendo la función referenciada:: - - .. php:function:: debug(mixed $var, boolean $showHtml = null, $showFrom = true) - :noindex: - -Referencias cruzadas -~~~~~~~~~~~~~~~~~~~~ - -Los siguientes *roles* hacen referencia a objetos PHP y los enlaces son generados -si se encuentra una directiva que coincida: - -.. rst:role:: php:func - - Referencia a una función PHP. - -.. rst:role:: php:global - - Referencia a una variable global cuyo nombre tiene prefijo ``$``. - -.. rst:role:: php:const - - Referencia tanto a una constante global como a una de clase. Las constantes - de clase deberán ir precedidas por la clase que las contenga:: - - DateTime tiene una constante :php:const:`DateTime::ATOM`. - -.. rst:role:: php:class - - Referencia una clase por el nombre:: - - :php:class:`ClassName` - -.. rst:role:: php:meth - - Referencia un método de una clase. Este *role* soporta ambos tipos de métodos:: - - :php:meth:`DateTime::setDate` - :php:meth:`Classname::staticMethod` - -.. rst:role:: php:attr - - Referencia una propiedad de un objeto:: - - :php:attr:`ClassName::$propertyName` - -.. rst:role:: php:exc - - Referencia una excepción. - -Código fuente -------------- - -Los bloques de citas de código fuente se crean finalizando un párrafo con ``::``. -El bloque debe ir anidado y, como todos los párrafos, separados por líneas en -blanco:: - - Esto es un párrafo:: - - while ($i--) { - doStuff() - } - - Esto es otra vez texto normal. - -Los textos citados no son modificados ni formateados salvo el primer nivel de -anidamiento, que es eliminado. - -Notas y avisos --------------- - -Hay muchas ocasiones en las que quieres avisar al lector de un consejo importante, -una nota especial o un peligro potencial. Las admonestaciones en *Sphinx* se -utilizan justo para eso. Hay cinco tipos de admonestaciones: - -* ``.. tip::`` Los consejos (*tips*) se utilizan para documentar o reiterar - información interesante o importante. El contenido de la directiva debe - escribirse en sentencias completas e incluir todas las puntuaciones apropiadas. -* ``.. note::`` Las notas (*notes*) se utilizan para documentar una pieza de - información importante. El contenido de la directiva debe escribirse en - sentencias completas e incluir todas las puntuaciones apropiadas. -* ``.. warning::`` Avisos (*warnings*) se utilizan para documentar posibles - obstáculos o información relativa a seguridad. El contenido de la directiva - debe escribirse en sentencias completas e incluir todas las puntuaciones - apropiadas. -* ``.. versionadded:: X.Y.Z`` las admonestaciones *"Version added"* se utilizan - para mostrar notas específicas a nuevas funcionalidades añadidas en una versión - específica, siendo ``X.Y.Z`` la versión en la que se añadieron. -* ``.. deprecated:: X.Y.Z`` es lo opuesto a *versionadded*, se utiliza para - avisar de una funcionalidad obsoleta, siendo ``X.Y.Z`` la versión en la - que pasó a ser obsoleta. - -Todas las admonestaciones se escriben igual:: - - .. note:: - - Anidado y precedido por una línea en blanco. - Igual que un párafo. - - Este texto no es parte de la nota. - -Ejemplos -~~~~~~~~ - -.. tip:: - - Esto es un consejo útil que probablemente hayas olvidado. - -.. note:: - - Deberías prestar atención aquí. - -.. warning:: - - Podría ser peligroso. - -.. versionadded:: 4.0.0 - - Esta funcionalidad tan genial fue añadida en la versión 4.0.0 - -.. deprecated:: 4.0.1 - - Esta antigua funcionalidad pasó a ser obsoleta en la versión 4.0.1 - -.. meta:: - :title lang=es: Documentación - :keywords lang=es: traducciones parciales, trabajos de traducción, entidades html,text markup,asfd,asdf,texto estructurado,contenido en ingles,markdown,texto formateado,punto org,repo,consistencia,traductor,freenode,textile,mejoras,sintaxis,cakephp,submission diff --git a/es/contributing/tickets.rst b/es/contributing/tickets.rst deleted file mode 100644 index 621dba7a73..0000000000 --- a/es/contributing/tickets.rst +++ /dev/null @@ -1,50 +0,0 @@ -Tickets -####### - -Aportar *feedback* y ayudar a la comunidad en la forma de tickets es una parte -extremadamente importante en el proceso de desarrollo de CakePHP. Todos los -tickets de CakePHP están alojados en `GitHub `_. - -Reportar errores -================ - -Los reportes de errores bien escritos son de mucha ayuda. Para ello hay una serie de -pasos que ayudan a crear el mejor reporte de error posible: - -* **Correcto**: Por favor, `busca tickets `_ - similares que ya existan y asegúrate de que nadie haya reportado ya tu problema - o que no haya sido arreglado en el repositorio. -* **Correcto**: Por favor, incluye instrucciones detalladas de **cómo reproducir el error**. - Esto podría estar escrito en el formato de caso de prueba o con un - **snippet** de código que demuestre el problema. No tener una forma de - reproducir el error significa menos probabilidades de poder arreglarlo. -* **Correcto**: Por favor, danos todos los detalles posibles de tu entorno: sistema - operativo, versión de PHP, versión de CakePHP... -* **Incorrecto**: Por favor, no utilices el sistema de tickets para hacer preguntas - de soporte. El canal #cakephp IRC en `Freenode `__ - tiene muchos desarrolladores disponibles para ayudar a responder tus preguntas. - También échale un vistazo a `Stack Overflow `__. - -Reportar problemas de seguridad -=============================== - -Si has encontrado problemas de seguridad en CakePHP, por favor, utiliza el -siguiente procedimiento en vez del sistema de reporte de errores. En vez de -utilizar el *tracker* de errores, lista de correos o IRC, por favor, envía -un email a **security [at] cakephp.org**. Los emails enviados a esta dirección -van al equipo principal de CakePHP en una lista de correo privada. - -Por cada reporte primero tratamos de confirmar la vulnerabilidad, una vez -confirmada el equipo de CakePHP tomará las siguientes acciones: - -* Dar a conocer al reportador que hemos recibido el problema y que estamos - trabajando en una solución. Pediremos al reportador que mantenga en - secreto el problema hasta que nosotros lo anunciemos. -* Preparar una solución/parche. -* Preparar un *post* describiendo la vulnerabilidad y las posibles consecuencias. -* Publicar nuevas versiones para todas las que estén afectadas. -* Mostrar de manera acentuada el problema en el anuncio de la publicación. - -.. meta:: - :title lang=es: Tickets - :keywords lang=es: sistema de reporte de error, code snippet, reporte de seguridad, mailing privado, anuncio de publicación, google, sistema de tickets, equipo principal, problema de seduridad, bug tracker, canal irc, casos de prueba, preguntas de soporte, reporte de error, problemas de seguridad, reportes de error, exploits, vulnerabilidad, repositorio diff --git a/es/controllers.rst b/es/controllers.rst deleted file mode 100644 index 5bdccff12c..0000000000 --- a/es/controllers.rst +++ /dev/null @@ -1,608 +0,0 @@ -Controladores -############# - -.. php:namespace:: Cake\Controller - -.. php:class:: Controller - -Los controladores son la 'C' en MVC. Después de aplicar el enrutamiento y -que el controlador -ha sido encontrado, la acción de tu controlador es llamado. Tu controlador -debe manejar la interpretación de los datos de la solicitud, -asegurándose de que se llamen -a los modelos correctos y se muestre la respuesta o vista correcta. -Los controladores se pueden -considerar como una capa intermedia entre el Modelo y la Vista. Quieres mantener -tus controladores delgados, y tus modelos gruesos. -Esto te ayudará a reutilizar tu código y lo hará más fácil de probar. - -Comúnmente, un controlador se usa para administrar la lógica en torno -a un solo modelo. Por -ejemplo, si estuvieras construyendo un sitio online para una panadería, -podrías tener un -RecipesController que gestiona tus recetas y un IngredientsController -que gestiona tus -ingredientes. Sin embargo, es posible hacer que los controladores trabajen -con más de -un modelo. En CakePHP, un controlador es nombrado a raíz del modelo que maneja. - -Los controladores de tu aplicación extienden de la clase ``AppController``, -que a su vez extiende de la clase principal :php:class:`Controller`. -La clase ``AppController`` puede ser definida en **src/Controller/AppController.php** -y debería contener los métodos que se comparten entre todos los controladores -de tu aplicación. - -Los controladores proveen una serie de métodos que manejan las peticiones. Estos -son llamadas *acciones*. Por defecto, cada método público en un controlador es -una acción, y es accesible mediante una URL. Una acción es responsable de -interpretar la petición y crear la respuesta. Por lo general, las respuestas -son de la forma de una vista renderizada, pero también, hay otras maneras de crear -respuestas. - -.. _app-controller: - -El App Controller -================== - -Como se indicó en la introducción, la clase ``AppController`` es clase padre de -todos los controladores de tu aplicación. ``AppController`` extiende de la clase -:php:class:`Cake\\Controller\\Controller` que está incluida en CakePHP. -``AppController`` se define en **src/Controller/AppController.php** como se -muestra a continuación:: - - namespace App\Controller; - - use Cake\Controller\Controller; - - class AppController extends Controller - { - } - -Los atributos y métodos del controlador creados en tu ``AppController`` van a -estar disponibles en todos los controladores que extiendan de este. Los -componentes (que aprenderás más adelante) son mejor usados para código que se -encuentra en muchos (pero no necesariamente en todos) los componentes. - -Puedes usar tu ``AppController`` para cargar componentes que van a ser utilizados -en cada controlador de tu aplicación. CakePHP proporciona un método ``initialize()`` -que es llamado al final del constructor de un controlador para este tipo de uso:: - - namespace App\Controller; - - use Cake\Controller\Controller; - - class AppController extends Controller - { - public function initialize(): void - { - // Always enable the FormProtection component. - $this->loadComponent('FormProtection'); - } - } - - -Flujo de solicitud -================== - -Cuando se realiza una solicitud a una aplicación CakePHP, las clases CakePHP -:php:class:`Cake\\Routing\\Router` y :php:class:`Cake\\Routing\\Dispatcher` -usan :ref:`routes-configuration` para encontrar y crear la instancia correcta -del controlador. Los datos de la solicitud son encapsulados en un objeto de -solicitud. CakePHP pone toda la información importante de la solicitud en la -propiedad ``$this->request``. Consulta la sección sobre :ref:`cake-request` -para obtener más información sobre el objeto de solicitud de CakePHP. - -Acciones del controlador -======================== - -Las acciones del controlador son las responsables de convertir los parámetros -de la solicitud en una respuesta para el navegador/usuario que realiza la -petición. CakePHP usa convenciones para automatizar este proceso y eliminar -algunos códigos repetitivos que de otro modo se necesitaría escribir. - -Por convención, CakePHP renderiza una vista con una versión en infinitivo del nombre -de la acción. Volviendo a nuestro ejemplo de la panadería online, nuestro -RecipesController podría contener las acciones ``view()``, ``share()``, y -``search()``. El controlador sería encontrado en -**src/Controller/RecipesController.php** y contiene:: - - // src/Controller/RecipesController.php - - class RecipesController extends AppController - { - public function view($id) - { - // La lógica de la acción va aquí. - } - - public function share($customerId, $recipeId) - { - // La lógica de la acción va aquí. - } - - public function search($query) - { - // La lógica de la acción va aquí. - } - } - -Las plantillas para estas acciones serían **templates/Recipes/view.php**, -**templates/Recipes/share.php**, y **templates/Recipes/search.php**. El nombre -convencional para un archivo de vista es con minúsculas y con el nombre de la -acción entre guiones bajos. - -Las acciones de los controladores por lo general usan ``Controller::set()`` para -crear un contexto que ``View`` usa para renderizar la capa de vista. Debido -a las convenciones que CakePHP usa, no necesitas crear y renderizar la vista -manualmente. En su lugar, una vez que se ha completado la acción del controlador, -CakePHP se encargará de renderizar y entregar la vista. - -Si por algún motivo deseas omitir el comportamiento predeterminado, puedes retornar -un objeto :php:class:`Cake\\Http\\Response` de la acción con la respuesta creada. - -Para que puedas usar un controlador de manera efectiva en tu aplicación, -cubriremos algunos de los atributos y métodos principales proporcionados por los -controladores de CakePHP. - -Interactuando con vistas -======================== - -Los controladores interactúan con las vistas de muchas maneras. Primero, los -controladores son capaces de pasar información a las vistas, usando ``Controller::set()``. -También puedes decidir qué clase de vista usar, y qué archivo de vista debería -ser renderizado desde el controlador. - -.. _setting-view_variables: - -Configuración de variables de vista ------------------------------------ - -.. php:method:: set(string $var, mixed $value) - -El método ``Controller::set()`` es la manera principal de mandar información -desde el controlador a la vista. Una vez que hayas utilizado ``Controller::set()``, -la variable puede ser accedida en tu vista:: - - // Primero pasas las información desde el controlador: - - $this->set('color', 'rosa'); - - // Después, en la vista, puede utilizar la información: - ?> - - Has seleccionado cubierta para la tarta. - -El método ``Controller::set()`` también toma un array asociativo como su primer -parámetro. A menudo, esto puede ser una forma rápida de asignar un conjunto de -información a la vista:: - - $data = [ - 'color' => 'pink', - 'type' => 'sugar', - 'base_price' => 23.95, - ]; - - // Hace $color, $type, y $base_price - // disponible para la vista: - - $this->set($data); - -Ten en cuenta que las variables de la vista se comparten entre todas las partes -renderizadas por tu vista. Estarán disponibles en todas las partes de la vista: -la plantilla y todos los elementos dentro de estas dos. - -Configuración de las opciones de la vista ------------------------------------------ - -Si deseas personalizar la clase vista, las rutas de diseño/plantillas, ayudantes -o el tema que se usarán para renderizar la vista, puede usar el método ``viewBuilder()`` -para obtener un constructor. Este constructor se puede utilizar para definir -propiedades de la vista antes de crearlas:: - - $this->viewBuilder() - ->addHelper('MyCustom') - ->setTheme('Modern') - ->setClassName('Modern.Admin'); - -Lo anterior muestra cómo puedes cargar ayudantes personalizados, configurar el tema -y usar una clase vista personalizada. - -Renderizando una vista ----------------------- - -.. php:method:: render(string $view, string $layout) - -El método ``Controller::render()`` es llamado automáticamente al final de cada -solicitud de la acción del controlador. Este método realiza toda la lógica -de la vista (usando la información que has enviado usando el método ``Controller::set()``), -coloca la vista dentro de su ``View::$layout``, y lo devuelve al usuario final. - -El archivo de vista por defecto utilizado para el renderizado es definido por -convención. -Si la acción ``search()`` de RecipesController es solicitada, el archivo vista en -**templates/Recipes/search.php** será renderizado:: - - namespace App\Controller; - - class RecipesController extends AppController - { - // ... - public function search() - { - // Renderiza la vista en templates/Recipes/search.php - return $this->render(); - } - // ... - } - -Aunque CakePHP va a llamarlo automáticamente después de cada acción de lógica -(a menos que llames a ``$this->disableAutoRender()``), puedes usarlo para -especificar un archivo de vista alternativo especificando el nombre de este como -primer argumento del método ``Controller::render()``. - -Si ``$view`` empieza con '/', se asume que es una vista o un archivo relacionado -con la carpeta **templates**. Esto permite el renderizado directo de elementos, -muy útil en llamadas AJAX:: - - // Renderiza el elemento en templates/element/ajaxreturn.php - $this->render('/element/ajaxreturn'); - -El segundo parámetro ``$layout`` de ``Controller::render()`` te permita especificar -la estructura con la que la vista es renderizada. - -Renderizando una plantilla específica -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En tu controlador, puede que quieras renderizar una vista diferente a la que es -convencional. Puedes hacer esto llamando a ``Controller::render()`` directamente. -Una vez que hayas llamado a ``Controller::render()``, CakePHP no tratará de -re-renderizar la vista:: - - namespace App\Controller; - - class PostsController extends AppController - { - public function my_action() - { - $this->render('custom_file'); - } - } - -Esto renderizará **templates/Posts/custom_file.php** en vez de -**templates/Posts/my_action.php**. - -También puedes renderizar vistas dentro de plugins usando la siguiente sintaxis: -``$this->render('PluginName.PluginController/custom_file')``. Por ejemplo:: - - namespace App\Controller; - - class PostsController extends AppController - { - public function myAction() - { - $this->render('Users.UserDetails/custom_file'); - } - } - -Esto renderizará **plugins/Users/templates/UserDetails/custom_file.php** - -.. _controller-viewclasses: - -Negociación del tipo de contenido -================================= - -.. php:method:: viewClasses() - -Los controladores pueden definir una lista de clases de vistas que soportan. -Después de que la acción del controlador este completa, CakePHP usará la lista de -vista para realizar negociación del tipo de contenido. Esto permite a tu aplicación -rehusar la misma acción del controlador para renderizar una vista HTML o -renderizar una respuesta JSON o XML. Para definir la lista de clases de vista que -soporta un controlador se utiliza el método ``viewClasses()``:: - - namespace App\Controller; - - use Cake\View\JsonView; - use Cake\View\XmlView; - - class PostsController extends AppController - { - public function viewClasses(): array - { - return [JsonView::class, XmlView::class]; - } - } - -La clase ``View`` de la aplicación se usa automáticamente como respaldo cuando -no se puede seleccionar otra vista en función del encabezado de la petición ``Accept`` -o de la extensión del enrutamiento. Si tu aplicación sólo soporta tipos de contenido -para una acción específica, puedes definir esa lógica dentro de ``viewClasses()``:: - - public function viewClasses(): array - { - if ($this->request->getParam('action') === 'export') { - // Usa una vista CSV personalizada para exportación de datos - return [CsvView::class]; - } - - return [JsonView::class]; - } - -Si dentro de las acciones de tu controlador necesitas procesar la petición o cargar datos -de forma diferente dependiendo del tipo de contenido puedes usar -:ref:`check-the-request`:: - - // En la acción de un controlador - - // Carga información adicional cuando se preparan respuestas JSON - if ($this->request->is('json')) { - $query->contain('Authors'); - } - -También puedes definir las clases View soportadas por tu controlador usando -el método ``addViewClasses()`` que unirá la vista proporcionada con -aquellas que están actualmente en la propiedad ``viewClasses``. - -.. note:: - Las clases de vista deben implementar el método estático ``contentType()`` - para participar en las negociaciones del tipo de contenido. - -Negociación de tipo de contenido alternativos -============================================= - -Si ninguna vista puede coincidir con las preferencias del tipo de contenido de la -petición, CakePHP usará la clase base ``View``. Si deseas solicitar una negociación -del tipo de contenido, puedes usar ``NegotiationRequiredView`` que setea un código -de estatus 406:: - - public function viewClasses(): array - { - // Requiere aceptar la negociación del encabezado o devuelve una respuesta 406. - return [JsonView::class, NegotiationRequiredView::class]; - } - -Puede usar el valor del tipo de contenido ``TYPE_MATCH_ALL`` para crear tu lógica -de vista alternativa:: - - namespace App\View; - - use Cake\View\View; - - class CustomFallbackView extends View - { - public static function contentType(): string - { - return static::TYPE_MATCH_ALL; - } - - } - -Es importante recordar que las vistas coincidentes se aplican sólo *después* de -intentar la negociación del tipo de contenido. - - -Redirigiendo a otras páginas -============================ - -.. php:method:: redirect(string|array $url, integer $status) - -El método ``redirect()`` agrega un encabezado ``Location`` y establece un código -de estado de una respuesta y la devuelve. Deberías devolver la respuesta creada -por ``redirect()`` para que CakePHP envíe la redirección en vez de completar -la acción del controlador y renderizar la vista. - -Puedes redigir usando los valores de un array ordenado:: - - return $this->redirect([ - 'controller' => 'Orders', - 'action' => 'confirm', - $order->id, - '?' => [ - 'product' => 'pizza', - 'quantity' => 5 - ], - '#' => 'top' - ]); - -O usando una URL relativa o absoluta:: - - return $this->redirect('/orders/confirm'); - - return $this->redirect('http://www.example.com'); - -O la referencia de la página:: - - return $this->redirect($this->referer()); - -Usando el segundo parámetro puede definir un código de estatus para tu redirección:: - - // Haz un 301 (movido permanentemente) - return $this->redirect('/order/confirm', 301); - - // Haz un 303 (Ver otro) - return $this->redirect('/order/confirm', 303); - -Reenviando a un acción en el mismo controlador ----------------------------------------------- - -.. php:method:: setAction($action, $args...) - -Si necesitas reenviar la acción actual a una acción diferente en el *mismo* controlador, -puedes usar ``Controller::setAction()`` para actualizar el objeto de la solicitud, -modifica la plantilla de vista que será renderizada y reenvía la ejecución a la -nombrada acción:: - - // Desde una acción de eliminación, puedes renderizar a lista de página - // actualizada. - $this->setAction('index'); - -Cargando modelos adicionales -============================ - -.. php:method:: fetchModel(string $alias, array $config = []) - -La función ``fetchModel()`` es útil cuando se necesita cargar un modelo o tabla del ORM que -no es la predeterminada por el controlador. Modelos obtenidos de ésta manera no serán seteados -como propiedades en el controlador:: - - // Obtiene un modelo de ElasticSearch - $articles = $this->fetchModel('Articles', 'Elastic'); - - // Obtiene un modelo de webservice - $github = $this->fetchModel('GitHub', 'Webservice'); - -.. versionadded:: 4.5.0 - -.. php:method:: fetchTable(string $alias, array $config = []) - -La función ``fetchTable()`` es útil cuando se necesita usar una tabla del ORM que no es -la predeterminada por el controlador:: - - // En un método del controlador. - $recentArticles = $this->fetchTable('Articles')->find('all', - limit: 5, - order: 'Articles.created DESC' - ) - ->all(); - -.. versionadded:: 4.3.0 - ``Controller::fetchTable()`` fue añadido. Antes de 4.3 necesitas usar ``Controller::loadModel()``. - -Paginación de un modelo -======================= - -.. php:method:: paginate() - -Este método se utiliza para paginar los resultados obtenidos por tus modelos. -Puedes especificar tamaño de páginas, condiciones de búsqueda del modelo y más. -Ve a la sección :doc:`pagination ` para más detalles -sobre como usar ``paginate()``. - -El atributo ``$paginate`` te da una manera de personalizar cómo ``paginate()`` -se comporta:: - - class ArticlesController extends AppController - { - protected array $paginate = [ - 'Articles' => [ - 'conditions' => ['published' => 1], - ], - ]; - } - -Configuración de componentes para cargar -======================================== - -.. php:method:: loadComponent($name, $config = []) - -En el método ``initialize()`` de tu controlador, puedes definir cualquier componente -que deseas cargar, y cualquier dato de configuración para ellos:: - - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('Flash'); - $this->loadComponent('Comments', Configure::read('Comments')); - } - -.. _controller-life-cycle: - -Callbacks del ciclo de vida de la petición -======================================================= - -Los controladores de CakePHP activan varios eventos/callbacks que puedes usar -para insertar lógica alrededor del ciclo de vida de la solicitud. - -Lista de eventos ----------------- - -* ``Controller.initialize`` -* ``Controller.startup`` -* ``Controller.beforeRedirect`` -* ``Controller.beforeRender`` -* ``Controller.shutdown`` - -Métodos de callback del controlador -================================================ - -Por defecto, los siguientes métodos de callback están conectados a -eventos relacionados si los métodos son implementados por tus controladores. - -.. php:method:: beforeFilter(EventInterface $event) - - Llamado durante el evento ``Controller.initialize`` que ocurre antes de cada - acción en el controlador. Es un lugar útil para comprobar si hay una sesión - activa o inspeccionar los permisos del usuario. - - .. note:: - El método beforeFilter() será llamado por acciones faltantes. - - Devolver una respuesta del método ``beforeFilter`` no evitará que otros oyentes - del mismo evento sean llamados. Debes explícitamente parar el evento. - -.. php:method:: beforeRender(EventInterface $event) - - Llamado durante el evento ``Controller.beforeRender`` que ocurre después - de la lógica de acción del controlador, pero antes de que la vista sea renderizada. - Este callback no se usa con frecuencia, pero puede ser necesaria - si estas llamando :php:meth:`~Cake\\Controller\\Controller::render()` de forma - manual antes del final de una acción dada. - -.. php:method:: afterFilter(EventInterface $event) - - Llamado durante el evento ``Controller.shutdown`` que se desencadena después - de cada acción del controlador, y después de que se complete el renderizado. - Este es el último método del controlador para ejecutar. - -Además de las devoluciones de llamada del ciclo de vida del controlador, -:doc:`/controllers/components` también proporciona un conjunto similar de devoluciones -de llamada. - -Recuerda llamar a los callbacks de ``AppController`` dentro de los callbacks -del controlador hijo para mejores resultados:: - - //use Cake\Event\EventInterface; - public function beforeFilter(EventInterface $event) - { - parent::beforeFilter($event); - } - -.. _controller-middleware: - -Middleware del controlador -========================== - -.. php:method:: middleware($middleware, array $options = []) - -:doc:`Middleware ` puede ser definido globalmente, en -un ámbito de enrutamiento o dentro de un controlador. Para definir el middleware -para un controlador en específico usa el método ``middleware()`` de tu método -``initialize()`` del controlador:: - - public function initialize(): void - { - parent::initialize(); - - $this->middleware(function ($request, $handler) { - // Haz la lógica del middleware. - - // Verifica que devuelves una respuesta o llamas a handle() - return $handler->handle($request); - }); - } - -El middleware definido por un controlador será llamado **antes** ``beforeFilter()`` -y se llamarán a los métodos de acción. - -Más sobre controladores -======================= - -.. toctree:: - :maxdepth: 1 - - controllers/pages-controller - controllers/components - -.. meta:: - :title lang=es: Controllers - :keywords lang=es: correct models,controller class,controller controller,core library,single model,request data,middle man,bakery,mvc,attributes,logic,recipes diff --git a/es/controllers/components.rst b/es/controllers/components.rst deleted file mode 100644 index 6c9adcf801..0000000000 --- a/es/controllers/components.rst +++ /dev/null @@ -1,346 +0,0 @@ -Componentes -########### - -Los componentes son paquetes de lógica que se comparten entre los controladores. -CakePHP viene un con fantástico conjunto de componentes básicos que puedes usar -para ayudar en varias tareas comunes. También puedes crear tus propios componentes. -Si te encuentras queriendo copiar y pegar cosas entre componentes, deberías considerar -crear tu propio componente que contenga la funcionalidad. Crear componentes mantiene -el código del controlador limpio y te permite rehusar código entre los diferentes -controladores. - -Para más información sobre componentes incluidos en CakePHP, consulte el capítulo -de cada componente: - -.. toctree:: - :maxdepth: 1 - - /controllers/components/flash - /controllers/components/form-protection - /controllers/components/check-http-cache - -.. _configuring-components: - -Configurando componentes -======================== - -Muchos de los componentes principales requieren configuración. Un ejemplo sería -:doc:`/controllers/components/form-protection`. La configuración para estos -componentes, y para los componentes en general, es usualmente hecho a través ``loadComponent()`` -en el método ``initialize()`` del controlador o a través del array ``$components``:: - - class PostsController extends AppController - { - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('FormProtection', [ - 'unlockedActions' => ['index'], - ]); - $this->loadComponent('Flash'); - } - } - -También puedes configurar los componentes en tiempo de ejecución usando el método -``setConfig()``. A veces, esto es hecho en el método ``beforeFilter()`` del controlador. -Lo anterior podría ser también expresado como:: - - public function beforeFilter(EventInterface $event) - { - $this->FormProtection->setConfig('unlockedActions', ['index']); - } - -Al igual que los helpers, los componentes implementan los métodos ``getConfig()`` y -``setConfig()`` para leer y escribir los datos de configuración:: - - // Lee los datos de configuración. - $this->FormProtection->getConfig('unlockedActions'); - - // Escribe los datos de configuración - $this->Flash->setConfig('key', 'myFlash'); - -Al igual que con los helpers, los componentes fusionarán automáticamente su propiedad ``$_defaultConfig`` -con la configuración del controlador para crear la propiedad ``$_config`` que es -accesible con ``getConfig()`` y ``setConfig()``. - -Componentes de alias --------------------- - -Una configuración común para usar es la opción ``className``, que te permite utilizar -componentes de alias. Esta característica es útil cuando quieres reemplazar ``$this->Flash`` -u otra referencia común de componente con una implementación personalizada:: - - // src/Controller/PostsController.php - class PostsController extends AppController - { - public function initialize(): void - { - $this->loadComponent('Flash', [ - 'className' => 'MyFlash' - ]); - } - } - - // src/Controller/Component/MyFlashComponent.php - use Cake\Controller\Component\FlashComponent; - - class MyFlashComponent extends FlashComponent - { - // Agrega tu código para sobreescribir el FlashComponent principal - } - -Lo de arriba haría *alias* ``MyFlashComponent`` a ``$this->Flash`` en tus controladores. - -.. note:: - El alias de un componente reemplaza esa instancia en cualquier lugar donde se - use ese componente, incluso dentro de otros componentes. - -Carga de componentes sobre la marcha ------------------------------------- - -Es posible que no necesites todos tus componentes disponibles en cada acción del -controlador. En situaciones como estas, puedes cargar un componente en tiempo de -ejecución usando el método ``loadComponent()`` en tu controlador:: - - // En una acción del controlador - $this->loadComponent('OneTimer'); - $time = $this->OneTimer->getTime(); - -.. note:: - - Ten en cuenta que los componentes cargados sobre la marcha no perderán devoluciones - de llamadas. Si te basas en que las devoluciones de llamada ``beforeFilter`` o - ``startup`` serán llamadas, necesitarás llamarlas manualmente dependiendo de - cuándo cargas tu componente. - -Uso de componentes -================== - -Una vez que hayas incluido algunos componentes a tu controlador, usarlos es bastante -simple. Cada componente que uses se exponen como una propiedad en tu controlador. -Si cargaste el :php:class:`Cake\\Controller\\Component\\FlashComponent` en tu controlador, -puedes acceder a él de esta forma:: - - class PostsController extends AppController - { - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('Flash'); - } - - public function delete() - { - if ($this->Post->delete($this->request->getData('Post.id')) { - $this->Flash->success('Post deleted.'); - - return $this->redirect(['action' => 'index']); - } - } - } - -.. note:: - - Dado que tanto los modelos como los componentes se agregan a los controladores - como propiedades, comparten el mismo 'espacio de nombres'. Asegúrate de no - dar a un componente y un modelo el mismo nombre. - -.. warning:: - - Los métodos de un componente **no** tienen acceso a :doc:`/development/dependency-injection` - como lo tienen los controladores. Usa una clase de servicio dentro de las acciones de tu controlador - en lugar de en el componente si necesitas ésta funcionalidad. - -.. _creating-a-component: - -Creando un componente -===================== - -Supongamos que nuestra aplicación necesita realizar una operación matemática compleja -en muchas partes diferentes de la aplicación. Podríamos crear un componente para -albergar esta lógica compartida para su uso en muchos controladores diferentes. - -El primer paso es crear un nuevo archivo de componente y clase. Crea el archivo en -**src/Controller/Component/MathComponent.php**. La estructura básica para el componente -debería verse algo como esto:: - - namespace App\Controller\Component; - - use Cake\Controller\Component; - - class MathComponent extends Component - { - public function doComplexOperation($amount1, $amount2) - { - return $amount1 + $amount2; - } - } - -.. note:: - - Todos los componentes deben extender de :php:class:`Cake\\Controller\\Component`. - De lo contrario, se disparará una excepción. - -Incluyendo tu componente en tus controladores ---------------------------------------------- - -Una vez que nuestro componente está terminado, podemos usarlo en los controladores -de la aplicación cargándolo durante el método ``initialize()`` del controlador. -Una vez cargado, el controlador recibirá un nuevo atributo con el nombre del -componente, a través del cual podemos acceder a una instancia del mismo:: - - // En un controlador - // Haz que el nuevo componente esté disponible en $this->Math, - // así como el estándar $this->Flash - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('Math'); - $this->loadComponent('Flash'); - } - -Al incluir componentes en un controlador, también puedes declarar un conjunto de -parámetros que se pasarán al constructor del componente. Estos parámetros pueden -ser manejados por el componente:: - - // En tu controlador. - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('Math', [ - 'precision' => 2, - 'randomGenerator' => 'srand' - ]); - $this->loadComponent('Flash'); - } - -Lo anterior pasaría el array que contiene precision y randomGenerator a ``MathComponent::initialize()`` -en el parámetro ``$config``. - -Usando otros componentes en tu componente ------------------------------------------ - -A veces, uno de tus componentes necesita usar otro componente. Puedes cargar otros -componentes agregándolos a la propiedad `$components`:: - - // src/Controller/Component/CustomComponent.php - namespace App\Controller\Component; - - use Cake\Controller\Component; - - class CustomComponent extends Component - { - // El otro componente que tu componente usa - protected $components = ['Existing']; - - // Ejecuta cualquier otra configuración adicional para tu componente. - public function initialize(array $config): void - { - $this->Existing->foo(); - } - - public function bar() - { - // ... - } - } - - // src/Controller/Component/ExistingComponent.php - namespace App\Controller\Component; - - use Cake\Controller\Component; - - class ExistingComponent extends Component - { - public function foo() - { - // ... - } - } - -.. note:: - - A diferencia de un componente incluido en un controlador, no se activarán - devoluciones de llamada en el componente de un componente. - -Accediendo al controlador de un componente ------------------------------------------- - -Desde dentro de un componente, puedes acceder al controlador actual a través del -registro:: - - $controller = $this->getController(); - -Devoluciones de llamadas de componentes -======================================= - -Los componentes también ofrecen algunas devoluciones de llamadas de ciclo de vida -de las solicitudes que les permiten aumentar el ciclo de solicitud. - -.. php:method:: beforeFilter(EventInterface $event) - - Es llamado antes que el método beforeFilter() del controlador, pero *después* - del método initialize() del controlador. - -.. php:method:: startup(EventInterface $event) - - Es llamado después del método beforeFilter() del controlador, pero antes de que - el controlador ejecute el "handler" de la acción actual - -.. php:method:: beforeRender(EventInterface $event) - - Es llamado después de que el controlador ejecute la lógica de la acción - solicitada, pero antes de que el controlador renderize las vistas y el diseño. - -.. php:method:: afterFilter(EventInterface $event) - - Es llamado durante el evento ``Controller.shutdown``, antes de enviar la salida al navegador. - -.. php:method:: beforeRedirect(EventInterface $event, $url, Response $response) - - Es llamado cuando el método de redirección del controlador es llamado pero - antes de cualquier otra acción. Si este método devuelve ``false`` el controlador - no continuará en redirigir la petición. Los parámetros $url y $response permiten - modificar e inspeccionar la ubicación o cualquier otro encabezado en la respuesta. - -.. _redirect-component-events: - -Usando redireccionamiento en eventos de componentes -=================================================== - -Para redirigir desde dentro de un método de devolución de llamada de un componente, -puedes usar lo siguiente:: - - public function beforeFilter(EventInterface $event) - { - $event->stopPropagation(); - - return $this->getController()->redirect('/'); - } - -Al detener el evento, le haces saber a CakePHP que no quieres ninguna otra devolución -de llamada de componente para ejecutar, y que el controlador no debe manejar la acción -más lejos. A partir de 4.1.0 puedes generar una ``RedirectException`` para señalar -una redirección:: - - use Cake\Http\Exception\RedirectException; - use Cake\Routing\Router; - - public function beforeFilter(EventInterface $event) - { - throw new RedirectException(Router::url('/')) - } - -Generar una excepción detendrá todos los demás detectores de eventos y creará -una nueva respuesta que no conserva ni hereda ninguno de los encabezados de la respuesta -actual. Al generar una ``RedirectException`` puedes incluir encabezados adicionales:: - - throw new RedirectException(Router::url('/'), 302, [ - 'Header-Key' => 'value', - ]); - -.. versionadded:: 4.1.0 - -.. meta:: - :title lang=es: Componentes - :keywords lang=en: array controller,core libraries,array name,access control lists,public components,controller code,core components,cookiemonster,login cookie,configuration settings,functionality,logic,sessions,cakephp,doc diff --git a/es/controllers/components/check-http-cache.rst b/es/controllers/components/check-http-cache.rst deleted file mode 100644 index 0324b1a250..0000000000 --- a/es/controllers/components/check-http-cache.rst +++ /dev/null @@ -1,11 +0,0 @@ -Checking HTTP Cache -=================== - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. \ No newline at end of file diff --git a/es/controllers/components/flash.rst b/es/controllers/components/flash.rst deleted file mode 100644 index 57f94b1065..0000000000 --- a/es/controllers/components/flash.rst +++ /dev/null @@ -1,11 +0,0 @@ -FlashComponent -############## - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. diff --git a/es/controllers/components/form-protection.rst b/es/controllers/components/form-protection.rst deleted file mode 100644 index 4ccf013abd..0000000000 --- a/es/controllers/components/form-protection.rst +++ /dev/null @@ -1,15 +0,0 @@ -FormProtection -############## - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. - -.. meta:: - :title lang=en: FormProtection - :keywords lang=en: configurable parameters,form protection component,configuration parameters,protection features,tighter security,php class,meth,array,submission,security class,disable security,unlockActions \ No newline at end of file diff --git a/es/controllers/middleware.rst b/es/controllers/middleware.rst deleted file mode 100644 index cefa1954e0..0000000000 --- a/es/controllers/middleware.rst +++ /dev/null @@ -1,296 +0,0 @@ -Middleware -########## - -Los objetos del middleware le dan la posibilidad de "envolver" su aplicación en -capas compuestas y reutilizables de la lógica de manejo de la solicitud -o respuesta. Visualmente, su aplicación termina en el centro y el middleware se -envuelve en la aplicación como una cebolla. Aquí podemos ver una aplicación con -enrutamiento, activos, manejo de excepciones y middleware de encabezado CORS. - -.. image:: /_static/img/middleware-setup.png - - -Cuando una solicitud es manejada por su aplicación, ésta ingresa desde el -extremo del middleware. Cada middleware puede delegar la solicitud / respuesta -a la siguiente capa, o devolver una respuesta. Devolver una respuesta evita que -las capas inferiores siempre vean la solicitud. Un ejemplo de eso es el -AssetMiddleware que maneja una solicitud de una imagen de un plugin durante el -desarrollo. - -.. image:: /_static/img/middleware-request.png - -Si ningún middleware toma medidas para manejar la solicitud, se ubicará un -controlador y se invocará su acción, o se generará una excepción generando una -página de error. - -El middleware es parte de la nueva pila HTTP en CakePHP que aprovecha las -interfaces de solicitud y respuesta del PSR-7. Debido a que CakePHP está -aprovechando el estándar PSR-7, puede usar cualquier middleware compatible con -PSR-7 disponible en The `Packagist `__. - -Middleware en CakePHP -===================== - -CakePHP proporciona varios middleware para manejar tareas comunes en aplicaciones web: - -* ``Cake\Error\Middleware\ErrorHandlerMiddleware`` atrapa las excepciones del middleware envuelto - y presenta una página de error usando el controlador de - :doc:`/development/errors` excepciones de manejo de errores y excepciones. -* ``Cake\Routing\AssetMiddleware`` comprueba si la solicitud se refiere a un archivo - de tema o complemento, como un archivo CSS, JavaScript o de imagen almacenado en - la carpeta webroot de un complemento o la correspondiente para un Tema. -* ``Cake\Routing\Middleware\RoutingMiddleware`` utiliza ``Router`` para analizar la URL entrante - y asignar parámetros de enrutamiento a la solicitud. -* ``Cake\I18n\Middleware\LocaleSelectorMiddleware`` permite el cambio automático de idioma desde - el ``Accept-Languageencabezado`` enviado por el navegador. -* ``Cake\Http\Middleware\SecurityHeadersMiddleware`` facilita la adición de encabezados relacionados - con la seguridad ``X-Frame-Options`` a las respuestas. -* ``Cake\Http\Middleware\EncryptedCookieMiddleware`` le brinda la capacidad de manipular cookies - encriptadas en caso de que necesite manipular las cookies con datos confusos. -* ``Cake\Http\Middleware\CsrfProtectionMiddleware`` agrega protección CSRF a su aplicación. -* ``Cake\Http\Middleware\BodyParserMiddleware`` le permite decodificar JSON, XML y otros cuerpos - de solicitud codificados basados ​​en ``Content-Type`` del encabezado. - -.. _using-middleware: - -Usando Middlewares -================== - -Los Middlewares pueden ser agregados a tu aplicación globalmente, a rutas específicas o incluso controladores. - -Para agregar un middleware a todos los requests, usa el método ``middleware`` de tu -clase ``App\Application``. Este método se llama al inicio del procesamiento del request. -Puedes usar el objeto ``MiddlewareQueue`` para agregar el middleware:: - - namespace App; - - use Cake\Http\BaseApplication; - use Cake\Http\MiddlewareQueue; - use Cake\Error\Middleware\ErrorHandlerMiddleware; - - class Application extends BaseApplication - { - public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue - { - // Agregar el manejador de errores en la cola de middlewares. - $middlewareQueue->add(new ErrorHandlerMiddleware()); - - // Agregar middleware por clase. - // Desde la versión 4.5.0 el nombre de la clase del middleware es - // resuelto opcionalmente usando el contener DI. Si la clase no es encontrada - // en el contenedor, entonces una instancia es creada por la cola de middlewares - $middlewareQueue->add(UserRateLimiting::class); - - return $middlewareQueue; - } - } - -Adicionalmente, aparte de agregarlo al final de la ``MiddlewareQueue``, puedes realizar distintas -operaciones:: - - $layer = new \App\Middleware\CustomMiddleware; - - // El middleware agregado será el último de la cola. - $middlewareQueue->add($layer); - - // El middleware agregado será el primero de la cola. - $middlewareQueue->prepend($layer); - - // Inserta el middleware en un lugar especifico. Si el lugar no existe, - // será agregado al final - $middlewareQueue->insertAt(2, $layer); - - // Inserta el middleware antes de otro. - // Si el middleware especificado no existe, - // se lanzará una excepción - $middlewareQueue->insertBefore( - 'Cake\Error\Middleware\ErrorHandlerMiddleware', - $layer - ); - - // Inserta después que otro middleware - // Si el middleware especificado no existe, - // el middleware will added to the end. - $middlewareQueue->insertAfter( - 'Cake\Error\Middleware\ErrorHandlerMiddleware', - $layer - ); - - -Si tu middleware solo es aplicable a un subconjunto de rutas o controladores especificos puedes usar -:ref:`Middleware por Rutas `, o :ref:`Middleware por Controlador `. - -Agregando Middleware desde un Plugin -------------------------------------- - -Los Plugins pueden usar su propio método ``middleware`` para agregar cualquier middleware que -implementen a la cola de middlewares de la aplicación:: - - // en plugins/ContactManager/src/Plugin.php - namespace ContactManager; - - use Cake\Core\BasePlugin; - use Cake\Http\MiddlewareQueue; - use ContactManager\Middleware\ContactManagerContextMiddleware; - - class Plugin extends BasePlugin - { - public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue - { - $middlewareQueue->add(new ContactManagerContextMiddleware()); - - return $middlewareQueue; - } - } - -Creaando un Middleware -====================== - -Un Middleware puede ser implementado mediante funciones anónimas (Closures), o clases que extiendan -a ``Psr\Http\Server\MiddlewareInterface``. Mientras que los Closures son apropiados para -tareas pequeñas, las pruebas se vuelven complicadas y puedes complicar aún más la clase -``Application``. Las clases middleware en CakePHP tienen algunasconvenciones: - -* Los archivos deben ubicarse en **src/Middleware**. Por ejemplo: - **src/Middleware/CorsMiddleware.php** -* Deben tener ``Middleware`` como sufijo. Por ejemplo: - ``LinkMiddleware``. -* Deben implementar la interfaz ``Psr\Http\Server\MiddlewareInterface``. - -Un Middleware puede devolver la respuesta llamando a ``$handler->handle()`` o -creando su propia respuesta. Podemos ver ambas opciones en el siguiente ejemplo:: - - // En src/Middleware/TrackingCookieMiddleware.php - namespace App\Middleware; - - use Cake\Http\Cookie\Cookie; - use Cake\I18n\Time; - use Psr\Http\Message\ResponseInterface; - use Psr\Http\Message\ServerRequestInterface; - use Psr\Http\Server\RequestHandlerInterface; - use Psr\Http\Server\MiddlewareInterface; - - class TrackingCookieMiddleware implements MiddlewareInterface - { - public function process( - ServerRequestInterface $request, - RequestHandlerInterface $handler - ): ResponseInterface - { - // Llamando $handler->handle() delega el control al siguiente middleware - // en la cola de tu aplicación. - $response = $handler->handle($request); - - if (!$request->getCookie('landing_page')) { - $expiry = new Time('+ 1 year'); - $response = $response->withCookie(new Cookie( - 'landing_page', - $request->getRequestTarget(), - $expiry - )); - } - - return $response; - } - } - -Ahora que hemos hecho un middleware bastante simple, agreguémoslo a nuestra aplicación:: - - // En src/Application.php - namespace App; - - use App\Middleware\TrackingCookieMiddleware; - use Cake\Http\MiddlewareQueue; - - class Application - { - public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue - { - // Agrega tu middleware a la cola - $middlewareQueue->add(new TrackingCookieMiddleware()); - - // Agrega más middlewares a la cola si lo deseas - - return $middlewareQueue; - } - } - - -.. _routing-middleware: - -Middleware Routing -================== - -El middleware de enrutamiento es responsable de procesar las rutas de tu aplicación e -identificar el plugin, controlador, y acción hacia la cual va un request:: - - // En Application.php - public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue - { - // ... - $middlewareQueue->add(new RoutingMiddleware($this)); - } - -.. _encrypted-cookie-middleware: - -Middleware EncryptedCookie -=========================== - -Si tu aplicación tiene cookies que contienen información que -quieres ofuscar y proteger, puedes usar el middleware de Cookies encriptadas -de CakePHP para encriptar y desencriptar de manera transparente la información -vía middleware. La información del Cookie es encriptada vía OpenSSL using AES:: - - use Cake\Http\Middleware\EncryptedCookieMiddleware; - - $cookies = new EncryptedCookieMiddleware( - // Names of cookies to protect - ['secrets', 'protected'], - Configure::read('Security.cookieKey') - ); - - $middlewareQueue->add($cookies); - -.. note:: - Se recomienda que la clave de encriptación que se utiliza para la información - del Cookie sea **exclusivamente** para esto. - -Los algoritmos de encriptación y el estilo de relleno usado por el middleware son retrocompatibles -con el ``CookieComponent`` de versiones anteriores de CakePHP. - -.. _body-parser-middleware: - -Middleware BodyParser -====================== - -Si tu aplicación acepta JSON, XML o algún `request` de este tipo, el -``BodyParserMiddleware`` te permitirá decodificar esos `requests` en un arreglo que -estará disponible via ``$request->getParsedData()`` y ``$request->getData()``. Por defecto sólo -``json`` será procesado, pero el procesamiento XML puede ser activado como opción. -También puedes definir tus propios procesadores:: - - use Cake\Http\Middleware\BodyParserMiddleware; - - // solo JSON será procesado - $bodies = new BodyParserMiddleware(); - - // Activar procesamiento XML - $bodies = new BodyParserMiddleware(['xml' => true]); - - // Desactivar procesamiento JSON - $bodies = new BodyParserMiddleware(['json' => false]); - - // Agregar tu propio procesador aplicándolo a un content-type - // específico y asignandole una funcion de procesamiento - $bodies = new BodyParserMiddleware(); - $bodies->addParser(['text/csv'], function ($body, $request) { - // Use a CSV parsing library. - return Csv::parse($body); - }); - -.. meta:: - :title lang=es: Http Middleware - :keywords lang=es: http, middleware, psr-7, request, response, wsgi, application, baseapplication - - - diff --git a/es/controllers/pages-controller.rst b/es/controllers/pages-controller.rst deleted file mode 100644 index bcca9f401a..0000000000 --- a/es/controllers/pages-controller.rst +++ /dev/null @@ -1,17 +0,0 @@ -El controlador de Páginas -######################### - -El esqueleto oficial de CakePHP incluye un controlador por defecto **PagesController.php**. -Este es un controlador simple y opcional que se usa para servir contenido estático. -La página home que ves después de la instalación es generada usando este controlador -y el archivo de vista **templates/Pages/home.php**. Si se crea el archivo de vista -**templates/Pages/about_us.php** se podrá acceder a este usando la URL -**http://example.com/pages/about_us**. Sientete libre de modificar el controlador -para que cumpla con tus necesidades. - -Cuando se cocina una app usando Composer el controlador es creado en la carpeta -**src/Controller/**. - -.. meta:: - :title lang=es: El Controlador de Páginas - :keywords lang=es: controlador pages, pages controller,default controller,cakephp,ships,php,file folder,home page diff --git a/es/controllers/pagination.rst b/es/controllers/pagination.rst deleted file mode 100644 index 63c3a4a4ea..0000000000 --- a/es/controllers/pagination.rst +++ /dev/null @@ -1,13 +0,0 @@ -Paginación -########## - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta - página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón - **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección - superior para obtener información sobre el tema de esta página. diff --git a/es/controllers/request-response.rst b/es/controllers/request-response.rst deleted file mode 100644 index 860cf851be..0000000000 --- a/es/controllers/request-response.rst +++ /dev/null @@ -1,1118 +0,0 @@ -Objetos de Solicitud y Respuesta -################################ - -.. php:namespace:: Cake\Http - -Los objetos de solicitud y respuesta proporcionan una abstracción en torno a las solicitudes y respuestas HTTP. El objeto -de solicitud en CakePHP le permite realizar una introspección de una solicitud entrante, mientras que el objeto de -respuesta le permite crear respuestas HTTP sin esfuerzo desde sus controladores. - -.. index:: $this->request -.. _cake-request: - -Solicitud (Request) -=================== - -.. php:class:: ServerRequest - -``ServerRequest`` es el objeto de solicitud predeterminado utilizado en CakePHP. Centraliza una serie de funciones para -interrogar e interactuar con los datos de la solicitud. En cada solicitud, se crea un Request y luego se pasa por -referencia a las distintas capas de una aplicación que utiliza datos de solicitud. De forma predeterminada, la solicitud -se asigna a ``$this->request`` y está disponible en Controladores, Celdas, Vistas y Ayudantes. También puede acceder a él -en Componentes usando la referencia del controlador. - -Algunas de las tareas que realiza ``ServerRequest`` incluyen: - -* Procesar los arreglos GET, POST y FILES en las estructuras de datos con las que está familiarizado. -* Proporcionar una introspección del entorno correspondiente a la solicitud. Información como los encabezados enviados, - la dirección IP del cliente y los nombres de subdominio/dominio en el servidor en el que se ejecuta su aplicación. -* Proporcionar acceso a los parámetros de solicitud tanto como índices de matriz como propiedades de objetos. - -El objeto de la solicitud de CakePHP implementa `PSR-7 ServerRequestInterface `_, lo que -facilita el uso de bibliotecas desde fuera de CakePHP. - -.. _request-parameters: - -Parámetros de la solicitud --------------------------- - -La solicitud expone los parámetros de enrutamiento a través del método ``getParam()``:: - - $controllerName = $this->request->getParam('controller'); - -Para obtener todos los parámetros de enrutamiento como una matriz, use ``getAttribute()``:: - - $parameters = $this->request->getAttribute('params'); - -Se accede a todos los :ref:`route-elements` a través de esta interfaz. - -Además de :ref:`route-elements`, a menudo también necesita acceso a :ref:`passed-arguments`. Ambos también están -disponibles en el objeto de solicitud: - - // Argumentos pasados - $passedArgs = $this->request->getParam('pass'); - -Todos le proporcionarán acceso a los argumentos pasados. Hay varios parámetros importantes/útiles que CakePHP usa -internamente, y todos ellos también se encuentran en los parámetros de enrutamiento: - -* ``plugin`` El complemento que maneja la solicitud. Será nulo cuando no haya ningún complemento. -* ``controller`` El controlador que maneja la solicitud actual. -* ``action`` La acción que maneja la solicitud actual. -* ``prefix`` El prefijo de la acción actual. Consulte :ref:`prefix-routing` para obtener más información. - -Parámetros de cadena de consulta --------------------------------- - -.. php:method:: getQuery($name, $default = null) - -Los parámetros de la cadena de consulta se pueden leer usando el método ``getQuery()``:: - - // URL es /posts/index?page=1&sort=title - $page = $this->request->getQuery('page'); - -Puede acceder directamente a la propiedad de consulta o puede utilizar el método ``getQuery()`` para leer la matriz de -consulta de URL sin errores. Cualquier clave que no exista devolverá ``null``:: - - $foo = $this->request->getQuery('value_that_does_not_exist'); - // $foo === null - - // También puede proporcionar valores predeterminados - $foo = $this->request->getQuery('does_not_exist', 'default val'); - -Si desea acceder a todos los parámetros de consulta, puede utilizar ``getQueryParams()``: - - $query = $this->request->getQueryParams(); - -Datos del cuerpo de la solicitud --------------------------------- - -.. php:method:: getData($name, $default = null) - -Se puede acceder a todos los datos POST normalmente disponibles a través de la variable global ``$_POST`` de PHP usando -:php:meth:`Cake\\Http\\ServerRequest::getData()`. Por ejemplo:: - - // Se puede acceder a una entrada con un atributo de nombre 'título' - $title = $this->request->getData('title'); - -Puede utilizar nombres separados por puntos para acceder a datos anidados. Por ejemplo:: - - $value = $this->request->getData('address.street_name'); - -Para nombres inexistentes se devolverá el valor ``$default``:: - - $foo = $this->request->getData('value.that.does.not.exist'); - // $foo == null - -También puede utilizar :ref:`body-parser-middleware` para analizar el cuerpo de la solicitud de diferentes tipos de -contenido en una matriz, de modo que sea accesible a través de ``ServerRequest::getData()``. - -Si desea acceder a todos los parámetros de datos, puede utilizar -``getParsedBody()``:: - - $data = $this->request->getParsedBody(); - -.. _request-file-uploads: - -Cargas de archivos ------------------- - -Se puede acceder a los archivos cargados a través de los datos del cuerpo de la solicitud, utilizando el método -:php:meth:`Cake\\Http\\ServerRequest::getData()` descrito anteriormente. Por ejemplo, se puede acceder a un archivo desde -un elemento de entrada con un atributo de nombre ``attachment`` de esta manera:: - - $attachment = $this->request->getData('attachment'); - -De forma predeterminada, las cargas de archivos se representan en los datos de la solicitud como objetos que implementan -`\\Psr\\Http\\Message\\UploadedFileInterface `__. En la -implementación actual, la variable ``$attachment`` en el ejemplo anterior contendría de forma predeterminada una -instancia de ``\Laminas\Diactoros\UploadedFile``. - -Acceder a los detalles del archivo cargado es bastante simple, así es como puede obtener los mismos datos que proporciona -la matriz de carga de archivos de estilo antiguo: - - $name = $attachment->getClientFilename(); - $type = $attachment->getClientMediaType(); - $size = $attachment->getSize(); - $tmpName = $attachment->getStream()->getMetadata('uri'); - $error = $attachment->getError(); - -Mover el archivo cargado desde su ubicación temporal a la ubicación de destino deseada no requiere acceder manualmente al -archivo temporal, sino que se puede hacer fácilmente usando el método ``moveTo()`` del objeto:: - - $attachment->moveTo($targetPath); - -En un entorno HTTP, el método ``moveTo()`` validará automáticamente si el archivo es un archivo cargado real y generará -una excepción en caso de que sea necesario. En un entorno CLI, donde no existe el concepto de cargar archivos, permitirá -mover el archivo al que ha hecho referencia independientemente de sus orígenes, lo que hace posible probar la carga de -archivos. - -.. php:method:: getUploadedFile($path) - -Devuelve el archivo cargado en una ruta específica. La ruta utiliza la misma sintaxis de puntos que el método -:php:meth:`Cake\\Http\\ServerRequest::getData()`:: - - $attachment = $this->request->getUploadedFile('attachment'); - -A diferencia de :php:meth:`Cake\\Http\\ServerRequest::getData()`, :php:meth:`Cake\\Http\\ServerRequest::getUploadedFile()` -solo devolvería datos cuando exista una carga de archivo real para la ruta dada, si hay datos regulares del cuerpo de la -solicitud que no son archivos presentes en la ruta dada, entonces este método devolverá "nulo", tal como lo haría para -cualquier ruta inexistente. - -.. php:method:: getUploadedFiles() - -Devuelve todos los archivos cargados en una estructura de matriz normalizada. Para el ejemplo anterior con el nombre de -entrada del archivo ``attachment``, la estructura se vería así:: - - [ - 'attachment' => object(Laminas\Diactoros\UploadedFile) { - // ... - } - ] - -.. php:method:: withUploadedFiles(array $files) - -Este método establece los archivos cargados del objeto de solicitud, acepta una matriz de objetos que implementan -`\\Psr\\Http\\Message\\UploadedFileInterface `__. Reemplazará -todos los archivos cargados posiblemente existentes:: - - $files = [ - 'MyModel' => [ - 'attachment' => new \Laminas\Diactoros\UploadedFile( - $streamOrFile, - $size, - $errorStatus, - $clientFilename, - $clientMediaType - ), - 'anotherAttachment' => new \Laminas\Diactoros\UploadedFile( - '/tmp/hfz6dbn.tmp', - 123, - \UPLOAD_ERR_OK, - 'attachment.txt', - 'text/plain' - ), - ], - ]; - - $this->request = $this->request->withUploadedFiles($files); - -.. note:: - - Los archivos cargados que se agregaron a la solicitud a través de este método *no* estarán disponibles en los datos - del cuerpo de la solicitud, es decir, no puede recuperarlos a través de - :php:meth:`Cake\\Http\\ServerRequest::getData()` ! Si los necesita en los datos de la solicitud (también), entonces - debe configurarlos mediante :php:meth:`Cake\\Http\\ServerRequest::withData()` o - :php:meth:`Cake\\Http\ \ServerRequest::withParsedBody()`. - -PUT, PATCH o DELETE Datos -------------------------- - -.. php:method:: input($callback, [$options]) - -Al crear servicios REST, a menudo se aceptan datos de solicitud en solicitudes ``PUT`` y ``DELETE``. Cualquier dato del -cuerpo de solicitud ``application/x-www-form-urlencoded`` se analizará automáticamente y se establecerá en -``$this->data`` para las solicitudes ``PUT`` y ``DELETE``. Si acepta datos JSON o XML, consulte a continuación cómo puede -acceder a esos cuerpos de solicitud. - -Al acceder a los datos de entrada, puede decodificarlos con una función opcional. Esto resulta útil al interactuar con el -contenido del cuerpo de la solicitud XML o JSON. Se pueden pasar parámetros adicionales para la función de decodificación -como argumentos a ``input()``:: - - $jsonData = $this->request->input('json_decode'); - -Variables de entorno (de $_SERVER y $_ENV) ------------------------------------------- - -.. php:method:: putenv($key, $value = null) - -``ServerRequest::getEnv()`` es un contenedor para la función global ``getenv()`` y actúa como un captador/establecedor de -variables de entorno sin tener que modificar los globales ``$_SERVER`` y ``$_ENV``:: - - // Obtener el host - $host = $this->request->getEnv('HTTP_HOST'); - - // Establecer un valor, generalmente útil en las pruebas. - $this->request->withEnv('REQUEST_METHOD', 'POST'); - -Para acceder a todas las variables de entorno en una solicitud, utilice ``getServerParams()``:: - - $env = $this->request->getServerParams(); - -Datos XML o JSON ----------------- - -Las aplicaciones que emplean :doc:`/development/rest` a menudo intercambian datos en cuerpos de publicaciones sin -codificación URL. Puede leer datos de entrada en cualquier formato usando :php:meth:`~Cake\\Http\\ServerRequest::input()`. -Al proporcionar una función de decodificación, puede recibir el contenido en un formato deserializado:: - - // Obtenga datos codificados en JSON enviados a una acción PUT/POST - $jsonData = $this->request->input('json_decode'); - -Algunos métodos de deserialización requieren parámetros adicionales cuando se llaman, como el parámetro 'as array' en -``json_decode``. Si desea convertir XML en un objeto DOMDocument, :php:meth:`~Cake\\Http\\ServerRequest::input()` también -admite el paso de parámetros adicionales:: - - // Obtener datos codificados en XML enviados a una acción PUT/POST - $data = $this->request->input('Cake\Utility\Xml::build', ['return' => 'domdocument']); - -Información de ruta -------------------- - -El objeto de solicitud también proporciona información útil sobre las rutas de su aplicación. Los atributos ``base`` y -``webroot`` son útiles para generar URL y determinar si su aplicación está o no en un subdirectorio. Los atributos que -puedes utilizar son: - - // Supongamos que la URL de solicitud actual es /subdir/articles/edit/1?page=1 - - // Contiene /subdir/articles/edit/1?page=1 - $here = $request->getRequestTarget(); - - // Contiene /subdir - $base = $request->getAttribute('base'); - - // Contiene /subdir/ - $base = $request->getAttribute('webroot'); - -.. _check-the-request: - -Comprobación de las condiciones de la solicitud ------------------------------------------------ - -.. php:method:: is($type, $args...) - -El objeto de solicitud proporciona una forma de inspeccionar ciertas condiciones en una solicitud determinada. Al -utilizar el método ``is()``, puede comprobar una serie de condiciones comunes, así como inspeccionar otros criterios de -solicitud específicos de la aplicación: - - $isPost = $this->request->is('post'); - -También puede ampliar los detectores de solicitudes que están disponibles, utilizando -:php:meth:`Cake\\Http\\ServerRequest::addDetector()` para crear nuevos tipos de detectores. Hay diferentes tipos de -detectores que puedes crear: - -* Comparación de valores del entorno: compara un valor obtenido de :php:func:`env()` para determinar su igualdad con el - valor proporcionado. -* Comparación del valor del encabezado: si el encabezado especificado existe con el valor especificado o si el invocable - devuelve verdadero. -* Comparación de valores de patrón: la comparación de valores de patrón le permite comparar un valor obtenido de - :php:func:`env()` con una expresión regular. -* Comparación basada en opciones: las comparaciones basadas en opciones utilizan una lista de opciones para crear una - expresión regular. Las llamadas posteriores para agregar un detector de opciones ya definido fusionarán las opciones. -* Detectores de devolución de llamada: los detectores de devolución de llamada le permiten proporcionar un tipo de - "callback" para manejar la verificación. La devolución de llamada recibirá el objeto de solicitud como único parámetro. - -.. php:method:: addDetector($name, $options) - -Algunos ejemplos serían:: - - // Agregue un detector de entorno. - $this->request->addDetector( - 'post', - ['env' => 'REQUEST_METHOD', 'value' => 'POST'] - ); - - // Agregue un detector de valor de patrón. - $this->request->addDetector( - 'iphone', - ['env' => 'HTTP_USER_AGENT', 'pattern' => '/iPhone/i'] - ); - - // Agregar un detector de opciones - $this->request->addDetector('internalIp', [ - 'env' => 'CLIENT_IP', - 'options' => ['192.168.0.101', '192.168.0.100'] - ]); - - - // Agregue un detector de encabezado con comparación de valores - $this->request->addDetector('fancy', [ - 'env' => 'CLIENT_IP', - 'header' => ['X-Fancy' => 1] - ]); - - // Agregue un detector de encabezado con comparación invocable - $this->request->addDetector('fancy', [ - 'env' => 'CLIENT_IP', - 'header' => ['X-Fancy' => function ($value, $header) { - return in_array($value, ['1', '0', 'yes', 'no'], true); - }] - ]); - - // Agregue un detector de devolución de llamada. Debe ser un invocable válido. - $this->request->addDetector( - 'awesome', - function ($request) { - return $request->getParam('awesome'); - } - ); - - // Agregue un detector que use argumentos adicionales. - $this->request->addDetector( - 'csv', - [ - 'accept' => ['text/csv'], - 'param' => '_ext', - 'value' => 'csv', - ] - ); - -Hay varios detectores integrados que puedes utilizar: - -* ``is('get')`` Verifique si la solicitud actual es un GET. -* ``is('put')`` Verifique si la solicitud actual es un PUT. -* ``is('patch')`` Verifique si la solicitud actual es un PATCH. -* ``is('post')`` Verifique si la solicitud actual es una POST. -* ``is('delete')`` Verifique si la solicitud actual es DELETE. -* ``is('head')`` Verifique si la solicitud actual es HEAD. -* ``is('options')`` Verifique si la solicitud actual es OPTIONS. -* ``is('ajax')`` Verifique si la solicitud actual vino con X-Requested-With = XMLHttpRequest. -* ``is('ssl')`` Compruebe si la solicitud se realiza a través de SSL. -* ``is('flash')`` Verifique si la solicitud tiene un User-Agent de Flash. -* ``is('json')`` Verifique si la solicitud tiene la extensión 'json' y acepte el tipo mime 'application/json'. -* ``is('xml')`` Verifique si la solicitud tiene la extensión 'xml' y acepte el tipo mime 'application/xml' o 'text/xml'. - -``ServerRequest`` También incluye métodos como :php:meth:`Cake\\Http\\ServerRequest::domain()`, -:php:meth:`Cake\\Http\\ServerRequest::subdomains()` y :php:meth:`Cake\\Http\\ServerRequest::host()` para simplificar las -aplicaciones que utilizan subdominios. - -Datos de sesión ---------------- - -Para acceder a la sesión para una solicitud determinada utilice el método ``getSession()`` o utilice el atributo -``session``:: - - $session = $this->request->getSession(); - $session = $this->request->getAttribute('session'); - - $data = $session->read('sessionKey'); - -Para obtener más información, consulte la documentación :doc:`/development/sessions` sobre cómo utilizar el objeto de -sesión. - -Host y nombre de dominio ------------------------- - -.. php:method:: domain($tldLength = 1) - -Devuelve el nombre de dominio en el que se ejecuta su aplicación:: - - // Muestra 'example.org' - echo $request->domain(); - -.. php:method:: subdomains($tldLength = 1) - -Devuelve los subdominios en los que se ejecuta su aplicación como una matriz:: - - // Regresa ['my', 'dev'] de 'my.dev.example.org' - $subdomains = $request->subdomains(); - -.. php:method:: host() - -Devuelve el host en el que se encuentra su aplicación:: - - // Muestra 'my.dev.example.org' - echo $request->host(); - -Leyendo el método HTTP ----------------------- - -.. php:method:: getMethod() - -Devuelve el método HTTP con el que se realizó la solicitud:: - - // Salida POST - echo $request->getMethod(); - -Restringir qué método HTTP acepta una acción --------------------------------------------- - -.. php:method:: allowMethod($methods) - -Establecer métodos HTTP permitidos. Si no coincide, arrojará ``MethodNotAllowedException``. La respuesta 405 incluirá el -encabezado ``Allow`` requerido con los métodos pasados:: - - public function delete() - { - // Solo acepte solicitudes POST y DELETE - $this->request->allowMethod(['post', 'delete']); - ... - } - -Lectura de encabezados HTTP ---------------------------- - -Le permite acceder a cualquiera de los encabezados ``HTTP_*`` que se utilizaron para la solicitud. Por ejemplo:: - - // Obtener el encabezado como una cadena - $userAgent = $this->request->getHeaderLine('User-Agent'); - - // Obtenga una matriz de todos los valores. - $acceptHeader = $this->request->getHeader('Accept'); - - // Comprobar si existe un encabezado - $hasAcceptHeader = $this->request->hasHeader('Accept'); - -Si bien algunas instalaciones de Apache no hacen que el encabezado ``Authorization`` sea accesible, CakePHP lo hará -disponible a través de métodos específicos de Apache según sea necesario. - -.. php:method:: referer($local = true) - -Devuelve la dirección de referencia de la solicitud. - -.. php:method:: clientIp() - -Devuelve la dirección IP del visitante actual. - -Confiar en los encabezados de proxy ------------------------------------ - -Si su aplicación está detrás de un balanceador de carga o se ejecuta en un servicio en la nube, a menudo obtendrá el -host, el puerto y el esquema del balanceador de carga en sus solicitudes. A menudo, los balanceadores de carga también -enviarán encabezados ``HTTP-X-Forwarded-*`` con los valores originales. CakePHP no utilizará los encabezados reenviados -de fábrica. Para que el objeto de solicitud utilice estos encabezados, establezca la propiedad ``trustProxy`` en ``true``:: - - $this->request->trustProxy = true; - - // Estos métodos ahora utilizarán los encabezados proxy. - $port = $this->request->port(); - $host = $this->request->host(); - $scheme = $this->request->scheme(); - $clientIp = $this->request->clientIp(); - -Una vez que se confía en los servidores proxy, el método ``clientIp()`` utilizará la *última* dirección IP en el -encabezado ``X-Forwarded-For``. Si su aplicación está detrás de varios servidores proxy, puede usar -``setTrustedProxies()`` para definir las direcciones IP de los servidores proxy bajo su control:: - - $request->setTrustedProxies(['127.1.1.1', '127.8.1.3']); - -Después de que los servidores proxy sean confiables, ``clientIp()`` usará la primera dirección IP en el encabezado -``X-Forwarded-For`` siempre que sea el único valor que no provenga de un proxy confiable. - -Comprobando encabezados aceptados ---------------------------------- - -.. php:method:: accepts($type = null) - -Descubra qué tipos de contenido acepta el cliente o compruebe si acepta un tipo de contenido en particular. - -Consigue todos los tipos:: - - $accepts = $this->request->accepts(); - -Consulta por un solo tipo:: - - $acceptsJson = $this->request->accepts('application/json'); - -.. php:method:: acceptLanguage($language = null) - -Obtenga todos los idiomas aceptados por el cliente o verifique si se acepta un idioma específico. - -Obtenga la lista de idiomas aceptados:: - - $acceptsLanguages = $this->request->acceptLanguage(); - -Compruebe si se acepta un idioma específico:: - - $acceptsSpanish = $this->request->acceptLanguage('es-es'); - -.. _request-cookies: - -Leyendo Cookies ---------------- - -Las cookies de solicitud se pueden leer a través de varios métodos: - - // Obtenga el valor de la cookie, o nulo si falta la cookie. - $rememberMe = $this->request->getCookie('remember_me'); - - // Lea el valor u obtenga el valor predeterminado de 0 - $rememberMe = $this->request->getCookie('remember_me', 0); - - // Obtener todas las cookies como hash - $cookies = $this->request->getCookieParams(); - - // Obtener una instancia de CookieCollection - $cookies = $this->request->getCookieCollection() - -Consulte la documentación :php:class:`Cake\\Http\\Cookie\\CookieCollection` para saber cómo trabajar con la recopilación -de cookies. - -Archivos cargados ------------------ - -Las solicitudes exponen los datos del archivo cargado en ``getData()`` o ``getUploadedFiles()`` como objetos -``UploadedFileInterface``:: - - // Obtener una lista de objetos UploadedFile - $files = $request->getUploadedFiles(); - - // Lea los datos del archivo. - $files[0]->getStream(); - $files[0]->getSize(); - $files[0]->getClientFileName(); - - // Mover el archivo - $files[0]->moveTo($targetPath); - -Manipulación de URI -------------------- - -Las solicitudes contienen un objeto URI, que contiene métodos para interactuar con el URI solicitado:: - - // Obtener la URI - $uri = $request->getUri(); - - // Leer datos de la URI. - $path = $uri->getPath(); - $query = $uri->getQuery(); - $host = $uri->getHost(); - - -.. index:: $this->response - -Respueta (Response) -=================== - -.. php:class:: Response - -:php:class:`Cake\\Http\\Response` es la clase de respuesta predeterminada en CakePHP. Encapsula una serie de -características y funcionalidades para generar respuestas HTTP en su aplicación. También ayuda en las pruebas, ya que se -puede simular o eliminar, lo que le permite inspeccionar los encabezados que se enviarán. - -``Response`` proporciona una interfaz para envolver las tareas comunes relacionadas con la respuesta, como por ejemplo: - -* Envío de encabezados para redireccionamientos. -* Envío de encabezados de tipo de contenido. -* Envío de cualquier encabezado. -* Envío del cuerpo de la respuesta. - -Tratar con tipos de contenido ------------------------------ - -.. php:method:: withType($contentType = null) - -Puede controlar el tipo de contenido de las respuestas de su aplicación con :php:meth:`Cake\\Http\\Response::withType()`. -Si su aplicación necesita manejar tipos de contenido que no están integrados en Response, también puede asignarlos con -``setTypeMap()``:: - - // Agregar un tipo de vCard - $this->response->setTypeMap('vcf', ['text/v-card']); - - // Establezca el tipo de contenido de respuesta en vcard - $this->response = $this->response->withType('vcf'); - -Por lo general, querrás asignar tipos de contenido adicionales en la devolución de llamada de tu controlador -:php:meth:`~Controller::beforeFilter()`, para poder aprovechar las funciones de cambio automático de vista de -:php:class:`RequestHandlerComponent` si lo están usando. - -.. _cake-response-file: - -Enviando arhivos ----------------- - -.. php:method:: withFile(string $path, array $options = []) - -Hay ocasiones en las que desea enviar archivos como respuesta a sus solicitudes. Puedes lograrlo usando -:php:meth:`Cake\\Http\\Response::withFile()`:: - - public function sendFile($id) - { - $file = $this->Attachments->getFile($id); - $response = $this->response->withFile($file['path']); - // Devuelve la respuesta para evitar que el controlador intente representar una vista. - return $response; - } - -Como se muestra en el ejemplo anterior, debe pasar la ruta del archivo al método. CakePHP enviará un encabezado de tipo -de contenido adecuado si es un tipo de archivo conocido que figura en `Cake\\Http\\Response::$_mimeTypes`. Puede agregar -nuevos tipos antes de llamar a :php:meth:`Cake\\Http\\Response::withFile()` usando el método -:php:meth:`Cake\\Http\\Response::withType()` . - -Si lo desea, también puede forzar la descarga de un archivo en lugar de mostrarlo en el navegador especificando las -opciones:: - - $response = $this->response->withFile( - $file['path'], - ['download' => true, 'name' => 'foo'] - ); - -Las opciones admitidas son: - -name - El nombre le permite especificar un nombre de archivo alternativo para enviarlo al usuario. -download - Un valor booleano que indica si los encabezados deben configurarse para forzar la descarga. - -Enviar una cadena como archivo ------------------------------- - -Puedes responder con un archivo que no existe en el disco, como un pdf o un ics generado sobre la marcha a partir de una -cadena:: - - public function sendIcs() - { - $icsString = $this->Calendars->generateIcs(); - $response = $this->response; - - // Inyectar contenido de cadena en el cuerpo de la respuesta - $response = $response->withStringBody($icsString); - - $response = $response->withType('ics'); - - // Opcionalmente forzar la descarga de archivos - $response = $response->withDownload('filename_for_download.ics'); - - // Devuelve un objeto de respuesta para evitar que el controlador intente representar una vista. - return $response; - } - -Configuración de encabezados ----------------------------- - -.. php:method:: withHeader($header, $value) - -La configuración de los encabezados se realiza con el método :php:meth:`Cake\\Http\\Response::withHeader()`. Como todos -los métodos de la interfaz PSR-7, este método devuelve una instancia *nueva* con el nuevo encabezado:: - - // Agregar/reemplazar un encabezado - $response = $response->withHeader('X-Extra', 'My header'); - - // Establecer múltiples encabezados - $response = $response->withHeader('X-Extra', 'My header') - ->withHeader('Location', 'http://example.com'); - - // Agregar un valor a un encabezado existente - $response = $response->withAddedHeader('Set-Cookie', 'remember_me=1'); - -Los encabezados no se envían cuando se configuran. En cambio, se retienen hasta que ``Cake\Http\Server`` emite la -respuesta. - -Ahora puede utilizar el método conveniente :php:meth:`Cake\\Http\\Response::withLocation()` para configurar u obtener -directamente el encabezado de ubicación de redireccionamiento. - -Configurando el cuerpo ----------------------- - -.. php:method:: withStringBody($string) - -Para establecer una cadena como cuerpo de respuesta, haga lo siguiente: - - // Coloca una cadena en el cuerpo. - $response = $response->withStringBody('My Body'); - - // Si quieres una respuesta json - $response = $response->withType('application/json')->withStringBody(json_encode(['Foo' => 'bar'])); - -.. php:method:: withBody($body) - -Para configurar el cuerpo de la respuesta, use el método ``withBody()``, que es proporcionado por -:php:class:`Laminas\\Diactoros\\MessageTrait`:: - - $response = $response->withBody($stream); - -Asegúrese de que ``$stream`` sea un objeto :php:class:`Psr\\Http\\Message\\StreamInterface`. Vea a continuación cómo -crear un nuevo stream. - -También puedes transmitir respuestas desde archivos usando :php:class:`Laminas\\Diactoros\\Stream` streams:: - - // Para transmitir desde un archivo - use Laminas\Diactoros\Stream; - - $stream = new Stream('/path/to/file', 'rb'); - $response = $response->withBody($stream); - -También puedes transmitir respuestas desde una devolución de llamada usando ``CallbackStream``. Esto es útil cuando tiene -recursos como imágenes, archivos CSV o PDF que necesita transmitir al cliente:: - - // Transmisión desde una devolución de llamada - use Cake\Http\CallbackStream; - - // Crea una imagen. - $img = imagecreate(100, 100); - // ... - - $stream = new CallbackStream(function () use ($img) { - imagepng($img); - }); - $response = $response->withBody($stream); - -Configuración del juego de caracteres -------------------------------------- - -.. php:method:: withCharset($charset) - -Establece el juego de caracteres que se utilizará en la respuesta:: - - $this->response = $this->response->withCharset('UTF-8'); - -Interactuar con el almacenamiento en caché del navegador --------------------------------------------------------- - -.. php:method:: withDisabledCache() - -A veces es necesario obligar a los navegadores a no almacenar en caché los resultados de una acción del controlador. -:php:meth:`Cake\\Http\\Response::withDisabledCache()` está destinado precisamente a eso:: - - public function index() - { - // Deshabilitar el almacenamiento en caché - $this->response = $this->response->withDisabledCache(); - } - -.. warning:: - - Deshabilitar el almacenamiento en caché de dominios SSL al intentar enviar archivos a Internet Explorer puede - generar errores. - -.. php:method:: withCache($since, $time = '+1 day') - -También puede decirles a los clientes que desea que almacenen en caché las respuestas. Usando -:php:meth:`Cake\\Http\\Response::withCache()`:: - - public function index() - { - // Habilitar el almacenamiento en caché - $this->response = $this->response->withCache('-1 minute', '+5 days'); - } - -Lo anterior les indicaría a los clientes que guarden en caché la respuesta resultante durante 5 días, con la esperanza -de acelerar la experiencia de sus visitantes. El método ``withCache()`` establece el valor ``Última modificación`` en el -primer argumento. El encabezado ``Expires`` y la directiva ``max-age`` se establecen en función del segundo parámetro. -La directiva "pública" de Cache-Control también está configurada. - -.. _cake-response-caching: - -Ajuste fino de la caché HTTP ----------------------------- - -Una de las mejores y más sencillas formas de acelerar su aplicación es utilizar la caché HTTP. Según este modelo de -almacenamiento en caché, solo debe ayudar a los clientes a decidir si deben usar una copia en caché de la respuesta -configurando algunos encabezados, como la hora de modificación y la etiqueta de entidad de respuesta. - -En lugar de obligarlo a codificar la lógica para el almacenamiento en caché y para invalidarla (actualizarla) una vez que -los datos han cambiado, HTTP utiliza dos modelos, caducidad y validación, que generalmente son mucho más simples de usar. - -Además de usar :php:meth:`Cake\\Http\\Response::withCache()`, también puedes usar muchos otros métodos para ajustar los -encabezados de caché HTTP para aprovechar el almacenamiento en caché del navegador o del proxy inverso. - -El encabezado de control de caché -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. php:method:: withSharable($public, $time = null) - -Utilizado bajo el modelo de vencimiento, este encabezado contiene múltiples indicadores que pueden cambiar la forma en -que los navegadores o servidores proxy usan el contenido almacenado en caché. Un encabezado ``Cache-Control`` puede -verse así:: - - Cache-Control: private, max-age=3600, must-revalidate - -La clase ``Response`` le ayuda a configurar este encabezado con algunos métodos de utilidad que producirán un encabezado -``Cache-Control`` final válido. El primero es el método ``withSharable()``, que indica si una respuesta debe considerarse -compartible entre diferentes usuarios o clientes. Este método en realidad controla la parte "pública" o "privada" de este -encabezado. Establecer una respuesta como privada indica que toda o parte de ella está destinada a un solo usuario. Para -aprovechar las cachés compartidas, la directiva de control debe configurarse como pública. - -El segundo parámetro de este método se utiliza para especificar una ``max-age`` para el caché, que es el número de -segundos después de los cuales la respuesta ya no se considera nueva:: - - public function view() - { - // ... - // Configure Cache-Control como público durante 3600 segundos - $this->response = $this->response->withSharable(true, 3600); - } - - public function my_data() - { - // ... - // Configure Cache-Control como privado durante 3600 segundos - $this->response = $this->response->withSharable(false, 3600); - } - -``Response`` expone métodos separados para configurar cada una de las directivas en el encabezado ``Cache-Control``. - -El encabezado de vencimiento -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. php:method:: withExpires($time) - -Puede configurar el encabezado ``Expires`` en una fecha y hora después de la cual la respuesta ya no se considera nueva. -Este encabezado se puede configurar usando el método ``withExpires()``:: - - public function view() - { - $this->response = $this->response->withExpires('+5 days'); - } - -Este método también acepta una instancia :php:class:`DateTime` o cualquier cadena que pueda ser analizada por la clase -:php:class:`DateTime`. - -El encabezado de la etiqueta electrónica -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. php:method:: withEtag($tag, $weak = false) - -La validación de caché en HTTP se usa a menudo cuando el contenido cambia constantemente y le pide a la aplicación que -solo genere el contenido de la respuesta si el caché ya no está actualizado. Bajo este modelo, el cliente continúa -almacenando páginas en el caché, pero pregunta a la aplicación cada vez si el recurso ha cambiado, en lugar de usarlo -directamente. Esto se usa comúnmente con recursos estáticos como imágenes y otros activos. - -El método ``withEtag()`` (llamado etiqueta de entidad) es una cadena que identifica de forma única el recurso solicitado, -como lo hace una suma de comprobación para un archivo, para determinar si coincide con un recurso almacenado en caché. - -Para aprovechar este encabezado, debe llamar al método ``isNotModified()`` manualmente o incluir -:doc:`/controllers/components/check-http-cache` en su controlador:: - - public function index() - { - $articles = $this->Articles->find('all')->all(); - - // Suma de comprobación simple del contenido del artículo. - // Debería utilizar una implementación más eficiente en una aplicación del mundo real. - $checksum = md5(json_encode($articles)); - - $response = $this->response->withEtag($checksum); - if ($response->isNotModified($this->request)) { - return $response; - } - - $this->response = $response; - // ... - } - -.. note:: - - La mayoría de los usuarios de proxy probablemente deberían considerar usar el encabezado de última modificación en - lugar de Etags por razones de rendimiento y compatibilidad. - -El último encabezado modificado -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. php:method:: withModified($time) - -Además, bajo el modelo de validación de caché HTTP, puede configurar el encabezado ``Last-Modified`` para indicar la -fecha y hora en la que se modificó el recurso por última vez. Configurar este encabezado ayuda a CakePHP a decirle a los -clientes de almacenamiento en caché si la respuesta se modificó o no según su caché. - -Para aprovechar este encabezado, debe llamar al método ``isNotModified()`` manualmente o incluir -:doc:`/controllers/components/check-http-cache` en su controlador:: - - public function view() - { - $article = $this->Articles->find()->first(); - $response = $this->response->withModified($article->modified); - if ($response->isNotModified($this->request)) { - return $response; - } - $this->response; - // ... - } - -El encabezado variable -~~~~~~~~~~~~~~~~~~~~~~ - -.. php:method:: withVary($header) - -En algunos casos, es posible que desee publicar contenido diferente utilizando la misma URL. Este suele ser el caso si -tiene una página multilingüe o responde con HTML diferente según el navegador. En tales circunstancias, puede utilizar -el encabezado ``Vary``:: - - $response = $this->response->withVary('User-Agent'); - $response = $this->response->withVary('Accept-Encoding', 'User-Agent'); - $response = $this->response->withVary('Accept-Language'); - -Envío de respuestas no modificadas -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. php:method:: isNotModified(Request $request) - -Compara los encabezados de la caché del objeto de solicitud con el encabezado de la caché de la respuesta y determina -sitodavía se puede considerar nuevo. Si es así, elimina el contenido de la respuesta y envía el encabezado -`304 Not Modified`:: - - // En una acción del controlador. - if ($this->response->isNotModified($this->request)) { - return $this->response; - } - -.. _response-cookies: - -Configuración de cookies ------------------------- - -Las cookies se pueden agregar a la respuesta usando una matriz o un objeto :php:class:`Cake\\Http\\Cookie\\Cookie`:: - - use Cake\Http\Cookie\Cookie; - use DateTime; - - // Agregar una cookie - $this->response = $this->response->withCookie(Cookie::create( - 'remember_me', - 'yes', - // Todas las claves son opcionales. - [ - 'expires' => new DateTime('+1 year'), - 'path' => '', - 'domain' => '', - 'secure' => false, - 'httponly' => false, - 'samesite' => null // O una de las constantes CookieInterface::SAMESITE_* - ] - )); - -Consulte la sección :ref:`creating-cookies` para saber cómo utilizar el objeto cookie. Puede utilizar -``withExpiredCookie()`` para enviar una cookie caducada en la respuesta. Esto hará que el navegador elimine su cookie -local:: - - $this->response = $this->response->withExpiredCookie(new Cookie('remember_me')); - -.. _cors-headers: - -Configuración de encabezados de solicitud de origen cruzado (CORS) -================================================================== - -El método ``cors()`` se utiliza para definir `Control de acceso HTTP -`__ encabezados relacionados con una interfaz fluida:: - - $this->response = $this->response->cors($this->request) - ->allowOrigin(['*.cakephp.org']) - ->allowMethods(['GET', 'POST']) - ->allowHeaders(['X-CSRF-Token']) - ->allowCredentials() - ->exposeHeaders(['Link']) - ->maxAge(300) - ->build(); - -Los encabezados relacionados con CORS solo se aplicarán a la respuesta si se cumplen los siguientes criterios: - -#. The request has an ``Origin`` header. -#. The request's ``Origin`` value matches one of the allowed Origin values. - -.. tip:: - - CakePHP no tiene middleware CORS incorporado porque manejar solicitudes CORS es muy específico de la aplicación. - Le recomendamos que cree su propio ``CORSMiddleware`` si lo necesita y ajuste el objeto de respuesta como desee. - -Errores comunes con respuestas inmutables -========================================= - -Los objetos de respuesta ofrecen varios métodos que tratan las respuestas como objetos inmutables. Los objetos inmutables -ayudan a prevenir efectos secundarios accidentales difíciles de rastrear y reducen los errores causados por llamadas a -métodos causadas por la refactorización que cambia el orden. Si bien ofrecen una serie de beneficios, es posible que sea -necesario algo de tiempo para acostumbrarse a los objetos inmutables. Cualquier método que comience con ``with`` opera -en la respuesta de forma inmutable y **siempre** devolverá una **nueva** instancia. Olvidar conservar la instancia -modificada es el error más frecuente que cometen las personas cuando trabajan con objetos inmutables: - - $this->response->withHeader('X-CakePHP', 'yes!'); - -En el código anterior, a la respuesta le faltará el encabezado ``X-CakePHP``, ya que el valor de retorno del método -``withHeader()`` no se retuvo. Para corregir el código anterior escribirías:: - - $this->response = $this->response->withHeader('X-CakePHP', 'yes!'); - -.. php:namespace:: Cake\Http\Cookie - -Colección de Cookies -===================== - -.. php:class:: CookieCollection - -Se puede acceder a los objetos ``CookieCollection`` desde los objetos de solicitud y respuesta. Le permiten interactuar -con grupos de cookies utilizando patrones inmutables, que permiten preservar la inmutabilidad de la solicitud y la -respuesta. - -.. _creating-cookies: - -Creando cookies ---------------- - -.. php:class:: Cookie - -Los objetos ``Cookie`` se pueden definir a través de objetos constructores o utilizando la interfaz fluida que sigue -patrones inmutables:: - - use Cake\Http\Cookie\Cookie; - - // Todos los argumentos en el constructor. - $cookie = new Cookie( - 'remember_me', // nombre - 1, // valor - new DateTime('+1 year'), // tiempo de vencimiento, si corresponde - '/', // ruta, si corresponde - 'example.com', // dominio, si corresponde - false, // ¿Solo seguro? - true // ¿Solo http? - ); - - // Usando los métodos constructores - $cookie = (new Cookie('remember_me')) - ->withValue('1') - ->withExpiry(new DateTime('+1 year')) - ->withPath('/') - ->withDomain('example.com') - ->withSecure(false) - ->withHttpOnly(true); - -Una vez que haya creado una cookie, puede agregarla a una ``CookieCollection`` nueva o existente:: - - use Cake\Http\Cookie\CookieCollection; - - // Crear una nueva colección - $cookies = new CookieCollection([$cookie]); - - // Agregar a una colección existente - $cookies = $cookies->add($cookie); - - // Eliminar una cookie por nombre - $cookies = $cookies->remove('remember_me'); - -.. note:: - Recuerde que las colecciones son inmutables y agregar o eliminar cookies de una colección crea un *nuevo* objeto - de colección. - -Se pueden agregar objetos cookie a las respuestas:: - - // Agregar una cookie - $response = $this->response->withCookie($cookie); - - // Reemplazar toda la colección de cookies - $response = $this->response->withCookieCollection($cookies); - -Las cookies configuradas para las respuestas se pueden cifrar utilizando :ref:`encrypted-cookie-middleware`. - -Leyendo Cookies ---------------- - -Una vez que tenga una instancia ``CookieCollection``, podrá acceder a las cookies que contiene:: - - // Comprobar si existe una cookie - $cookies->has('remember_me'); - - // Obtener el número de cookies de la colección. - count($cookies); - - // Obtener una instancia de cookie. Lanzará un error si no se encuentra la cookie. - $cookie = $cookies->get('remember_me'); - - // Obtener una cookie o nulo - $cookie = $cookies->remember_me; - - // Comprobar si existe una cookie - $exists = isset($cookies->remember_me) - -Una vez que tenga un objeto ``Cookie``, puede interactuar con su estado y modificarlo. Tenga en cuenta que las cookies -son inmutables, por lo que deberá actualizar la colección si modifica una cookie:: - - // Obtener el valor - $value = $cookie->getValue() - - // Acceder a datos dentro de un valor JSON - $id = $cookie->read('User.id'); - - // Comprobar estado - $cookie->isHttpOnly(); - $cookie->isSecure(); - -.. meta:: - :title lang=es: Objetos Request y Response - :keywords lang=en: request controller,request parameters,array indexes,purpose index,response objects,domain information,request object,request data,interrogating,params,parameters,previous versions,introspection,dispatcher,rout,data structures,arrays,ip address,migration,indexes,cakephp,PSR-7,immutable diff --git a/es/core-libraries/app.rst b/es/core-libraries/app.rst deleted file mode 100644 index 0028cc9dff..0000000000 --- a/es/core-libraries/app.rst +++ /dev/null @@ -1,132 +0,0 @@ -La clase App -############ - -.. php:namespace:: Cake\Core - -.. php:class:: App - -La clase App se encarga de la localización de recursos y de la -administración de rutas. - -Búsqueda de clases -================== - -.. php:staticmethod:: classname($name, $type = '', $suffix = '') - -Éste método se utiliza para resolver el nombre completo de una clase en todo Cakephp. -Como parámetros del método entran los nombre cortos que usa CakePHP y devuelve -el nombre completo (La ruta relativa al espacio de trabajo):: - - // Resuelve el nombre de clase corto utilizando el nombre y el sufijo. - App::classname('Auth', 'Controller/Component', 'Component'); - // Salida: Cake\Controller\Component\AuthComponent - - // Resuelve el nombre de plugin. - App::classname('DebugKit.Toolbar', 'Controller/Component', 'Component'); - // Salida: DebugKit\Controller\Component\ToolbarComponent - - // Nombres con '\' se devuelven inalterados. - App::classname('App\Cache\ComboCache'); - // Salida: App\Cache\ComboCache - -A la hora de resolver clases, primero se prueba con el espacio de nombres de -``App``, si no existe, se prueba con el espacio de nombres de ``Cake`` -. Si no existe ninguno, devuelve ``false``. - -Búsqueda de rutas al espacio de nombres -======================================= - -.. php:staticmethod:: path(string $package, string $plugin = null) - -Se usa para la búsqueda de rutas basada en convenio de nombres de -CakePHP:: - - // Buscar la ruta de Controller/ en tu aplicación - App::path('Controller'); - -Se puede utilizar para todos los espacios de nombres de tu -aplicacón. Además puedes extraer rutas de plugins:: - - // Devuelve la ruta del 'Component' en DebugKit - App::path('Component', 'DebugKit'); - -``App::path()`` sólo devuelve la ruta por defecto,no mostrará ningún tipo de -información sobre las rutas adicionales configuadas en autoloader. - -.. php:staticmethod:: core(string $package) - -Se usa para buscar rutas de paquetes dentro del core de Cakephp:: - - // Devuelve la ruta de engine de cake. - App::core('Cache/Engine'); - -Búsqueda de plugins -=================== - -.. php:staticmethod:: Plugin::path(string $plugin) - -Los plugins se localizan con el método Plugin. Por ejemplo, ``Plugin::path('DebugKit');`` -devuelve la ruta completa al plugin DebugKit:: - - $path = Plugin::path('DebugKit'); - -Localización de temas (nota:'themes') -===================================== - -Dado que los temas (nota:'themes') son también plugins, -se localizan con el método anterior, "Plugin". -(nota:'Aquí se refiere a los themes que se pueden crear -para modificar el comportamiento del bake, generador de código.') - -Cargar archivos externos (nota: 'vendor') -========================================= - -Lo ideal es que los archivos externos ('vendor') se carguen automáticamente -usando ``Composer``, si necesita archivos externos que no se pueden cargar -automáticamente o no se pueden instalar con el Composer, entonces hay que usar -``require`` para cargarlos. - -Si no puede instalar alguna librería con el Composer, debería instalar cada librería -en el directorio apropiado, siguiendo el convenio del Composer: ``vendor/$author/$package``. -Si tiene una librería de autor 'Acme' que se llama 'AcmeLib', la tiene que instalar en: -``vendor/Acme/AcmeLib``. Asumiendo que la librería no usa nombres de clase compatibles -con 'PSR-0', puede cargar las clases definiéndolas en el ``classmap``, dentro del archivo: -``composer.json`` en su aplicación:: - - "autoload": { - "psr-4": { - "App\\": "App", - "App\\Test\\": "Test", - "": "./Plugin" - }, - "classmap": [ - "vendor/Acme/AcmeLib" - ] - } - -Si la librería no usa clases y sólo proporciona métodos,puede configurar -el Composer para que cargue esos archivos al inicio de cada petición('request'), -usando la estrategia de carga automática de ficheros ``files``, como sigue:: - - "autoload": { - "psr-4": { - "App\\": "App", - "App\\Test\\": "Test", - "": "./Plugin" - }, - "files": [ - "vendor/Acme/AcmeLib/functions.php" - ] - } - -Después de la configuración de las librerías externas, tiene que regenerar el -autoloader de su aplicación usando:: - - $ php composer.phar dump-autoload - -Si no usa Composer en su aplicación, tendrá que cargar manualmente cada librería en -su aplicación. - -.. meta:: - :title lang=es: La clase App - :keywords lang=es: implementación compatible,comportamientos de modelos,administración de rutas,carga de archivos,clase php,carga de clases,comportamiento del modelo,localización de clase,componente model,management class,autoloader,autocarga,nombre de clase,localización de directorio,sobreescritura,convenios,lib,librería,textile,cakephp,php classes, cargado diff --git a/es/core-libraries/caching.rst b/es/core-libraries/caching.rst deleted file mode 100644 index ffa7ea2568..0000000000 --- a/es/core-libraries/caching.rst +++ /dev/null @@ -1,584 +0,0 @@ -Caching -####### - -.. php:namespace:: Cake\Cache - -.. php:class:: Cache - -El almacenamiento en caché se puede utilizar para acelerar la lectura de recursos caros o lentos, manteniendo una segunda copia de los datos requeridos en un sistema de almacenamiento más rápido o más cercano. Por ejemplo, puedes almacenar los resultados de consultas costosas o el acceso a servicios web remotos que no cambian con frecuencia en una caché. Una vez que los datos están en la caché, leerlos desde la caché es mucho más económico que acceder al recurso remoto. - -En CakePHP, el almacenamiento en caché se facilita mediante la clase ``Cache``. Esta clase proporciona una interfaz estática y uniforme para interactuar con diversas implementaciones de almacenamiento en caché. CakePHP proporciona varios motores de caché y ofrece una interfaz sencilla si necesitas construir tu propio backend. Los motores de almacenamiento en caché integrados son: - -- ``File``: el almacenamiento en caché de archivos es una caché simple que utiliza archivos locales. Es el motor de caché más lento y no proporciona muchas características para operaciones atómicas. Sin embargo, dado que el almacenamiento en disco a menudo es bastante económico, almacenar objetos grandes o elementos que rara vez se escriben funciona bien en archivos. -- ``Memcached``: utiliza la extensión `Memcached `_. -- ``Redis``: utiliza la extensión `phpredis `_. Redis proporciona un sistema de caché rápido y persistente similar a Memcached y también ofrece operaciones atómicas. -- ``Apcu``: la caché de APCu utiliza la extensión PHP `APCu `_. Esta extensión utiliza memoria compartida en el servidor web para almacenar objetos. Esto lo hace muy rápido y capaz de proporcionar funciones de lectura/escritura atómicas. -- ``Array``: almacena todos los datos en una matriz. Este motor no proporciona almacenamiento persistente y está destinado a su uso en suites de pruebas de aplicaciones. -- ``Null``: el motor nulo en realidad no almacena nada y falla en todas las operaciones de lectura. - -Independientemente del motor de caché que elijas usar, tu aplicación interactúa con :php:class:`Cake\\Cache\\Cache`. - -.. _cache-configuration: - -Configuración de los Motores de Caché -====================================== - -.. php:staticmethod:: setConfig($clave, $configuracion = null) - -Tu aplicación puede configurar cualquier número de 'motores' durante su proceso de inicio. Las configuraciones del motor de caché se definen en **config/app.php**. - -Para un rendimiento óptimo, CakePHP requiere que se definan dos motores de caché. - -- ``_cake_core_`` se utiliza para almacenar mapas de archivos y resultados analizados de archivos de :doc:`/core-libraries/internationalization-and-localization`. -- ``_cake_model_`` se utiliza para almacenar descripciones de esquemas para los modelos de tu aplicación. - -Usar múltiples configuraciones de motores también te permite cambiar incrementalmente el almacenamiento según sea necesario. Por ejemplo, en tu **config/app.php** podrías poner lo siguiente:: - - // ... - 'Cache' => [ - 'short' => [ - 'className' => 'File', - 'duration' => '+1 hours', - 'path' => CACHE, - 'prefix' => 'cake_short_', - ], - // Usando un nombre completamente calificado. - 'long' => [ - 'className' => 'Cake\Cache\Engine\FileEngine', - 'duration' => '+1 week', - 'probability' => 100, - 'path' => CACHE . 'long' . DS, - ], - ] - // ... - -Las opciones de configuración también se pueden proporcionar como una cadena :term:`DSN`. Esto es útil cuando se trabaja con variables de entorno o proveedores de :term:`PaaS`:: - - Cache::setConfig('short', [ - 'url' => 'memcached://user:password@cache-host/?timeout=3600&prefix=myapp_', - ]); - -Cuando usas una cadena DSN, puedes definir cualquier parámetro/opción adicional como argumentos de cadena de consulta. - -También puedes configurar los motores de caché en tiempo de ejecución:: - - // Usando un nombre corto - Cache::setConfig('short', [ - 'className' => 'File', - 'duration' => '+1 hours', - 'path' => CACHE, - 'prefix' => 'cake_short_' - ]); - - // Usando un nombre completamente calificado. - Cache::setConfig('long', [ - 'className' => 'Cake\Cache\Engine\FileEngine', - 'duration' => '+1 week', - 'probability' => 100, - 'path' => CACHE . 'long' . DS, - ]); - - // Usando un objeto construido. - $objeto = new FileEngine($configuracion); - Cache::setConfig('otro', $objeto); - -Los nombres de estas configuraciones de motor ('short' y 'long') se utilizan como el parámetro ``$config`` -para :php:meth:`Cake\\Cache\\Cache::write()` y -:php:meth:`Cake\\Cache\\Cache::read()`. Al configurar los motores de caché, puedes -referenciar el nombre de la clase utilizando las siguientes sintaxis:: - - // Nombre corto (en App\ o en los espacios de nombres de Cake) - Cache::setConfig('long', ['className' => 'File']); - - // Nombre corto del plugin - Cache::setConfig('long', ['className' => 'MyPlugin.SuperCache']); - - // Espacio de nombres completo - Cache::setConfig('long', ['className' => 'Cake\Cache\Engine\FileEngine']); - - // Un objeto que implementa CacheEngineInterface - Cache::setConfig('long', ['className' => $miCache]); - -.. note:: - - Al utilizar FileEngine, es posible que necesites usar la opción ``mask`` para - asegurarte de que los archivos de caché se creen con los permisos correctos. - -Opciones del Motor ------------------- - -Cada motor acepta las siguientes opciones: - -- ``duration``: especifica cuánto tiempo duran los elementos en esta configuración de caché. Se especifica como una expresión compatible con ``strtotime()``. -- ``groups``: lista de grupos o 'etiquetas' asociados a cada clave almacenada en esta configuración. Útil cuando necesitas eliminar un subconjunto de datos de una caché. -- ``prefix``: se antepone a todas las entradas. Bueno cuando necesitas compartir - un espacio de claves con otra configuración de caché o con otra aplicación. -- ``probability``: probabilidad de activar una limpieza de la caché. Establecerlo en 0 deshabilitará automáticamente la llamada a ``Cache::gc()`` - -Opciones del Motor de FileEngine --------------------------------- - -FileEngine utiliza las siguientes opciones específicas del motor: - -- ``isWindows``: se rellena automáticamente con si el host es Windows o no. -- ``lock``: ¿deberían bloquearse los archivos antes de escribir en ellos? -- ``mask``: la máscara utilizada para los archivos creados. -- ``path``: ruta donde deben guardarse los archivos de caché. Por defecto, es el directorio temporal del sistema. - -.. _caching-redisengine: - -Opciones del Motor RedisEngine ------------------------------- - -RedisEngine utiliza las siguientes opciones específicas del motor: - -- ``port``: el puerto en el que se está ejecutando tu servidor Redis. -- ``host``: el host en el que se está ejecutando tu servidor Redis. -- ``database``: el número de base de datos a usar para la conexión. -- ``password``: contraseña del servidor Redis. -- ``persistent``: ¿se debe realizar una conexión persistente a Redis? -- ``timeout``: tiempo de espera de conexión para Redis. -- ``unix_socket``: ruta a un socket Unix para Redis. - -Opciones del Motor MemcacheEngine ---------------------------------- - -- ``compress``: si comprimir datos o no. -- ``username``: usuario para acceder al servidor Memcache. -- ``password``: contraseña para acceder al servidor Memcache. -- ``persistent``: el nombre de la conexión persistente. Todas las configuraciones que usan - el mismo valor persistente compartirán una única conexión subyacente. -- ``serialize``: el motor de serialización utilizado para serializar datos. Los motores disponibles son php, - igbinary y json. Además de php, la extensión memcached debe estar compilada con el - soporte adecuado para el serializador correspondiente. -- ``servers``: cadena o array de servidores memcached. Si es un array, MemcacheEngine los usará - como un grupo. -- ``duration``: ten en cuenta que cualquier duración mayor de 30 días se tratará como un valor de tiempo Unix real - en lugar de un desfase desde el tiempo actual. -- ``options``: opciones adicionales para el cliente memcached. Debe ser un array de opción => valor. - Usa las constantes ``\Memcached::OPT_*`` como claves. - -.. _configuracion-fallback-caché: - -Configuración de la Caída de Caché ----------------------------------- - -En caso de que un motor no esté disponible, como el ``FileEngine`` que intenta -escribir en una carpeta no escribible o el ``RedisEngine`` que no puede conectarse a -Redis, el motor volverá al ``NullEngine`` y generará un error que se puede registrar. -Esto evita que la aplicación genere una excepción no capturada debido a un error de caché. - -Puedes configurar las configuraciones de la caché para que vuelvan a una configuración especificada usando la clave de configuración ``fallback``:: - - Cache::setConfig('redis', [ - 'className' => 'Redis', - 'duration' => '+1 hours', - 'prefix' => 'cake_redis_', - 'host' => '127.0.0.1', - 'port' => 6379, - 'fallback' => 'default', - ]); - -Si falla la inicialización de la instancia ``RedisEngine``, la configuración de caché ``redis`` -volverá a usar la configuración de caché ``default``. Si también falla la inicialización del -motor para la configuración de caché ``default`` en este escenario, el motor volvería nuevamente al ``NullEngine`` -y evitaría que la aplicación genere una excepción no capturada. - -Puedes desactivar las caídas de caché con ``false``:: - - Cache::setConfig('redis', [ - 'className' => 'Redis', - 'duration' => '+1 hours', - 'prefix' => 'cake_redis_', - 'host' => '127.0.0.1', - 'port' => 6379, - 'fallback' => false - ]); - -Cuando no hay una caída, los errores de caché se generarán como excepciones. - -Eliminación de Motores de Caché Configurados ---------------------------------------------- - -.. php:staticmethod:: drop($clave) - -Una vez que se crea una configuración, no puedes cambiarla. En su lugar, debes eliminar -la configuración y volver a crearla usando :php:meth:`Cake\\Cache\\Cache::drop()` y -:php:meth:`Cake\\Cache\\Cache::setConfig()`. Eliminar un motor de caché eliminará -la configuración y destruirá el adaptador si se construyó. - -Escritura en Caché -================== - -.. php:staticmethod:: write($clave, $valor, $configuracion = 'default') - -``Cache::write()`` escribirá un $valor en la caché. Puedes leer o -eliminar este valor más tarde refiriéndote a él por ``$clave``. Puedes -especificar una configuración opcional para almacenar la caché también. Si -no se especifica ninguna ``$configuración``, se usará la predeterminada. ``Cache::write()`` -puede almacenar cualquier tipo de objeto y es ideal para almacenar resultados de -búsquedas de modelos:: - - $entradas = Cache::read('entradas'); - if ($entradas === null) { - $entradas = $servicio->obtenerTodasLasEntradas(); - Cache::write('entradas', $entradas); - } - -Usar ``Cache::write()`` y ``Cache::read()`` para reducir el número -de consultas realizadas a la base de datos para obtener las entradas. - -.. note:: - - Si planeas almacenar en caché el resultado de las consultas realizadas con el ORM de CakePHP, - es mejor utilizar las capacidades de almacenamiento en caché integradas del objeto de consulta - como se describe en la sección de :ref:`caching-query-results` - -Escritura de Múltiples Claves a la Vez --------------------------------------- - -.. php:staticmethod:: writeMany($datos, $configuracion = 'default') - -Puede que necesites escribir múltiples claves de caché a la vez. Aunque podrías usar múltiples llamadas a ``write()``, ``writeMany()`` permite a CakePHP utilizar -API de almacenamiento más eficientes cuando están disponibles. Por ejemplo, usando ``writeMany()`` -ahorras múltiples conexiones de red cuando usas Memcached:: - - $resultado = Cache::writeMany([ - 'articulo-' . $slug => $articulo, - 'articulo-' . $slug . '-comentarios' => $comentarios - ]); - - // $resultado contendrá - ['articulo-primer-post' => true, 'articulo-primer-post-comentarios' => true] - -Escrituras Atómicas -------------------- - -.. php:staticmethod:: add($clave, $valor, $configuracion = 'default') - -Usar ``Cache::add()`` te permitirá establecer atómicamente una clave en un valor si la clave -aún no existe en la caché. Si la clave ya existe en el backend de la caché o la escritura falla, ``add()`` devolverá ``false``:: - - // Establecer una clave para actuar como bloqueo - $resultado = Cache::add($claveBloqueo, true); - if (!$resultado) { - return; - } - // Realizar una acción donde solo puede haber un proceso activo a la vez. - - // Eliminar la clave de bloqueo. - Cache::delete($claveBloqueo); - -.. warning:: - - La caché basada en archivos no admite escrituras atómicas. - -Caché de Lectura Directa ------------------------- - -.. php:staticmethod:: remember($clave, $callable, $configuracion = 'default') - -La caché ayuda con la caché de lectura directa. Si la clave de caché nombrada existe, -se devolverá. Si la clave no existe, se invocará la función de llamada -y los resultados se almacenarán en la caché en la clave proporcionada. - -Por ejemplo, a menudo quieres cachear los resultados de las llamadas a servicios remotos. Puedes usar -``remember()`` para hacerlo simple:: - - class ServicioDeAsunto - { - public function todasLasTemas($repositorio) - { - return Cache::remember($repositorio . '-temas', function () use ($repositorio) { - return $this->obtenerTodos($repositorio); - }); - } - } - -Lectura Desde la Caché -====================== - -.. php:staticmethod:: read($clave, $configuracion = 'default') - -``Cache::read()`` se usa para leer el valor en caché almacenado bajo -``$clave`` desde la ``$configuración``. Si ``$configuración`` es nulo, se usará la configuración predeterminada -configuración. ``Cache::read()`` devolverá el valor en caché -si es una caché válida o ``null`` si la caché ha caducado o -no existe. Utiliza los operadores de comparación estricta ``===`` o ``!==`` -para comprobar el éxito de la operación ``Cache::read()``. - -Por ejemplo:: - - $nube = Cache::read('nube'); - if ($nube !== null) { - return $nube; - } - - // Generar datos de la nube - // ... - - // Almacenar datos en la caché - Cache::write('nube', $nube); - - return $nube; - -O si estás usando otra configuración de caché llamada ``corta``, puedes -especificarlo en las llamadas a ``Cache::read()`` y ``Cache::write()`` de la siguiente manera:: - - // Leer la clave "nube", pero de la configuración corta en lugar de la predeterminada - $nube = Cache::read('nube', 'corta'); - if ($nube === null) { - // Generar datos de la nube - // ... - - // Almacenar datos en la caché, usando la configuración de caché corta en lugar de la predeterminada - Cache::write('nube', $nube, 'corta'); - } - - return $nube; - -Lectura de Múltiples Claves a la Vez -------------------------------------- - -.. php:staticmethod:: readMany($claves, $configuracion = 'default') - -Después de haber escrito múltiples claves a la vez, probablemente querrás leerlas también. Aunque podrías usar múltiples llamadas a ``read()``, ``readMany()`` permite -a CakePHP utilizar API de almacenamiento más eficientes donde estén disponibles. Por ejemplo, usando -``readMany()`` ahorras múltiples conexiones de red cuando usas Memcached:: - - $resultado = Cache::readMany([ - 'articulo-' . $slug, - 'articulo-' . $slug . '-comentarios' - ]); - // $resultado contendrá - ['articulo-primer-post' => '...', 'articulo-primer-post-comentarios' => '...'] - -Eliminación de la Caché -======================= - -.. php:staticmethod:: delete($clave, $configuracion = 'default') - -``Cache::delete()`` te permitirá eliminar completamente un objeto en caché -del almacén:: - - // Eliminar una clave - Cache::delete('mi_clave'); - -A partir de la versión 4.4.0, el ``RedisEngine`` también proporciona un método ``deleteAsync()`` que utiliza la operación ``UNLINK`` para eliminar las claves de caché:: - - Cache::pool('redis')->deleteAsync('mi_clave'); - -Eliminación de Múltiples Claves a la Vez ----------------------------------------- - -.. php:staticmethod:: deleteMany($claves, $configuracion = 'default') - -Después de haber escrito múltiples claves a la vez, es posible que desees eliminarlas. Aunque -podrías usar múltiples llamadas a ``delete()``, ``deleteMany()`` permite a CakePHP utilizar -API de almacenamiento más eficientes donde estén disponibles. Por ejemplo, usando ``deleteMany()`` -ahorras múltiples conexiones de red cuando usas Memcached:: - - $resultado = Cache::deleteMany([ - 'articulo-' . $slug, - 'articulo-' . $slug . '-comentarios' - ]); - // $resultado contendrá - ['articulo-primer-post' => true, 'articulo-primer-post-comentarios' => true] - -Limpieza de Datos en Caché -========================== - -.. php:staticmethod:: clear($configuracion = 'default') - -Elimina todos los valores en caché para una configuración de caché. En motores como: Apcu, -Memcached, se utiliza el prefijo de la configuración de caché para eliminar -entradas de caché. Asegúrate de que las diferentes configuraciones de caché tengan diferentes -prefijos:: - - // Eliminará todas las claves. - Cache::clear(); - -A partir de la versión 4.4.0, el ``RedisEngine`` también proporciona un método ``clearBlocking()`` que utiliza la operación ``UNLINK`` para eliminar las claves de caché:: - - Cache::pool('redis')->clearBlocking(); - -.. note:: - - Debido a que APCu utiliza cachés aisladas para el servidor web y la interfaz de línea de comandos, - deben ser limpiadas por separado (la CLI no puede limpiar el servidor web y viceversa). - -Uso de Caché para Almacenar Contadores -======================================= - -.. php:staticmethod:: increment($key, $offset = 1, $config = 'default') - -.. php:staticmethod:: decrement($key, $offset = 1, $config = 'default') - -Los contadores en tu aplicación son buenos candidatos para ser almacenados en caché. Por ejemplo, -una contador de días para un evento puede ser guardado en la caché. La clase Cache -expone formas de incrementar y decrementar los valores del contador. El hecho de que estas -operaciones sean atómicas es importante para que se reduzca el riesgo de contención y la abilidad de que -dos usuarios simultaneamente incrementen o decrementen el mismo valor. - -Después de guardar un valor entero en la caché, puedes manipularlo usando ``increment()`` y -``decrement()``:: - - Cache::write('initial_count', 10); - - // Later on - Cache::decrement('initial_count'); - - // Or - Cache::increment('initial_count'); - - -.. note:: - - Recuerda que las operaciones de incremento y decremento no están disponibles en FileEngine. Debes usar APCu, Redis o Memcached. - -.. _caching-query-results: - -Utilizando la Caché para Almacenar Resultados Comunes de Consultas -=================================================================== - -Puedes mejorar significativamente el rendimiento de tu aplicación almacenando en caché los resultados -que rara vez cambian o que están sujetos a lecturas frecuentes. Un ejemplo perfecto de esto son los resultados de -:php:meth:`Cake\\ORM\\Table::find()`. El objeto de consulta te permite almacenar en caché -los resultados utilizando el método ``cache()``. Consulta la sección :ref:`caching-query-results` -para obtener más información. - -.. _cache-groups: - -Uso de Grupos -============= - -A veces querrás marcar varias entradas en caché para que pertenezcan a cierto grupo o espacio de nombres. Esta es una necesidad común para invalidar masivamente claves cada vez que cambia alguna información que se comparte entre todas las entradas en el mismo grupo. Esto es posible declarando los grupos en la configuración de la caché:: - - Cache::setConfig('site_home', [ - 'className' => 'Redis', - 'duration' => '+999 days', - 'groups' => ['comment', 'article'], - ]); - -.. php:method:: clearGroup($group, $config = 'default') - -Digamos que quieres almacenar en caché el HTML generado para tu página de inicio, pero también quieres invalidar automáticamente esta caché cada vez que se agrega un comentario o una publicación a tu base de datos. Al agregar los grupos ``comment`` y ``article``, hemos etiquetado efectivamente cualquier clave almacenada en esta configuración de caché con ambos nombres de grupo. - -Por ejemplo, cada vez que se añade una nueva publicación, podríamos decirle al motor de caché que elimine todas las entradas asociadas al grupo ``article``:: - - // src/Model/Table/ArticlesTable.php - public function afterSave($event, $entity, $options = []) - { - if ($entity->isNew()) { - Cache::clearGroup('article', 'site_home'); - } - } - -.. php:staticmethod:: groupConfigs($group = null) - -``groupConfigs()`` se puede utilizar para recuperar la asignación entre el grupo y las configuraciones, es decir, tener el mismo grupo:: - - // src/Model/Table/ArticlesTable.php - - /** - * Una variación del ejemplo anterior que limpia todas las configuraciones de caché - * que tienen el mismo grupo - */ - public function afterSave($event, $entity, $options = []) - { - if ($entity->isNew()) { - $configs = Cache::groupConfigs('article'); - foreach ($configs['article'] as $config) { - Cache::clearGroup('article', $config); - } - } - } - -Los grupos se comparten en todas las configuraciones de caché que utilizan el mismo motor y el mismo prefijo. Si estás usando grupos y quieres aprovechar la eliminación de grupos, elige un prefijo común para todas tus configuraciones. - -Habilitar o Deshabilitar Globalmente la Caché -============================================= - -.. php:staticmethod:: disable() - -Puede que necesites deshabilitar todas las lecturas y escrituras en la caché cuando intentas resolver problemas relacionados con la expiración de la caché. Puedes hacerlo usando ``enable()`` y ``disable()``:: - - // Deshabilitar todas las lecturas y escrituras en la caché. - Cache::disable(); - -Una vez deshabilitada, todas las lecturas y escrituras devolverán ``null``. - -.. php:staticmethod:: enable() - -Una vez deshabilitada, puedes usar ``enable()`` para habilitar nuevamente la caché:: - - // Habilitar nuevamente todas las lecturas y escrituras en la caché. - Cache::enable(); - -.. php:staticmethod:: enabled() - -Si necesitas verificar el estado de la caché, puedes usar ``enabled()``. - -Creación de un Motor de Caché -============================= - -Puedes proporcionar motores de ``Cache`` personalizados en ``App\Cache\Engine``, así como en plugins usando ``$plugin\Cache\Engine``. Los motores de caché deben estar en un directorio de caché. Si tuvieras un motor de caché llamado ``MyCustomCacheEngine``, se colocaría en **src/Cache/Engine/MyCustomCacheEngine.php**. O en **plugins/MyPlugin/src/Cache/Engine/MyCustomCacheEngine.php** como parte de un plugin. Las configuraciones de caché de los plugins deben utilizar la sintaxis de puntos del plugin:: - - Cache::setConfig('custom', [ - 'className' => 'MyPlugin.MyCustomCache', - // ... - ]); - -Los motores de caché personalizados deben extender :php:class:`Cake\\Cache\\CacheEngine`, que define varios métodos abstractos y también proporciona algunos métodos de inicialización. - -La API requerida para un CacheEngine es - -.. php:class:: CacheEngine - - La clase base para todos los motores de caché utilizados con Cache. - -.. php:method:: write($key, $value) - - :return: booleano para indicar el éxito. - - Escribe el valor de una clave en la caché, devuelve ``true`` si los datos se almacenaron correctamente, ``false`` en caso de fallo. - -.. php:method:: read($key) - - :return: El valor en caché o ``null`` en caso de fallo. - - Lee una clave de la caché. Devuelve ``null`` para indicar que la entrada ha caducado o no existe. - -.. php:method:: delete($key) - - :return: Booleano ``true`` en caso de éxito. - - Elimina una clave de la caché. Devuelve ``false`` para indicar que la entrada no existía o no se pudo eliminar. - -.. php:method:: clear($check) - - :return: Booleano ``true`` en caso de éxito. - - Elimina todas las claves de la caché. Si $check es ``true``, debes validar que cada valor realmente ha caducado. - -.. php:method:: clearGroup($group) - - :return: Booleano ``true`` en caso de éxito. - - Elimina todas las claves de la caché pertenecientes al mismo grupo. - -.. php:method:: decrement($key, $offset = 1) - - :return: Booleano ``true`` en caso de éxito. - - Decrementa un número bajo la clave y devuelve el valor decrecido. - -.. php:method:: increment($key, $offset = 1) - - :return: Booleano ``true`` en caso de éxito. - - Incrementa un número bajo la clave y devuelve el valor incrementado. - -.. meta:: - :title lang=es: Almacenamiento en Caché - :keywords lang=en: uniform api,cache engine,cache system,atomic operations,php class,disk storage,static methods,php extension,consistent manner,similar features,apcu,apc,memcache,queries,cakephp,elements,servers,memory diff --git a/es/core-libraries/collections.rst b/es/core-libraries/collections.rst deleted file mode 100644 index de4626b311..0000000000 --- a/es/core-libraries/collections.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. php:namespace:: Cake\Collection - -.. _collection-objects: - -Collections -########### - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. - -.. meta:: - :title lang=es: Collections - :keywords lang=es: collections, cakephp, append, sort, compile, contains, countBy, each, every, extract, filter, first, firstMatch, groupBy, indexBy, jsonSerialize, map, match, max, min, reduce, reject, sample, shuffle, some, random, sortBy, take, toArray, insert diff --git a/es/core-libraries/email.rst b/es/core-libraries/email.rst deleted file mode 100644 index 338b640ac3..0000000000 --- a/es/core-libraries/email.rst +++ /dev/null @@ -1,613 +0,0 @@ -Mailer -###### - -.. php:namespace:: Cake\Mailer - -.. php:class:: Mailer(string|array|null $profile = null) - -``Mailer`` es una clase de conveniencia para enviar correos electrónicos. Con esta clase, puedes enviar correos electrónicos desde cualquier lugar dentro de tu aplicación. - -Uso Básico -========== - -Primero, asegúrate de que la clase esté cargada:: - - use Cake\Mailer\Mailer; - -Después de cargar ``Mailer``, puedes enviar un correo electrónico de la siguiente manera:: - - $mailer = new Mailer('default'); - $mailer->setFrom(['me@example.com' => 'Mi Sitio']) - ->setTo('you@example.com') - ->setSubject('Acerca de') - ->deliver('Mi mensaje'); - -Dado que los métodos setter de ``Mailer`` devuelven una instancia de la clase, puedes configurar sus propiedades encadenando los métodos. - -``Mailer`` tiene varios métodos para definir destinatarios: ``setTo()``, ``setCc()``, ``setBcc()``, ``addTo()``, ``addCc()`` y ``addBcc()``. -La principal diferencia es que los primeros tres sobrescribirán lo que ya se haya establecido, mientras que los últimos simplemente -agregarán más destinatarios a su campo respectivo:: - - $mailer = new Mailer(); - $mailer->setTo('to@example.com', 'Destinatario Ejemplo'); - $mailer->addTo('to2@example.com', 'Destinatario2 Ejemplo'); - // Los destinatarios del correo electrónico son: to@example.com y to2@example.com - $mailer->setTo('test@example.com', 'DestinatarioPrueba Ejemplo'); - // El destinatario del correo electrónico es: test@example.com - -Elección del Remitente ----------------------- - -Cuando envíes correos electrónicos en nombre de otras personas, suele ser una buena idea definir el remitente original usando el encabezado del remitente (Sender header). Puedes hacerlo usando ``setSender()``:: - - $mailer = new Mailer(); - $mailer->setSender('app@example.com', 'Mi aplicación de correo'); - -.. note:: - - También es una buena idea establecer el remitente del sobre (envelope sender) al enviar correos electrónicos en nombre de otra persona. Esto evita que reciban mensajes sobre la entregabilidad. - -.. _email-configuration: - -Configuración -============= - -Los perfiles de Mailer y las configuraciones de transporte de correo electrónico se definen en los archivos de configuración de tu aplicación. Las claves ``'Email'`` y ``'EmailTransport'`` definen perfiles de Mailer y configuraciones de transporte de correo electrónico respectivamente. Durante el inicio de la aplicación, los valores de configuración se pasan desde ``Configure`` a las clases ``Mailer`` y ``TransportFactory`` utilizando ``setConfig()``. Al definir perfiles y transportes, puedes mantener el código de tu aplicación libre de datos de configuración y evitar la duplicación que complica el mantenimiento y el despliegue. - -Para cargar una configuración predefinida, puedes usar el método ``setProfile()`` o pasarlo al constructor de ``Mailer``:: - - $mailer = new Mailer(); - $mailer->setProfile('default'); - - // O en el constructor - $mailer = new Mailer('default'); - -En lugar de pasar una cadena que coincida con un nombre de configuración preestablecido, también puedes cargar simplemente un array de opciones:: - - $mailer = new Mailer(); - $mailer->setProfile(['from' => 'me@example.org', 'transport' => 'my_custom']); - - // O en el constructor - $mailer = new Mailer(['from' => 'me@example.org', 'transport' => 'my_custom']); - -.. _email-configurations: - -Perfiles de Configuración -------------------------- - -Definir perfiles de entrega te permite consolidar la configuración común del correo electrónico en perfiles reutilizables. Tu aplicación puede tener tantos perfiles como sea necesario. Se utilizan las siguientes claves de configuración: - -- ``'from'``: Mailer o array del remitente. Ver ``Mailer::setFrom()``. -- ``'sender'``: Mailer o array del remitente real. Ver ``Mailer::setSender()``. -- ``'to'``: Mailer o array del destino. Ver ``Mailer::setTo()``. -- ``'cc'``: Mailer o array de copia carbono. Ver ``Mailer::setCc()``. -- ``'bcc'``: Mailer o array de copia carbono oculta. Ver ``Mailer::setBcc()``. -- ``'replyTo'``: Mailer o array para responder al correo electrónico. Ver ``Mailer::setReplyTo()``. -- ``'readReceipt'``: Dirección del Mailer o un array de direcciones para recibir - el recibo de lectura. Ver ``Mailer::setReadReceipt()``. -- ``'returnPath'``: Dirección del Mailer o un array de direcciones para devolver si hay - algún error. Ver ``Mailer::setReturnPath()``. -- ``'messageId'``: ID del mensaje del correo electrónico. Ver ``Mailer::setMessageId()``. -- ``'subject'``: Asunto del mensaje. Ver ``Mailer::setSubject()``. -- ``'message'``: Contenido del mensaje. No establezcas este campo si estás usando contenido renderizado. -- ``'priority'``: Prioridad del correo electrónico como valor numérico (generalmente de 1 a 5, siendo 1 el más alto). -- ``'headers'``: Cabeceras a incluir. Ver ``Mailer::setHeaders()``. -- ``'viewRenderer'``: Si estás usando contenido renderizado, establece el nombre de la clase de vista. - Ver ``ViewBuilder::setClassName()``. -- ``'template'``: Si estás usando contenido renderizado, establece el nombre de la plantilla. Ver - ``ViewBuilder::setTemplate()``. -- ``'theme'``: Tema utilizado al renderizar la plantilla. Ver ``ViewBuilder::setTheme()``. -- ``'layout'``: Si estás usando contenido renderizado, establece el diseño a renderizar. Ver - ``ViewBuilder::setTemplate()``. -- ``'autoLayout'``: Si quieres renderizar una plantilla sin diseño, establece este campo en - ``false``. Ver ``ViewBuilder::disableAutoLayout()``. -- ``'viewVars'``: Si estás usando contenido renderizado, establece el array con - variables que se utilizarán en la vista. Ver ``Mailer::setViewVars()``. -- ``'attachments'``: Lista de archivos para adjuntar. Ver ``Mailer::setAttachments()``. -- ``'emailFormat'``: Formato del correo electrónico (html, texto o ambos). Ver ``Mailer::setEmailFormat()``. -- ``'transport'``: Nombre de la configuración del transporte. Ver :ref:`email-transport`. -- ``'log'``: Nivel de registro para registrar las cabeceras y el mensaje del correo electrónico. ``true`` utilizará - LOG_DEBUG. Ver :ref:`logging-levels`. Ten en cuenta que los registros se emitirán bajo el ámbito denominado ``email``. - Ver también :ref:`logging-scopes`. -- ``'helpers'``: Array de helpers utilizados en la plantilla del correo electrónico. - ``ViewBuilder::setHelpers()``/``ViewBuilder::addHelpers()``. - -.. note:: - - Los valores de las claves mencionadas anteriormente que usan Mailer o array, como from, to, cc, etc., se pasarán - como el primer parámetro de los métodos correspondientes. El equivalente a: - ``$mailer->setFrom('mi@example.com', 'Mi Sitio')`` - se definiría como ``'from' => ['mi@example.com' => 'Mi Sitio']`` en tu configuración. - -Configurando Cabeceras -====================== - -En ``Mailer``, eres libre de establecer las cabeceras que desees. No olvides agregar el prefijo ``X-`` a tus cabeceras personalizadas. - -Consulta ``Mailer::setHeaders()`` y ``Mailer::addHeaders()`` - -Envío de Correos Electrónicos con Plantillas -============================================= - -Los correos electrónicos a menudo son mucho más que un simple mensaje de texto. Para facilitar eso, CakePHP proporciona una forma de enviar correos electrónicos utilizando la :doc:`capa de vista ` de CakePHP. - -Las plantillas para correos electrónicos residen en una carpeta especial ``templates/email`` de tu aplicación. Las vistas del Mailer también pueden utilizar diseños y elementos al igual que las vistas normales:: - - $mailer = new Mailer(); - $mailer - ->setEmailFormat('html') - ->setTo('bob@example.com') - ->setFrom('app@domain.com') - ->viewBuilder() - ->setTemplate('bienvenida') - ->setLayout('elegante'); - - $mailer->deliver(); - -Lo anterior utilizará **templates/email/html/bienvenida.php** para la vista -y **templates/layout/email/html/elegante.php** para el diseño. También puedes -enviar mensajes de correo electrónico con varias partes de plantilla:: - - $mailer = new Mailer(); - $mailer - ->setEmailFormat('both') - ->setTo('bob@example.com') - ->setFrom('app@domain.com') - ->viewBuilder() - ->setTemplate('bienvenida') - ->setLayout('elegante'); - - $mailer->deliver(); - -Esto utilizará los siguientes archivos de plantilla: - -* **templates/email/text/bienvenida.php** -* **templates/layout/email/text/elegante.php** -* **templates/email/html/bienvenida.php** -* **templates/layout/email/html/elegante.php** - -Cuando envíes correos electrónicos con plantillas, tienes la opción de enviar ``texto``, ``html`` o ``ambos``. - -Puedes configurar toda la configuración relacionada con la vista usando la instancia de creador de vistas ``Mailer::viewBuilder()`` de manera similar a como lo haces en el controlador. - -Puedes establecer variables de vista con ``Mailer::setViewVars()``:: - - $mailer = new Mailer('plantilla'); - $mailer->setViewVars(['valor' => 12345]); - -O puedes usar los métodos del creador de vistas ``ViewBuilder::setVar()`` y ``ViewBuilder::setVars()``. - -En tus plantillas de correo electrónico, puedes usarlos de la siguiente manera:: - -

      Aquí está tu valor:

      - -También puedes usar ayudantes en los correos electrónicos, al igual que en los archivos de plantilla normales. De forma predeterminada, solo se carga el ``HtmlHelper``. Puedes cargar ayudantes adicionales utilizando el método ``ViewBuilder::addHelpers()``:: - - $mailer->viewBuilder()->addHelpers(['Html', 'Custom', 'Text']); - -Cuando agregues ayudantes, asegúrate de incluir 'Html' o se eliminará de los ayudantes cargados en tu plantilla de correo electrónico. - -.. note:: - En versiones anteriores a 4.3.0, deberás usar ``setHelpers()`` en su lugar. - -Si deseas enviar correos electrónicos utilizando plantillas en un plugin, puedes usar la familiar :term:`Sintaxis de plugin` para hacerlo:: - - $mailer = new Mailer(); - $mailer->viewBuilder()->setTemplate('Blog.new_comment'); - -Lo anterior utilizará la plantilla y el diseño del plugin Blog como ejemplo. - -En algunos casos, es posible que necesites anular la plantilla predeterminada proporcionada por los complementos. -Puedes hacer esto usando temas:: - - $mailer->viewBuilder() - ->setTemplate('Blog.new_comment') - ->setLayout('Blog.auto_message') - ->setTheme('MiTema'); - -Esto te permite anular la plantilla "new_comment" en tu tema sin modificar el complemento Blog. El archivo de plantilla debe crearse en la siguiente ruta: -**templates/plugin/MiTema/plugin/Blog/email/text/new_comment.php**. - -Envío de Archivos Adjuntos -=========================== - -.. php:method:: setAttachments($adjuntos) - -También puedes adjuntar archivos a los mensajes de correo electrónico. Hay algunos formatos diferentes dependiendo del tipo de archivos que tengas y de cómo quieras que aparezcan los nombres de archivo en el cliente de correo del destinatario: - -1. Array: ``$mailer->setAttachments(['/ruta/completa/archivo.png'])`` adjuntará este archivo con el nombre archivo.png.. -2. Array con clave: - ``$mailer->setAttachments(['foto.png' => '/ruta/completa/algun_hash.png'])`` adjuntará some_hash.png con el nombre foto.png. El destinatario verá - foto.png, no some_hash.png. -3. Arrays anidados:: - - $mailer->setAttachments([ - 'foto.png' => [ - 'archivo' => '/ruta/completa/algun_hash.png', - 'mimetype' => 'image/png', - 'contentId' => 'mi-id-unico', - ], - ]); - - Lo anterior adjuntará el archivo con un tipo MIME diferente y con un ID de contenido personalizado (cuando se establece el ID de contenido, el archivo adjunto se convierte en incrustado). - El tipo MIME y contentId son opcionales en esta forma. - - 3.1. Cuando estás usando el ``contentId``, puedes usar el archivo en el cuerpo HTML - como ````. - - 3.2. Puedes usar la opción ``contentDisposition`` para desactivar el encabezado ``Content-Disposition`` para un archivo adjunto. Esto es útil cuando - envías invitaciones ical a clientes que usan Outlook. - - 3.3 En lugar de la opción ``archivo``, puedes proporcionar el contenido del archivo como - una cadena utilizando la opción ``datos``. Esto te permite adjuntar archivos sin - necesidad de tener rutas de archivo para ellos. - -Relajando las Reglas de Validación de Direcciones --------------------------------------------------- - -.. php:method:: setEmailPattern($patrón) - -Si tienes problemas de validación al enviar a direcciones no conformes, puedes relajar el patrón utilizado para validar direcciones de correo electrónico. Esto es a veces -necesario al tratar con algunos proveedores de servicios de Internet:: - - $mailer = new Mailer('predeterminado'); - - // Relaja el patrón de correo electrónico, para que puedas enviar - // a direcciones no conformes. - $mailer->setEmailPattern($nuevoPatrón); - -Envío de Correos Electrónicos desde la CLI -=========================================== - -Cuando envíes correos electrónicos dentro de un script de CLI (Shells, Tasks, ...), debes establecer manualmente -el nombre de dominio que Mailer utilizará. Servirá como el nombre de host para el -ID del mensaje (ya que no hay un nombre de host en un entorno CLI):: - - $mailer->setDomain('www.ejemplo.org'); - // Da como resultado IDs de mensajes como ```` (válidos) - // En lugar de ```` (inválidos) - -Un ID de mensaje válido puede ayudar a evitar que los correos electrónicos terminen en carpetas de spam. - -Creación de Correos Electrónicos Reutilizables -=============================================== - -Hasta ahora hemos visto cómo usar directamente la clase ``Mailer`` para crear y -enviar un correo electrónico. Pero la característica principal del mailer es permitir la creación de correos electrónicos reutilizables -en toda tu aplicación. También se pueden usar para contener múltiples -configuraciones de correo electrónico en un solo lugar. Esto ayuda a mantener tu código DRY y a evitar la configuración de correo electrónico -en otras áreas de tu aplicación. - -En este ejemplo, crearemos un ``Mailer`` que contiene correos electrónicos relacionados con el usuario. -Para crear nuestro ``UserMailer``, crea el archivo -**src/Mailer/UserMailer.php**. El contenido del archivo debería verse así:: - - namespace App\Mailer; - - use Cake\Mailer\Mailer; - - class UserMailer extends Mailer - { - public function welcome($user) - { - $this - ->setTo($user->email) - ->setSubject(sprintf('Welcome %s', $user->name)) - ->viewBuilder() - ->setTemplate('welcome_mail'); // Por defecto, se utiliza la plantilla con el mismo nombre que el nombre del método. - } - - public function resetPassword($user) - { - $this - ->setTo($user->email) - ->setSubject('Reset password') - ->setViewVars(['token' => $user->token]); - } - } - - -En nuestro ejemplo, hemos creado dos métodos, uno para enviar un correo electrónico de bienvenida y -otro para enviar un correo electrónico de restablecimiento de contraseña. Cada uno de estos métodos espera una entidad de usuario -y utiliza sus propiedades para configurar cada correo electrónico. - -Ahora podemos usar nuestro ``UserMailer`` para enviar nuestros correos electrónicos relacionados con el usuario -desde cualquier parte de nuestra aplicación. Por ejemplo, si queremos enviar nuestro correo de bienvenida -podríamos hacer lo siguiente:: - - namespace App\Controller; - - use Cake\Mailer\MailerAwareTrait; - - class UsersController extends AppController - { - use MailerAwareTrait; - - public function register() - { - $user = - - $this->Users->newEmptyEntity(); - if ($this->request->is('post')) { - $user = $this->Users->patchEntity($user, $this->request->getData()); - if ($this->Users->save($user)) { - // Enviar correo electrónico de bienvenida. - $this->getMailer('User')->send('welcome', [$user]); - // Redirigir a la página de inicio de sesión u otra página de destino. - return $this->redirect(['controller' => 'Users', 'action' => 'login']); - } - $this->Flash->error(__('Unable to register user. Please try again.')); - } - $this->set(compact('user')); - } - } - -Si quisiéramos separar por completo el envío del correo de bienvenida del usuario de nuestro código de aplicación, podemos hacer que nuestro -`UserMailer` se suscriba al evento `Model.afterSave`. Al suscribirse a un evento, podemos mantener nuestras clases relacionadas con el -usuario completamente libres de lógica e instrucciones relacionadas con el correo electrónico de nuestra aplicación. Por ejemplo, -podríamos agregar lo siguiente a nuestro `UserMailer`:: - - public function implementedEvents() - { - return [ - 'Model.afterSave' => 'onRegistration', - ]; - } - - public function onRegistration(EventInterface $event, EntityInterface $entity, ArrayObject $options) - { - if ($entity->isNew()) { - $this->send('welcome', [$entity]); - } - } - -Ahora puedes registrar el mailer como un oyente de eventos y el método `onRegistration()` se invocará cada vez que se dispare el evento `Model.afterSave`:: - - // Adjuntar al gestor de eventos de Usuarios - $this->Users->getEventManager()->on($this->getMailer('User')); - -.. _email-transport: - -Configuración de Transportes -============================ - -Los mensajes de correo electrónico se entregan mediante transportes. Diferentes transportes te permiten enviar mensajes a través de la función `mail()` -de PHP, servidores SMTP o no enviarlos en absoluto, lo cual es útil para depurar. Configurar transportes te permite mantener los datos de configuración -fuera del código de tu aplicación y simplifica la implementación, ya que simplemente puedes cambiar los datos de configuración. Una configuración de -transporte de ejemplo se ve así:: - - // En config/app.php - 'EmailTransport' => [ - // Configuración de ejemplo para correo - 'default' => [ - 'className' => 'Mail', - ], - // Configuración de ejemplo para SMTP - 'gmail' => [ - 'host' => 'smtp.gmail.com', - 'port' => 587, - 'username' => 'mi@gmail.com', - 'password' => 'secreto', - 'className' => 'Smtp', - 'tls' => true, - ], - ], - -Los transportes también se pueden configurar en tiempo de ejecución utilizando `TransportFactory::setConfig()`:: - - use Cake\Mailer\TransportFactory; - - // Definir un transporte SMTP - TransportFactory::setConfig('gmail', [ - 'host' => 'ssl://smtp.gmail.com', - 'port' => 465, - 'username' => 'mi@gmail.com', - 'password' => 'secreto', - 'className' => 'Smtp' - ]); - -Puedes configurar servidores SMTP SSL, como Gmail. Para hacerlo, coloca el prefijo `ssl://` en el host y configura el valor del puerto en consecuencia. También puedes habilitar SMTP TLS usando la opción `tls`:: - - use Cake\Mailer\TransportFactory; - - TransportFactory::setConfig('gmail', [ - 'host' => 'smtp.gmail.com', - 'port' => 587, - 'username' => 'mi@gmail.com', - 'password' => 'secreto', - 'className' => 'Smtp', - 'tls' => true - ]); - -La configuración anterior habilitaría la comunicación TLS para los mensajes de correo electrónico. - -Para configurar tu mailer para usar un transporte específico, puedes usar el método :php:meth:`Cake\\Mailer\\Mailer::setTransport()` o tener el transporte en tu configuración:: - - - // Usa un transporte con nombre ya configurado usando TransportFactory::setConfig() - $mailer->setTransport('gmail'); - - // Usa un objeto construido. - $mailer->setTransport(new \Cake\Mailer\Transport\DebugTransport()); - -.. warning :: - - Deberás tener habilitado el acceso para aplicaciones menos seguras en tu cuenta de Google para que funcione: - `Permitir que aplicaciones menos seguras accedan a tu cuenta `__. - -.. note :: -   `Configuración SMTP de Gmail `__. - -.. note :: - Para usar SSL + SMTP, necesitarás tener SSL configurado en tu instalación de PHP. - -También se pueden proporcionar opciones de configuración como una cadena :term:`DSN`. Esto es útil cuando trabajas con variables de entorno o proveedores de :term:`PaaS`:: - - TransportFactory::setConfig('default', [ - 'url' => 'smtp://mi@gmail.com:secreto@smtp.gmail.com:587?tls=true', - ]); - -Cuando usas una cadena DSN, puedes definir cualquier parámetro / opción adicional como argumentos de cadena de consulta. - -.. php:staticmethod:: drop($key) - -Una vez configurados, los transportes no se pueden modificar. Para modificar un transporte, primero debes eliminarlo y luego reconfigurarlo. - -Creación de Transportes Personalizados --------------------------------------- - -Puedes crear tus propios transportes para situaciones como enviar correos electrónicos utilizando servicios como SendGrid, MailGun -o Postmark. Para crear tu transporte, primero crea el archivo **src/Mailer/Transport/ExampleTransport.php** (donde Example es el -nombre de tu transporte). Para empezar, tu archivo debería verse así:: - - namespace App\Mailer\Transport; - - use Cake\Mailer\AbstractTransport; - use Cake\Mailer\Message; - - class ExampleTransport extends AbstractTransport - { - public function send(Message $message): array - { - // Haz algo. - } - } - -Debes implementar el método ``send(Message $message)`` con tu lógica personalizada. - -Envío de correos electrónicos sin usar Mailer -============================================= - -El ``Mailer`` es una clase de abstracción de nivel superior que actúa como un puente entre las clases ``Cake\Mailer\Message``, ``Cake\Mailer\Renderer`` y ``Cake\Mailer\AbstractTransport`` para configurar correos electrónicos con una interfaz fluida. - -Si lo deseas, también puedes usar estas clases directamente con el ``Mailer``. - -Por ejemplo:: - - $render = new \Cake\Mailer\Renderer(); - $render->viewBuilder() - ->setTemplate('custom') - ->setLayout('sparkly'); - - $message = new \Cake\Mailer\Message(); - $message - ->setFrom('admin@cakephp.org') - ->setTo('user@foo.com') - ->setBody($render->render()); - - $transport = new \Cake\Mailer\Transport\MailTransport(); - $result = $transport->send($message); - -Incluso puedes omitir el uso del ``Renderer`` y establecer el cuerpo del mensaje directamente -usando los métodos ``Message::setBodyText()`` y ``Message::setBodyHtml()``. - -.. _email-testing: - -Pruebas de Mailers -================== - -Para probar mailers, agrega ``Cake\TestSuite\EmailTrait`` a tu caso de prueba.El ``MailerTrait`` -utiliza ganchos de PHPUnit para reemplazar los transportes de correo electrónico de tu aplicación -con un proxy que intercepta los mensajes de correo electrónico y te permite hacer afirmaciones -sobre el correo que se enviaría. - -Agrega el trait a tu caso de prueba para comenzar a probar correos electrónicos, y carga rutas si tus -correos electrónicos necesitan generar URL:: - - namespace App\Test\TestCase\Mailer; - - use App\Mailer\WelcomeMailer; - use App\Model\Entity\User; - - use Cake\TestSuite\EmailTrait; - use Cake\TestSuite\TestCase; - - class WelcomeMailerTestCase extends TestCase - { - use EmailTrait; - - public function setUp(): void - { - parent::setUp(); - $this->loadRoutes(); - } - } - -Supongamos que tenemos un mailer que envía correos electrónicos de bienvenida cuando un nuevo usuario -se registra. Queremos comprobar que el asunto y el cuerpo contienen el nombre del usuario:: - - // en nuestra clase WelcomeMailerTestCase. - public function testName() - { - $user = new User([ - 'name' => 'Alice Alittea', - 'email' => 'alice@example.org', - ]); - $mailer = new WelcomeMailer(); - $mailer->send('welcome', [$user]); - - $this->assertMailSentTo($user->email); - $this->assertMailContainsText('Hola ' . $user->name); - $this->assertMailContainsText('¡Bienvenido a CakePHP!'); - } - -Métodos de afirmación ----------------------- - -El trait ``Cake\TestSuite\EmailTrait`` proporciona las siguientes afirmaciones:: - - // Asegura que se enviaron un número esperado de correos electrónicos - $this->assertMailCount($count); - - // Asegura que no se enviaron correos electrónicos - $this->assertNoMailSent(); - - // Asegura que se envió un correo electrónico a una dirección - $this->assertMailSentTo($address); - - // Asegura que se envió un correo electrónico desde una dirección - $this->assertMailSentFrom($emailAddress); - $this->assertMailSentFrom([$emailAddress => $displayName]); - - // Asegura que un correo electrónico contiene los contenidos esperados - $this->assertMailContains($contents); - - // Asegura que un correo electrónico contiene los contenidos HTML esperados - $this->assertMailContainsHtml($contents); - - // Asegura que un correo electrónico contiene los contenidos de texto esperados - $this->assertMailContainsText($contents); - - // Asegura que un correo electrónico contiene el valor esperado dentro de un getter de Message (por ejemplo, "subject") - $this->assertMailSentWith($expected, $parameter); - - // Asegura que un correo electrónico en un índice específico se envió a una dirección - $this->assertMailSentToAt($at, $address); - - // Asegura que un correo electrónico en un índice específico se envió desde una dirección - $this->assertMailSentFromAt($at, $address); - - // Asegura que un correo electrónico en un índice específico contiene los contenidos esperados - $this->assertMailContainsAt($at, $contents); - - // Asegura que un correo electrónico en un índice específico contiene los contenidos HTML esperados - $this->assertMailContainsHtmlAt($at, $contents); - - // Asegura que un correo electrónico en un índice específico contiene los contenidos de texto esperados - $this->assertMailContainsTextAt($at, $contents); - - // Asegura que un correo electrónico contiene un archivo adjunto - $this->assertMailContainsAttachment('test.png'); - - // Asegura que un correo electrónico en un índice específico contiene el valor esperado dentro de un getter de Message (por ejemplo, "cc") - $this->assertMailSentWithAt($at, $expected, $parameter); - - // Asegura que un correo electrónico contiene una subcadena en el asunto. - $this->assertMailSubjectContains('Oferta Gratuita'); - - // Asegura que un correo electrónico en un índice específico contiene una subcadena en el asunto. - $this->assertMailSubjectContainsAt(1, 'Oferta Gratuita'); - -.. meta:: - :title lang=es: Correo Electrónico - :keywords lang=en: sending mail,email sender,envelope sender,php class,database configuration,sending emails,commands,smtp,transports,attributes,array,config,flexibility,php email,new email,sending email,models diff --git a/es/core-libraries/events.rst b/es/core-libraries/events.rst deleted file mode 100644 index 99ad4eff40..0000000000 --- a/es/core-libraries/events.rst +++ /dev/null @@ -1,15 +0,0 @@ -Events System -############# - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. - -.. meta:: - :title lang=es: Events system - :keywords lang=es: events, dispatch, decoupling, cakephp, callbacks, triggers, hooks, php diff --git a/es/core-libraries/form.rst b/es/core-libraries/form.rst deleted file mode 100644 index 77345fb7ed..0000000000 --- a/es/core-libraries/form.rst +++ /dev/null @@ -1,15 +0,0 @@ -Modelless Forms -############### - -.. php:namespace:: Cake\Form - -.. php:class:: Form - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. diff --git a/es/core-libraries/global-constants-and-functions.rst b/es/core-libraries/global-constants-and-functions.rst deleted file mode 100644 index dddb8aa52a..0000000000 --- a/es/core-libraries/global-constants-and-functions.rst +++ /dev/null @@ -1,15 +0,0 @@ -Constants & Functions -##################### - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. - -.. meta:: - :title lang=es: Global Constants and Functions - :keywords lang=es: internationalization and localization,global constants,example config,array php,convenience functions,core libraries,component classes,optional number,global functions,string string,core classes,format strings,unread messages,placeholders,useful functions,sprintf,arrays,parameters,existence,translations diff --git a/es/core-libraries/hash.rst b/es/core-libraries/hash.rst deleted file mode 100644 index f29389a73b..0000000000 --- a/es/core-libraries/hash.rst +++ /dev/null @@ -1,15 +0,0 @@ -Hash -#### - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. - -.. meta:: - :title lang=es: Hash - :keywords lang=es: array array,path array,array name,numeric key,regular expression,result set,person name,brackets,syntax,cakephp,elements,php,set path diff --git a/es/core-libraries/httpclient.rst b/es/core-libraries/httpclient.rst deleted file mode 100644 index 311e1c7607..0000000000 --- a/es/core-libraries/httpclient.rst +++ /dev/null @@ -1,19 +0,0 @@ -Http Client -########### - -.. php:namespace:: Cake\Network\Http - -.. php:class:: Client(mixed $config = []) - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. - -.. meta:: - :title lang=es: HttpClient - :keywords lang=es: array name,array data,query parameter,query string,php class,string query,test type,string data,google,query results,webservices,apis,parameters,cakephp,meth,search results diff --git a/es/core-libraries/inflector.rst b/es/core-libraries/inflector.rst deleted file mode 100644 index 1bb5d1af2b..0000000000 --- a/es/core-libraries/inflector.rst +++ /dev/null @@ -1,181 +0,0 @@ -Inflector -######### - -.. php:namespace:: Cake\Utility - -.. php:class:: Inflector - -La clase `Inflector` toma una cadena y puede manipularla para manejar variaciones de palabras como -pluralización o conversión a formato camello (camelCase). Por lo general, se accede a esta clase de -manera estática. Por ejemplo: - -``Inflector::pluralize('example')`` devuelve "examples". - -Puedes probar las inflecciones en línea en `inflector.cakephp.org -`_ or `sandbox.dereuromark.de -`_. - -.. _inflector-methods-summary: - -Métodos integrados en Inflector y su resultado -============================================== - -Los métodos integrados en el Inflector y los resultados que generan al proporcionarles un argumento compuesto por varias palabras: - -+-------------------+---------------+---------------+ -| Método | Argumento | Resultado | -+===================+===============+===============+ -| ``pluralize()`` | BigApple | BigApples | -+ +---------------+---------------+ -| | big_apple | big_apples | -+-------------------+---------------+---------------+ -| ``singularize()`` | BigApples | BigApple | -+ +---------------+---------------+ -| | big_apples | big_apple | -+-------------------+---------------+---------------+ -| ``camelize()`` | big_apples | BigApples | -+ +---------------+---------------+ -| | big apple | BigApple | -+-------------------+---------------+---------------+ -| ``underscore()`` | BigApples | big_apples | -+ +---------------+---------------+ -| | Big Apples | big apples | -+-------------------+---------------+---------------+ -| ``humanize()`` | big_apples | Big Apples | -+ +---------------+---------------+ -| | bigApple | BigApple | -+-------------------+---------------+---------------+ -| ``classify()`` | big_apples | BigApple | -+ +---------------+---------------+ -| | big apple | BigApple | -+-------------------+---------------+---------------+ -| ``dasherize()`` | BigApples | big-apples | -+ +---------------+---------------+ -| | big apple | big apple | -+-------------------+---------------+---------------+ -| ``tableize()`` | BigApple | big_apples | -+ +---------------+---------------+ -| | Big Apple | big apples | -+-------------------+---------------+---------------+ -| ``variable()`` | big_apple | bigApple | -+ +---------------+---------------+ -| | big apples | bigApples | -+-------------------+---------------+---------------+ - -Generando formas Plural y Singular -================================== - -.. php:staticmethod:: singularize($singular) -.. php:staticmethod:: pluralize($singular) - -Both ``pluralize`` and ``singularize()`` work on most English nouns. If you need -to support other languages, you can use :ref:`inflection-configuration` to -customize the rules used:: - - // Apples - echo Inflector::pluralize('Apple'); - -.. note:: - - ``pluralize()`` no debería ser usado en un nombre que ya está en su forma plural. - -.. code-block:: php - - // Person - echo Inflector::singularize('People'); - -.. note:: - - ``singularize()`` no debería ser usado en un nombre que ya está en su forma singular. - -Generando formas CamelCase y under_scored -========================================= - -.. php:staticmethod:: camelize($underscored) -.. php:staticmethod:: underscore($camelCase) - -Estos métodos son útiles cuando creas nombres de clases o de propiedades:: - - // ApplePie - Inflector::camelize('Apple_pie') - - // apple_pie - Inflector::underscore('ApplePie'); - -Nótese que el método *underscore* sólo convertirá palabras en formato *CamelCase*. -Palabras que contengan espacios serán transformadas a minúscula pero no contendrán un guión bajo. - -Generando formas legibles por humanos -===================================== - -.. php:staticmethod:: humanize($underscored) - -Este método es útil cuando se quiere convertir una palabra de la forma *under_scored* al formato "Título" para que sea legible por un ser humano:: - - // Apple Pie - Inflector::humanize('apple_pie'); - -Generando formas de tabla y nombre de clase -=========================================== - -.. php:staticmethod:: classify($underscored) -.. php:staticmethod:: dasherize($dashed) -.. php:staticmethod:: tableize($camelCase) - -Cuando se genera código, o usando las convenciones de CakePHP, puedes necesitar generar inflecciones para los nombres de tabla o de clase:: - - // UserProfileSetting - Inflector::classify('user_profile_settings'); - - // user-profile-setting - Inflector::dasherize('UserProfileSetting'); - - // user_profile_settings - Inflector::tableize('UserProfileSetting'); - -Generando Nombres de Variables -============================== - -.. php:staticmethod:: variable($underscored) - -Los nombres de variable son a menudo útiles cuando se hacen tareas de meta-programación que involucran generar código o hacer trabajo basado en convenciones:: - - // applePie - Inflector::variable('apple_pie'); - - -.. _inflection-configuration: - -Configurando las Inflecciones -============================= - -Las convenciones de nomenclatura de CakePHP pueden ser muy útiles: puedes nombrar tu -tabla de base de datos como ``big_boxes``, tu modelo como ``BigBoxes``, tu controlador -como ``BigBoxesController``, y todo funcionará automáticamente juntos. La forma en que -CakePHP sabe cómo vincular las cosas es *inflectando* las palabras entre sus formas -singular y plural. - -Existen ocasiones (especialmente para nuestros amigos que no hablan inglés) en las que -podrías encontrarte con situaciones donde el inflector de CakePHP (la clase que pluraliza, -singulariza, utiliza notación camello y subrayados) puede no funcionar como deseas. Si -CakePHP no reconoce tus "Foci" o "Fish", puedes indicarle a CakePHP acerca de tus casos especiales. - -Cargando Inflecciones Personalizadas ------------------------------------- - -.. php:staticmethod:: rules($type, $rules, $reset = false) - -Define nuevas reglas de inflexión y transliteración para que Inflector las utilice. A menudo, este método se utiliza -en tu archivo **config/bootstrap.php**:: - - Inflector::rules('singular', ['/^(bil)er$/i' => '\1', '/^(inflec|contribu)tors$/i' => '\1ta']); - Inflector::rules('uninflected', ['singulars']); - Inflector::rules('irregular', ['phylum' => 'phyla']); // The key is singular form, value is plural form - -Las reglas suministradas se fusionarán en los conjuntos de inflexión respectivos definidos en ``Cake/Utility/Inflector``, -y las reglas añadidas tendrán prioridad sobre las reglas principales del núcleo. Puedes usar ``Inflector::reset()`` -para eliminar las reglas y restaurar el estado original del Inflector. - -.. meta:: - :title lang=es: Objeto Inflector - :keywords lang=en: apple orange,word variations,apple pie,person man,latin versions,profile settings,php class,initial state,puree,slug,apples,oranges,user profile,underscore diff --git a/es/core-libraries/internationalization-and-localization.rst b/es/core-libraries/internationalization-and-localization.rst deleted file mode 100644 index 2fafe177fb..0000000000 --- a/es/core-libraries/internationalization-and-localization.rst +++ /dev/null @@ -1,15 +0,0 @@ -Internationalization & Localization -################################### - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. - -.. meta:: - :title lang=es: Internationalization & Localization - :keywords lang=es: internationalization localization,internationalization and localization,language application,gettext,l10n,pot,i18n,translation,languages diff --git a/es/core-libraries/logging.rst b/es/core-libraries/logging.rst deleted file mode 100644 index fe4f49c4da..0000000000 --- a/es/core-libraries/logging.rst +++ /dev/null @@ -1,502 +0,0 @@ -Logging -####### - -Si bien la configuración de la clase Configure de CakePHP puede ayudarte a ver -lo que está sucediendo en el sistema, hay momentos en los que necesitarás registrar -datos en el disco para averiguar lo que está ocurriendo. Con tecnologías como SOAP, AJAX y API REST, -la depuración puede ser bastante difícil. - -Logging también puede ser una forma de averiguar lo que ha estado ocurriendo -en tu aplicación con el tiempo. ¿Qué términos de búsqueda se están utilizando? -¿Qué tipos de errores están viendo mis usuarios? ¿Con qué frecuencia se ejecuta -una consulta en particular? - -Logging data in CakePHP is done with the ``log()`` function. It is provided by the -``LogTrait``, which is the common ancestor for many CakePHP classes. If the -context is a CakePHP class (Controller, Component, View,...), you can log your -data. You can also use ``Log::write()`` directly. See :ref:`writing-to-logs`. - -El registro de datos en CakePHP se realiza con la función "log()". Esta función es proporcionada por el -"LogTrait", que es el ancestro común de muchas clases de CakePHP. Si el contexto es una clase de CakePHP -(Controlador, Componente, Vista, etc.), puedes registrar tus datos. También puedes usar "Log::write()" -directamente. Consulta la sección :ref:`writing-to-logs` para obtener más información. - -.. _log-configuration: - -Logging Configuration -===================== - -La configuración de ``Log`` debe realizarse durante la fase de arranque de tu aplicación. -El archivo **config/app.php** está diseñado precisamente para esto. Puedes definir tantos -``loggers`` como necesite tu aplicación. Los ``loggers`` deben configurarse utilizando la clase -:php:class:`Cake\\Log\\Log`. Un ejemplo sería:: - - use Cake\Log\Engine\FileLog; - use Cake\Log\Log; - - // Nombre de la clase utilizando la constante 'class' del logger. - Log::setConfig('info', [ - 'className' => FileLog::class, - 'path' => LOGS, - 'levels' => ['info'], - 'file' => 'info', - ]); - - // Nombre de clase corto - Log::setConfig('debug', [ - 'className' => 'File', - 'path' => LOGS, - 'levels' => ['notice', 'debug'], - 'file' => 'debug', - ]); - - // Fully namespaced name. - Log::setConfig('error', [ - 'className' => 'Cake\Log\Engine\FileLog', - 'path' => LOGS, - 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'], - 'file' => 'error', - ]); - -Lo anterior crea tres loggers, llamados ``info``, ``debug`` and ``error``. -Cada uno está configurado para manejar diferentes niveles de mensajes. -También almacenan sus mensajes de registro en archivos separados, de esta manera, -podemos separar los registros de depuración/aviso/información de los errores más graves. -Consulta la sección sobr :ref:`logging-levels` para obtener más información sobre -los diferentes niveles y lo que significan. - -Una vez que se crea una configuración, no se puede cambiar. En su lugar, debes eliminar -la configuración y volver a crearla utilizando :php:meth:`Cake\\Log\\Log::drop()` y -:php:meth:`Cake\\Log\\Log::setConfig()`. - -También es posible crear loggers proporcionando un cierre (closure). Esto es útil -cuando necesitas un control completo sobre cómo se construye el objeto del logger. El cierre -debe devolver la instancia del logger. Por ejemplo:: - - Log::setConfig('special', function () { - return new \Cake\Log\Engine\FileLog(['path' => LOGS, 'file' => 'log']); - }); - - -Las opciones de configuración también se pueden proporcionar como una cadena :term:`DSN`. Esto es -útil cuando se trabaja con variables de entorno o proveedores :term:`PaaS`:: - - Log::setConfig('error', [ - 'url' => 'file:///full/path/to/logs/?levels[]=warning&levels[]=error&file=error', - ]); - -.. warning:: - Si no configuras motores de registro (logging), los mensajes de log no se almacenarán. - -Registro de Errores y Excepciones -================================= - -Los errores y excepciones también pueden registrarse configurando los valores correspondientes en tu archivo **config/app.php**. -Los errores se mostrarán cuando el modo de depuración esté en ``true`` y se registrarán en los archivos de log cuando el modo de depuración esté en ``false``. -Para registrar excepciones no capturadas, configura la opción ``log`` como ``true``. -Consulta ::doc:`/development/configuration` para obtener más información. - -.. _writing-to-logs: - -Escribiendo en los archivos de Log -=================================== - -Escribir en los archivos de registro se puede hacer de dos maneras diferentes. La primera es -utilizando el método estático ::php:meth:`Cake\\Log\\Log::write()`:: - - Log::write('debug', 'Something did not work'); - -La segunda opción es utilizar la función de acceso directo ``log()`` disponible en cualquier clase -que utilice el ``LogTrait``. Llamar a``log()`` llamará internamente a``Log::write()``:: - - // Ejecutando esto dentro de una clase que utiliza LogTrait - $this->log('Something did not work!', 'debug'); - -Todos los ``log`` configurados se escriben secuencialmente cada vez que se llama a -:php:meth:`Cake\\Log\\Log::write()`. Si no has configurado ningún motor de registro, -``log()`` devolverá "false" y no se escribirán mensajes de registro. - -Usando marcadores de posición (placeholders) en mensajes ---------------------------------------------------------- - -Si necesitas registrar datos definidos dinámicamente, puedes utilizar marcadores de posición en tus -mensajes de registro y proporcionar un array de pares clave/valor en el parámetro ``$context`` -como sigue:: - - - // Se registrará `No se pudo procesar para el usuario id = 1` - Log::write('error', 'No se pudo procesar para el usuario id ={user}', ['user' => $user->id]); - -Los marcadores (placeholders) que no tienen claves definidas no serán reemplazados. -Si necesitas utilizar una palabra entre llaves de forma literal, debes escapar el marcador:: - - - // Se registrará `No {replace}` - Log::write('error', 'No \\{replace}', ['replace' => 'no']); - -Si incluyes objetos en los marcadores, esos objetos deben implementar -uno de los siguientes métodos: - -* ``__toString()`` -* ``toArray()`` -* ``__debugInfo()`` - -.. _logging-levels: - -Usando Niveles ---------------- - -CakePHP admite el conjunto estándar de niveles de registro POSIX. Cada nivel representa un aumento -en el nivel de gravedad: - -* Emergency: el sistema no es utilizable -* Alert: se debe tomar una acción inmediata -* Critical: condiciones críticas -* Error: condiciones de error -* Warning: condiciones de advertencia -* Notice: condiciones normales pero significativas -* Info: mensajes informativos -* Debug: mensajes de depuración - -Puedes hacer referencia a estos niveles por nombre al configurar lo ``loggers`` y al escribir -mensajes de registro. Alternativamente, puedes utilizar métodos de conveniencia como : -:php:meth:`Cake\\Log\\Log::error()` para indicar claramente el nivel de registro. -Utilizar un nivel que no esté en la lista de niveles anteriores resultará en una excepción. - -.. note:: - Cuando ``levels`` se establece en un valor vacío en la configuración de un ``logger``, - aceptará mensajes de cualquier nivel. - -.. _logging-scopes: - -Ámbitos de Registro (scope) ----------------------------- - -En muchas ocasiones, querrás configurar diferentes comportamientos de registro para diferentes -subsistemas o partes de tu aplicación. Tomemos como ejemplo una tienda en línea. -Probablemente, quieras manejar el registro de pedidos y pagos de manera diferente a como lo haces -con otros registros menos críticos. - -CakePHP expone este concepto como ámbitos de registro. Cuando se escriben mensajes de registro, -puedes incluir un nombre de ámbito ``scope``. Si hay un registrador configurado para ese ámbito, -los mensajes de registro se dirigirán a esos ``loggers``. Por ejemplo:: - - use Cake\Log\Engine\FileLog; - - // Configura logs/shops.log para recibir todos los niveles, pero solo aquellos con ``scope`` - // `orders` y `payments`. - Log::setConfig('shops', [ - 'className' => FileLog::class, - 'path' => LOGS, - 'levels' => [], - 'scopes' => ['orders', 'payments'], - 'file' => 'shops.log', - ]); - - // Configura logs/payments.log para recibir todos los niveles, pero solo aquellos con ``scope`` - // `payments`. - Log::setConfig('payments', [ - 'className' => FileLog::class, - 'path' => LOGS, - 'levels' => [], - 'scopes' => ['payments'], - 'file' => 'payments.log', - ]); - - Log::warning('this gets written only to shops.log', ['scope' => ['orders']]); - Log::warning('this gets written to both shops.log and payments.log', ['scope' => ['payments']]); - -Los ``scopes`` también se pueden pasar como una cadena única o como una matriz indexada numéricamente. -Ten en cuenta que al usar esta forma, se limitará la capacidad de pasar más datos como contexto:: - - Log::warning('This is a warning', ['orders']); - Log::warning('This is a warning', 'payments'); - -.. note:: - Cuando ``scopes`` se establece como un arreglo vacío o null en la configuración de un ``logger``, - aceptará mensajes de cualquier ``scope``. Establecerlo como false solo coincidirá con mensajes sin ``scope``. - -.. _file-log: - -Guardando logs en Archivos -=========================== - -Como su nombre indica, ``FileLog`` escribe mensajes de registro en archivos. El nivel del mensaje -de registro que se está escribiendo determina el nombre del archivo en el que se almacena el mensaje. -Si no se proporciona un nivel, se utiliza :php:const:`LOG_ERR`, que escribe en el registro de errores. -La ubicación de registro predeterminada es **logs/$level.log**:: - - // Es ejecutado asi dentro de una clase CakePHP - $this->log("Something didn't work!"); - - // Se añadirá lo siguiente al archivo logs/error.log. - // 2007-11-02 10:22:02 Error: Something didn't work! - -El directorio configurado debe tener permisos de escritura por el usuario del servidor web para -que el registro funcione correctamente. - -Puedes configurar ubicaciones adicionales o alternativas para FileLog al configurar un registrador. -FileLog acepta un "path" que permite utilizar rutas personalizadas:: - - Log::setConfig('custom_path', [ - 'className' => 'File', - 'path' => '/path/to/custom/place/' - ]); - -El motor de ``FileLog`` toma las siguientes opciones: - -* ``size`` Se utiliza para implementar una rotación básica de archivos de registro. Si el tamaño - del archivo de registro alcanza el tamaño especificado, el archivo existente se renombra agregando - una marca de tiempo al nombre de archivo y se crea un nuevo archivo de registro. Puede ser un valor - entero en bytes o valores como '10MB', '100KB', etc. El valor predeterminado es 10MB. -* ``rotate`` Los archivos de registro se rotan un número especificado de veces antes de ser eliminados. - Si el valor es 0, se eliminan las versiones antiguas en lugar de rotarlas. El valor predeterminado es 10. -* ``mask`` Establece los permisos de archivo para los archivos creados. Si se deja vacío, se utilizan - los permisos predeterminados. - -.. note:: - - Los directorios faltantes se crearán automáticamente para evitar errores innecesarios - cuando se utiliza FileEngine. - -.. _syslog-log: - -Guardando logs en Syslog -========================= - -En entornos de producción, se recomienda encarecidamente configurar tu sistema para utilizar el -syslog en lugar del guardar los logs en archivos. Esto mejorará el rendimiento, ya que cualquier -escritura se realizará de manera (casi) no bloqueante y el ``logger`` del sistema operativo se -puede configurar de forma independiente para rotar archivos, preprocesar escrituras o -utilizar un almacenamiento completamente diferente para tus registros. - -Usar syslog es prácticamente como usar el motor de registro de archivos predeterminado, simplemente -necesitas especificar ``Syslog`` como el motor a utilizar para el registro de logs. El siguiente -fragmento de configuración reemplazará el ``logger`` predeterminado con syslog, esto se debe hacer -en el archivo **config/bootstrap.php**:: - - Log::setConfig('default', [ - 'engine' => 'Syslog' - ]); - -El arreglo de configuración aceptado para el motor de registro Syslog comprende -las siguientes claves: - -* ``format``: Una cadena de plantilla sprintf con dos marcadores de posición (placeholdes), - el primero para el nivel de error y el segundo para el mensaje en sí. Esta clave es - útil para agregar información adicional sobre el servidor o el proceso en el mensaje - registrado. Por ejemplo: ``%s -Servidor web 1 - %s`` se verá como - ``error - Servidor web 1 - Ocurrió un error en esta solicitud`` después de reemplazar - los placeholders. Esta opción está obsoleta. Deberías usar :ref:`logging-formatters` en su lugar. -* ``prefix``: Una cadena que se utilizará como prefijo para cada mensaje registrado. -* ``flag``: Una bandera tipo ``int`` que se usará para abrir la conexión al registro, - por defecto se usará ``LOG_ODELAY```. Consulta la documentación de ``openlog`` para ver más opciones. -* ``facility``: El espacio de registro a utilizar en syslog. Por defecto se utiliza ``LOG_USER``. - Consulta la documentación de ``syslog`` para ver más opciones. - -Creación de Motores de Logs -================================= - -Los motores de registro pueden formar parte de tu aplicación o de plugins. Por ejemplo, -si tuvieras un registro en base de datos llamado ``DatabaseLog``, como parte de tu aplicación -se colocaría en **src/Log/Engine/DatabaseLog.php**. Como parte de un plugin se colocaría en -**plugins/LoggingPack/src/Log/Engine/DatabaseLog.php**. Para configurar el motor de registro, -debes usar :php:meth:`Cake\\Log\\Log::setConfig()`. Por ejemplo, la configuración de nuestro -DatabaseLog se vería así:: - - // Para src/Log - Log::setConfig('otherFile', [ - 'className' => 'Database', - 'model' => 'LogEntry', - // ... - ]); - - // Para el plugin llamado LoggingPack - Log::setConfig('otherFile', [ - 'className' => 'LoggingPack.Database', - 'model' => 'LogEntry', - // ... - ]); - -Al configurar un motor de registro, el parámetro ``className`` se utiliza para localizar -y cargar el controlador de registro. Todas las demás propiedades de configuración se pasan -al constructor del motor de registro como un array.:: - - namespace App\Log\Engine; - use Cake\Log\Engine\BaseLog; - - class DatabaseLog extends BaseLog - { - public function __construct(array $config = []) - { - parent::__construct($config); - // ... - } - - public function log($level, string $message, array $context = []) - { - // Write to the database. - } - } - -CakePHP requiere que todos los motores de registro implementen Psr\Log\LoggerInterface. -La clase :php:class:`Cake\Log\Engine\BaseLog` es una forma sencilla de cumplir con la interfaz, -ya que solo requiere que implementes el método log(). - -.. _logging-formatters: - - -Formateadores de Logs ---------------------------- -Los formateadores de registro te permiten controlar cómo se formatean los mensajes de registro -de forma independiente al motor de almacenamiento. Cada motor de registro proporcionado por -defecto viene con un formateador configurado para mantener una salida compatible con versiones -anteriores. Sin embargo, puedes ajustar los formateadores para satisfacer tus requisitos. -Los formateadores se configuran junto al motor de registro:: - - use Cake\Log\Engine\SyslogLog; - use App\Log\Formatter\CustomFormatter; - - // Configuración de formato simple sin opciones. - Log::setConfig('error', [ - 'className' => SyslogLog::class, - 'formatter' => CustomFormatter::class, - ]); - - // Configurar un formateador con algunas opciones. - Log::setConfig('error', [ - 'className' => SyslogLog::class, - 'formatter' => [ - 'className' => CustomFormatter::class, - 'key' => 'value', - ], - ]); - - -Para implementar tu propio formateador de registro, necesitas extender -``Cake\Log\Format\AbstractFormatter`` o una de sus subclases. El método principal que -debes implementar es ``format($level, $message, $context)`` que es responsable de -formatear los mensajes de log. - - -Log API -======= - -.. php:namespace:: Cake\Log - -.. php:class:: Log - -Una clase sencilla para escribir logs. - -.. php:staticmethod:: setConfig($key, $config) - - :param string $name: Nombre para el registro al que se está conectando, utilizado para - eliminar un registro más adelante. - :param array $config: Arreglo de configuración y argumentos del constructor para el ``logger``. - - Devuelve o establece la configuración de un ``logger``. Para mas información ver :ref:`log-configuration`. - -.. php:staticmethod:: configured() - - :returns: Arreglo de los ``loggers`` configurados - - Devuelve los nombres de los ``loggers`` configurados. - -.. php:staticmethod:: drop($name) - - :param string $name: Nombre del ``logger`` del que ya no deseas recibir mensajes. - -.. php:staticmethod:: write($level, $message, $scope = []) - - Escribe un mensaje en todos los ``loggers`` configurados - ``$level`` indica el nivel del mensaje de registro que se está creando. - ``$message`` es el mensaje de la entrada del registro que se está escribiendo. - ``$scope`` es el(los) ámbito(s) en el que se está creando un mensaje de registro. - -.. php:staticmethod:: levels() - - -Llama a este método sin argumentos, por ejemplo: `Log::levels()` para obtener -la configuración actual del nivel. - - -Métodos de conveniencia ------------------------- - -Se agregaron los siguientes métodos útiles para registrar `$message` con el nivel -de registro apropiado. - -.. php:staticmethod:: emergency($message, $scope = []) -.. php:staticmethod:: alert($message, $scope = []) -.. php:staticmethod:: critical($message, $scope = []) -.. php:staticmethod:: error($message, $scope = []) -.. php:staticmethod:: warning($message, $scope = []) -.. php:staticmethod:: notice($message, $scope = []) -.. php:staticmethod:: info($message, $scope = []) -.. php:staticmethod:: debug($message, $scope = []) - -Logging Trait -============== - -.. php:trait:: LogTrait - - Un ``trait`` que proporciona métodos abreviados para el registro de mensajes. - -.. php:method:: log($msg, $level = LOG_ERR) - - Agregar un mensaje al log. De forma predeterminada, los mensajes se registran - como mensajes de ERROR. - - -Usando Monolog -================ - -Monolog es una librería de logging popular en PHP. Dado que implementa las mismas interfaces -que los ``loggers`` de CakePHP, puedes usarlos en tu aplicación como el ``logger`` predeterminado. - -Una vez instalado Monolog utilizando composer, configura el ``logger`` usando el método -``Log::setConfig()``:: - - // config/bootstrap.php - - use Monolog\Logger; - use Monolog\Handler\StreamHandler; - - Log::setConfig('default', function () { - $log = new Logger('app'); - $log->pushHandler(new StreamHandler('ruta/a/tu/combined.log')); - - return $log; - }); - - // Opcionalmente deja de usar los ``loggers`` predeterminados que ahora son redundantes. - Log::drop('debug'); - Log::drop('error'); - -Utiliza métodos similares si deseas configurar un ``logger`` diferente para tu consola:: - - // config/bootstrap_cli.php - - use Monolog\Logger; - use Monolog\Handler\StreamHandler; - - Log::setConfig('default', function () { - $log = new Logger('cli'); - $log->pushHandler(new StreamHandler('ruta/a/tu/combined-cli.log')); - - return $log; - }); - - // Opcionalmente deja de usar los ``logger`` predeterminados redundantes para la línea de comando. - Configure::delete('Log.debug'); - Configure::delete('Log.error'); - -.. note:: - - Cuando uses un ``logger`` específico para la consola, asegúrate de configurar condicionalmente tu ``logger`` de aplicación. - Esto evitará entradas de registro duplicadas. - -.. meta:: - :title lang=es: Logging - :description lang=en: Registra datos de CakePHP a disco para ayudar a depurar la aplicación a lo largo de largos períodos de tiempo - :keywords lang=en: cakephp logging,log errors,debug,logging data,cakelog class,ajax logging,soap logging,debugging,logs, bitácora de eventos, registro de datos, registro, depuración diff --git a/es/core-libraries/number.rst b/es/core-libraries/number.rst deleted file mode 100644 index 5edea0903e..0000000000 --- a/es/core-libraries/number.rst +++ /dev/null @@ -1,354 +0,0 @@ -Clase Number -############ - -.. php:namespace:: Cake\I18n - -.. php:class:: Number - -Si necesitas las funcionalidades de :php:class:`NumberHelper` fuera de una vista, utiliza la clase ``Number``:: - - namespace App\Controller; - - use Cake\I18n\Number; - - class UsersController extends AppController - { - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('Authentication.Authentication'); - } - - public function afterLogin() - { - $identity = $this->Authentication->getIdentity(); - $storageUsed = $identity->storage_used; - if ($storageUsed > 5000000) { - // Notificar a los usuarios de su cuota - $this->Flash->success(__('Estás usando {0} de almacenamiento', Number::toReadableSize($storageUsed))); - } - } - } - -.. start-cakenumber - -Todas estas funciones devuelven el número formateado; no imprimen automáticamente la salida en la vista. - -Formato de Valores Monetarios -============================= - -.. php:method:: currency(mixed $value, string $currency = null, array $options = []) - -Este método se utiliza para mostrar un número en formatos de moneda comunes (EUR, GBP, USD), basándose en el código de moneda de tres letras ISO 4217. Su uso en una vista se ve así:: - - // Llamado como NumberHelper - echo $this->Number->currency($value, $currency); - - // Llamado como Number - echo Number::currency($value, $currency); - -El primer parámetro, ``$value``, debería ser un número de punto flotante que representa la cantidad de dinero que estás expresando. El segundo parámetro es una cadena utilizada para elegir un esquema de formato de moneda predefinido: - -+---------------------+----------------------------------------------------+ -| $currency | 1234,56, formateado por tipo de moneda | -+=====================+====================================================+ -| EUR | €1.234,56 | -+---------------------+----------------------------------------------------+ -| GBP | £1.234,56 | -+---------------------+----------------------------------------------------+ -| USD | $1.234,56 | -+---------------------+----------------------------------------------------+ - -El tercer parámetro es un arreglo de opciones para definir aún más la salida. Las siguientes opciones están disponibles: - -+---------------------+----------------------------------------------------+ -| Opción | Descripción | -+=====================+====================================================+ -| before | Texto para mostrar antes del número formateado. | -+---------------------+----------------------------------------------------+ -| after | Texto para mostrar después del número formateado. | -+---------------------+----------------------------------------------------+ -| zero | El texto a usar para los valores cero; puede ser | -| | una cadena o un número, por ejemplo, 0, '¡Gratis!'.| -+---------------------+----------------------------------------------------+ -| places | Número de lugares decimales a usar, por ejemplo, 2 | -+---------------------+----------------------------------------------------+ -| precision | Número máximo de lugares decimales a usar, | -| | por ejemplo, 2. | -+---------------------+----------------------------------------------------+ -| locale | El nombre de la localidad a usar para formatear | -| | el número, por ejemplo, "es_ES". | -+---------------------+----------------------------------------------------+ -| fractionSymbol | Cadena a usar para números fraccionarios, por | -| | ejemplo, 'centavos'. | -+---------------------+----------------------------------------------------+ -| fractionPosition | Ya sea 'antes' o 'después' para colocar el símbolo | -| | fraccionario. | -+---------------------+----------------------------------------------------+ -| pattern | Un patrón de número ICU para usar para formatear el| -| | número, por ejemplo, #,###.00. | -+---------------------+----------------------------------------------------+ -| useIntlCode | Establecer en ``true`` para reemplazar el símbolo | -| | de moneda con el código de moneda internacional. | -+---------------------+----------------------------------------------------+ - -Si el valor de ``$currency`` es ``null``, la moneda predeterminada se recuperará de -:php:meth:`Cake\\I18n\\Number::defaultCurrency()`. Para formatear monedas en un -formato de contabilidad, debes establecer el formato de la moneda:: - - Number::setDefaultCurrencyFormat(Number::FORMAT_CURRENCY_ACCOUNTING); - -Configurar la Moneda Predeterminada -=================================== - -.. php:method:: setDefaultCurrency($currency) - -Configura la moneda predeterminada. Esto evita la necesidad de pasar siempre la -moneda a :php:meth:`Cake\\I18n\\Number::currency()` y cambiar todas las -salidas de moneda configurando otro valor predeterminado. Si ``$currency`` se establece en ``null``, -se eliminará el valor almacenado actualmente. - -Obtener la Moneda Predeterminada -================================ - -.. php:method:: getDefaultCurrency() - -Obtén la moneda predeterminada. Si la moneda predeterminada se configuró anteriormente utilizando -``setDefaultCurrency()``, se devolverá ese valor. De forma predeterminada, recuperará el valor de la ini de ``intl.default_locale`` si está configurado y ``'en_US'`` si no lo está. - -Formato de Números de Punto Flotante -==================================== - -.. php:method:: precision(float $value, int $precision = 3, array $options = []) - -Este método muestra un número con la cantidad especificada de precisión (lugares decimales). Se redondeará para mantener el -nivel de precisión definido. :: - - // Llamado como NumberHelper - echo $this->Number->precision(456.91873645, 2); - - // Salida - 456.92 - - // Llamado como Number - echo Number::precision(456.91873645, 2); - -Formato de Porcentajes -====================== - -.. php:method:: toPercentage(mixed $value, int $precision = 2, array $options = []) - -+---------------------+----------------------------------------------------+ -| Opción | Descripción | -+=====================+====================================================+ -| multiply | Booleano para indicar si el valor debe ser | -| | multiplicado por 100. Útil para porcentajes | -| | decimales. | -+---------------------+----------------------------------------------------+ - -Al igual que :php:meth:`Cake\\I18n\\Number::precision()`, este método formatea un número -según la precisión proporcionada (donde los números se redondean para cumplir con la -precisión dada). Adicionalmente, también expresa el número como un porcentaje -y agrega un signo de porcentaje a la salida. :: - - // Llamado como NumberHelper. Salida: 45.69% - echo $this->Number->toPercentage(45.691873645); - - // Llamado como Number. Salida: 45.69% - echo Number::toPercentage(45.691873645); - - // Llamado con multiplicar. Salida: 45.7% - echo Number::toPercentage(0.45691, 1, [ - 'multiply' => true - ]); - -Interactuar con Valores Legibles para Humanos -============================================= - -.. php:method:: toReadableSize(string $size) - -Este método formatea tamaños de datos en formas legibles para humanos. Proporciona -una forma abreviada de convertir bytes a KB, MB, GB y TB. El tamaño se -muestra con un nivel de precisión de dos dígitos, de acuerdo con el tamaño -de los datos suministrados (es decir, los tamaños más altos se expresan en términos más grandes):: - - // Llamado como NumberHelper - echo $this->Number->toReadableSize(0); // 0 Byte - echo $this->Number->toReadableSize(1024); // 1 KB - echo $this->Number->toReadableSize(1321205.76); // 1.26 MB - echo $this->Number->toReadableSize(5368709120); // 5 GB - - // Llamado como Number - echo Number::toReadableSize(0); // 0 Byte - echo Number::toReadableSize(1024); // 1 KB - echo Number::toReadableSize(1321205.76); // 1.26 MB - echo Number::toReadableSize(5368709120); // 5 GB - -Formato de Números -================== - -.. php:method:: format(mixed $value, array $options = []) - -Este método te brinda mucho más control sobre el formato de -números para usar en tus vistas (y se utiliza como el método principal por -la mayoría de los otros métodos de NumberHelper). Usar este método puede -verse así:: - - // Llamado como NumberHelper - $this->Number->format($value, $options); - - // Llamado como Number - Number::format($value, $options); - -El parámetro ``$value`` es el número que estás planeando -formatear para la salida. Sin opciones proporcionadas, el número -1236.334 se mostraría como 1,236. Ten en cuenta que la precisión predeterminada es -cero decimales. - -El parámetro ``$options`` es donde reside la verdadera magia para este método. - -- Si pasas un entero, este se convierte en la cantidad de precisión - o lugares para la función. -- Si pasas un arreglo asociado, puedes usar las siguientes claves: - -+---------------------+----------------------------------------------------+ -| Opción | Descripción | -+=====================+====================================================+ -| places | Número de lugares decimales a usar, por ejemplo, 2 | -+---------------------+----------------------------------------------------+ -| precision | Número máximo de lugares decimales a usar, por | -| | ejemplo, 2. | -+---------------------+----------------------------------------------------+ -| pattern | Un patrón de número ICU para usar para formatear el| -| | número, por ejemplo, #,###.00. | -+---------------------+----------------------------------------------------+ -| locale | El nombre de la localidad a usar para formatear el | -| | número, por ejemplo, "es_ES". | -+---------------------+----------------------------------------------------+ -| before | Texto para mostrar antes del número formateado. | -+---------------------+----------------------------------------------------+ -| after | Texto para mostrar después del número formateado. | -+---------------------+----------------------------------------------------+ - -Ejemplo:: - - // Llamado como NumberHelper - echo $this->Number->format('123456.7890', [ - 'places' => 2, - 'before' => '¥ ', - 'after' => ' !' - ]); - // Salida '¥ 123,456.79 !' - - echo $this->Number->format('123456.7890', [ - 'locale' => 'fr_FR' - ]); - // Salida '123 456,79 !' - - // Llamado como Number - echo Number::format('123456.7890', [ - 'places' => 2, - 'before' => '¥ ', - 'after' => ' !' - ]); - // Salida '¥ 123,456.79 !' - - echo Number::format('123456.7890', [ - 'locale' => 'fr_FR' - ]); - // Salida '123 456,79 !' - -.. php:method:: ordinal(mixed $value, array $options = []) - -Este método mostrará un número ordinal. - -Ejemplos:: - - echo Number::ordinal(1); - // Salida '1st' - - echo Number::ordinal(2); - // Salida '2nd' - - echo Number::ordinal(2, [ - 'locale' => 'fr_FR' - ]); - // Salida '2e' - - echo Number::ordinal(410); - // Salida '410th' - -Diferencias en el Formato -========================= - -.. php:method:: formatDelta(mixed $value, array $options = []) - -Este método muestra diferencias en el valor como un número con signo:: - - // Llamado como NumberHelper - $this->Number->formatDelta($value, $options); - - // Llamado como Number - Number::formatDelta($value, $options); - -El parámetro ``$value`` es el número que estás planeando -formatear para la salida. Sin opciones proporcionadas, el número -1236.334 se mostraría como 1,236. Ten en cuenta que la precisión predeterminada es -cero decimales. - -El parámetro ``$options`` toma las mismas claves que :php:meth:`Number::format()` en sí: - -+---------------------+----------------------------------------------------+ -| Opción | Descripción | -+=====================+====================================================+ -| places | Número de lugares decimales a usar, por ejemplo, 2 | -+---------------------+----------------------------------------------------+ -| precision | Número máximo de lugares decimales a usar, por | -| | ejemplo, 2. | -+---------------------+----------------------------------------------------+ -| locale | El nombre de la localidad a usar para formatear el | -| | número, por ejemplo, "es_ES". | -+---------------------+----------------------------------------------------+ -| before | Texto para mostrar antes del número formateado. | -+---------------------+----------------------------------------------------+ -| after | Texto para mostrar después del número formateado. | -+---------------------+----------------------------------------------------+ - -Ejemplo:: - - // Llamado como NumberHelper - echo $this->Number->formatDelta('123456.7890', [ - 'places' => 2, - 'before' => '[', - 'after' => ']' - ]); - // Salida '[+123,456.79]' - - // Llamado como Number - echo Number::formatDelta('123456.7890', [ - 'places' => 2, - 'before' => '[', - 'after' => ']' - ]); - // Salida '[+123,456.79]' - -.. end-cakenumber - -Configurar Formateadores -======================== - -.. php:method:: config(string $locale, int $type = NumberFormatter::DECIMAL, array $options = []) - -Este método te permite configurar valores predeterminados del formateador que persisten en llamadas -a varios métodos. - -Ejemplo:: - - Number::config('es_ES', \NumberFormatter::CURRENCY, [ - 'pattern' => '#,##,##0' - ]); - - -.. meta:: - :title lang=es: Clase Number - :keywords lang=es: number,currency,number format,number precision,format file size,format numbers diff --git a/es/core-libraries/plugin.rst b/es/core-libraries/plugin.rst deleted file mode 100644 index 64f10b13d0..0000000000 --- a/es/core-libraries/plugin.rst +++ /dev/null @@ -1,13 +0,0 @@ -Clase Plugin -############ - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta - página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón - **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección - superior para obtener información sobre el tema de esta página. diff --git a/es/core-libraries/registry-objects.rst b/es/core-libraries/registry-objects.rst deleted file mode 100644 index 525e3efdeb..0000000000 --- a/es/core-libraries/registry-objects.rst +++ /dev/null @@ -1,15 +0,0 @@ -Registry Objects -################ - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. - -.. meta:: - :title lang=es: Object Registry - :keywords lang=es: array name,loading components,several different kinds,unified api,loading objects,component names,special key,core components,callbacks,prg,callback,alias,fatal error,collections,memory,priority,priorities diff --git a/es/core-libraries/security.rst b/es/core-libraries/security.rst deleted file mode 100644 index 908fccac5b..0000000000 --- a/es/core-libraries/security.rst +++ /dev/null @@ -1,19 +0,0 @@ -Security -######## - -.. php:namespace:: Cake\Utility - -.. php:class:: Security - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. - -.. meta:: - :title lang=es: Security - :keywords lang=es: security api,secret password,cipher text,php class,class security,text key,security library,object instance,security measures,basic security,security level,string type,fallback,hash,data security,singleton,inactivity,php encrypt,implementation,php security diff --git a/es/core-libraries/text.rst b/es/core-libraries/text.rst deleted file mode 100644 index 1ac31f81cd..0000000000 --- a/es/core-libraries/text.rst +++ /dev/null @@ -1,21 +0,0 @@ -Text -#### - -.. php:namespace:: Cake\Utility - -.. php:class:: Text - -.. php:staticmethod:: uuid() - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. - -.. meta:: - :title lang=es: Text - :keywords lang=es: array php,array name,string options,data options,result string,class string,string data,string class,placeholders,default method,key value,markup,rfc,replacements,convenience,templates diff --git a/es/core-libraries/time.rst b/es/core-libraries/time.rst deleted file mode 100644 index 75cc083dd7..0000000000 --- a/es/core-libraries/time.rst +++ /dev/null @@ -1,20 +0,0 @@ -Time -#### - -.. php:namespace:: Cake\Utility - -.. php:class:: Time - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. - -.. meta:: - :title lang=es: Time - :description lang=es: Time class helps you format time and test time. - :keywords lang=es: time,format time,timezone,unix epoch,time strings,time zone offset,utc,gmt diff --git a/es/core-libraries/validation.rst b/es/core-libraries/validation.rst deleted file mode 100644 index 30e79e257e..0000000000 --- a/es/core-libraries/validation.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. php:namespace:: Cake\Validation - -Validation -########## - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. diff --git a/es/core-libraries/xml.rst b/es/core-libraries/xml.rst deleted file mode 100644 index 5e7bc351df..0000000000 --- a/es/core-libraries/xml.rst +++ /dev/null @@ -1,19 +0,0 @@ -Xml -### - -.. php:namespace:: Cake\Utility - -.. php:class:: Xml - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. - -.. meta:: - :title lang=es: Xml - :keywords lang=es: array php,xml class,xml objects,post xml,xml object,string url,string data,xml parser,php 5,bakery,constructor,php xml,cakephp,php file,unicorns,meth diff --git a/es/debug-kit.rst b/es/debug-kit.rst deleted file mode 100644 index 7f0d272e43..0000000000 --- a/es/debug-kit.rst +++ /dev/null @@ -1,11 +0,0 @@ -Debug Kit -######### - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. diff --git a/es/deployment.rst b/es/deployment.rst deleted file mode 100644 index 1421dfa20f..0000000000 --- a/es/deployment.rst +++ /dev/null @@ -1,140 +0,0 @@ -Despliegue -########## - -Una vez que tu aplicación esté lista para ser desplegada, hay algunas cosas que debes hacer. - -Mover archivos -============== - -Puedes clonar tu repositorio en tu servidor de producción y luego seleccionar la -revisión/etiqueta que deseas ejecutar. Luego, ejecuta ``composer install``. Aunque esto requiere -un cierto conocimiento sobre git y una instalación existente de ``git`` y ``composer``, -este proceso se encargará de las dependencias de las bibliotecas y los permisos de archivos y carpetas. - -Ten en cuenta que al desplegar a través de FTP deberás corregir los permisos de archivo y -carpeta. - -También puedes utilizar esta técnica de despliegue para configurar un servidor de pruebas o demostración -(preproducción) y mantenerlo sincronizado con tu entorno local. - -Ajustar la configuración -======================== - -Querrás hacer algunos ajustes en la configuración de tu aplicación para -un entorno de producción. El valor de ``debug`` es extremadamente importante. -Al desactivar debug = ``false`` se deshabilitan una serie de características de desarrollo que no deberían -ser expuestas a Internet en general. Deshabilitar debug cambia las siguientes -características: - -* Los mensajes de depuración, creados con :php:func:`pr()`, :php:func:`debug()` y :php:func:`dd()`, - están deshabilitados. -* La duración de las cachés básicas de CakePHP se establece en 365 días, en lugar de 10 segundos, - como en desarrollo. -* Las vistas de errores son menos informativas y se muestran páginas de error genéricas - en lugar de mensajes de error detallados con trazas de pila. -* Los avisos y errores de PHP no se muestran. - -Además de lo anterior, muchos complementos y extensiones de la aplicación usan ``debug`` -para modificar su comportamiento. - -Puedes utilizar una variable de entorno para establecer dinámicamente el nivel de depuración -entre entornos. Esto evitará desplegar una aplicación con debug -``true`` y también te ahorrará tener que cambiar el nivel de depuración cada vez -antes de desplegar en un entorno de producción. - -Por ejemplo, puedes establecer una variable de entorno en tu configuración de Apache:: - - SetEnv CAKEPHP_DEBUG 1 - -Y luego puedes establecer dinámicamente el nivel de depuración en **app_local.php**:: - - $debug = (bool)getenv('CAKEPHP_DEBUG'); - - return [ - 'debug' => $debug, - ..... - ]; - -Se recomienda que coloques la configuración que se comparte en todas -los entornos de tu aplicación en **config/app.php**. Para la configuración que -varía entre entornos, utiliza **config/app_local.php** o variables de entorno. - -Verificar tu Seguridad -====================== - -Si estás lanzando tu aplicación al mundo, es una buena idea asegurarte de que no tenga ningun problema de seguridad obvio: - -* Asegúrate de estar usando el componente o middleware :ref:`csrf-middleware`. -* Puedes habilitar el componente :doc:`/controllers/components/form-protection`. - Puede ayudar a prevenir varios tipos de manipulación de formularios y reducir la posibilidad - de problemas de asignación masiva. -* Asegúrate de que tus modelos tengan las reglas de :doc:`/core-libraries/validation` correctas - habilitadas. -* Verifica que solo tu directorio ``webroot`` sea públicamente visible y que tus - secretos (como tu sal de aplicación y cualquier clave de seguridad) sean privados y únicos - también. - -Establecer la Raíz (Document Root) -================================== - -Establecer correctamente la raíz en tu aplicación es un paso importante para -mantener tanto tu código como tu aplicación seguros. Las aplicaciones de CakePHP -deben tener la raíz establecida en el ``webroot`` de la aplicación. Esto -hace que los archivos de aplicación y configuración sean inaccesibles a través de una URL. -Establecer la raíz es diferente para diferentes servidores web. Consulta la -documentación de :ref:`url-rewriting` para obtener información específica del servidor web. - -En todos los casos, querrás establecer la raiz del host virtual/dominio en -``webroot/``. Esto elimina la posibilidad de que se ejecuten archivos fuera del directorio raíz. - -.. _symlink-assets: - -Mejora el Rendimiento de tu Aplicación -======================================= - -La carga de clases puede llevarse una gran parte del tiempo de procesamiento de tu aplicación. -Para evitar este problema, se recomienda que ejecutes este comando en tu servidor de producción una vez que la aplicación esté implementada:: - - php composer.phar dumpautoload -o - -Dado que manejar los archivos estáticos, como imágenes, archivos JavaScript y CSS de -los complementos, a través del ``Dispatcher`` es increíblemente ineficiente, se recomienda encarecidamente crear enlaces simbólicos para producción. Esto se puede hacer usando -el comando ``plugin``:: - - bin/cake plugin assets symlink - -El comando anterior creará enlaces simbólicos del directorio ``webroot`` de todos los complementos cargados -a la ruta adecuada en el directorio ``webroot`` de la aplicación. - -Si tu sistema de archivos no permite crear enlaces simbólicos, los directorios se copiarán en lugar de enlazarse. También puedes copiar explícitamente los directorios usando:: - - bin/cake plugin assets copy - -CakePHP utiliza internamente ``assert()`` para proporcionar comprobación de tipos en tiempo de ejecución y -proporcionar mejores mensajes de error durante el desarrollo. Puedes hacer que PHP omita estas -comprobaciones ``assert()`` actualizando tu ``php.ini`` para incluir: - -.. code-block:: ini - - ; Desactivar la generación de código assert(). - zend.assertions = -1 - -Omitir la generación de código para ``assert()`` proporcionará un rendimiento de ejecución más rápido, -y se recomienda para aplicaciones que tienen una buena cobertura de pruebas o que están -usando un analizador estático. - -Desplegar una actualización -============================ - -En cada implementación es probable que tengas algunas tareas para coordinar en tu servidor web. Algunas tareas típicas son: - -1. Instalar dependencias con ``composer install``. Evita usar ``composer - update`` al hacer implementaciones, ya que podrías obtener versiones inesperadas de paquetes. -2. Ejecutar `migraciones de base de datos `__ con el complemento Migrations - u otra herramienta. -3. Limpiar la caché del esquema del modelo con ``bin/cake schema_cache clear``. La página :doc:`/console-commands/schema-cache` - tiene más información sobre este comando. - -.. meta:: - :title lang=es: Despliegue - :keywords lang=en: stack traces,application extensions,set document,installation documentation,development features,generic error,document root,func,debug,caches,error messages,configuration files,webroot,deployment,cakephp,applications diff --git a/es/development/application.rst b/es/development/application.rst deleted file mode 100644 index 17dfbd0ad3..0000000000 --- a/es/development/application.rst +++ /dev/null @@ -1,13 +0,0 @@ -Clase Application -################# - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta - página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón - **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección - superior para obtener información sobre el tema de esta página. diff --git a/es/development/configuration.rst b/es/development/configuration.rst deleted file mode 100644 index a607285155..0000000000 --- a/es/development/configuration.rst +++ /dev/null @@ -1,504 +0,0 @@ -Configuración -############# - -Aunque las convenciones eliminan la necesidad de configurar todas las partes de CakePHP, -todavía necesitarás configurar algunas cosas, como las credenciales de tu base de datos. - -Además, existen opciones de configuración opcionales que te permiten cambiar los valores -y las implementaciones predeterminadas por otros personalizados para tu aplicación. - -.. index:: app.php, app_local.example.php - -.. index:: configuration - -Configurando tu Aplicación -============================ - -La configuración generalmente se almacena en archivos PHP o INI, y se carga durante el inicio -de la aplicación. CakePHP viene con un archivo de configuración por defecto, pero si es necesario, -puedes agregar archivos de configuración adicionales y cargarlos en el código de inicio de tu -aplicación. La clase :php:class:`Cake\\Core\\Configure` se utiliza para la configuración global, -y clases como ``Cache`` proporcionan métodos como ``setConfig()`` para hacer que la configuración -sea simple y transparente. - -El esqueleto de la aplicación incluye un archivo **config/app.php** que debería contener configuraciones -que no varían en los diversos entornos en los que se despliega tu aplicación. El archivo **config/app_local.php** -debería contener datos de configuración que varían entre los entornos y deben ser gestionados por -herramientas de gestión de configuración o tus herramientas de implementación. Ambos archivos hacen -referencia a variables de entorno a través de la función ``env()`` que permite establecer valores -de configuración a través del entorno del servidor. - -Cargar Archivos de Configuración Adicionales ---------------------------------------------- - -Si tu aplicación tiene muchas opciones de configuración, puede ser útil dividir la configuración -en varios archivos. Después de crear cada uno de los archivos en tu directorio **config/**, puedes -cargarlos en **bootstrap.php**:: - - use Cake\Core\Configure; - use Cake\Core\Configure\Engine\PhpConfig; - - Configure::setConfig('default', new PhpConfig()); - Configure::load('app', 'default', false); - Configure::load('other_config', 'default'); - -.. _environment-variables: - -Variables de Entorno -===================== - -Muchos proveedores de servicios en la nube modernos, como Heroku, te permiten definir variables de -entorno para datos de configuración. Puedes configurar tu aplicación CakePHP a través de variables -de entorno en el estilo de aplicación `12factor `. Las variables de entorno -permiten que tu aplicación sea fácil de gestionar cuando se implementa en varios entornos. - -Como puedes ver en tu archivo **app.php**, la función ``env()`` se utiliza para leer la configuración -del entorno y construir la configuración de la aplicación. CakePHP utiliza cadenas de conexión :term:`DSN` -para bases de datos, registros, transportes de correo electrónico y configuraciones de caché, lo que -te permite variar fácilmente estas bibliotecas en cada entorno. - -Para el desarrollo local, CakePHP utiliza `dotenv `_ para -recargar automáticamente las variables de entorno locales. Utiliza Composer para requerir esta biblioteca -y luego hay un bloque de código en ``bootstrap.php`` que debe descomentarse para aprovecharla. - -Encontrarás un archivo ``config/.env.example`` en tu aplicación. Al copiar este archivo en ``config/.env`` -y personalizar los valores, puedes configurar tu aplicación. - -Debes evitar incluir el archivo ``config/.env`` en tu repositorio y, en su lugar, utilizar -``config/.env.example`` como una plantilla con valores predeterminados para que todos -en tu equipo sepan qué variables de entorno se están utilizando y qué debe ir en cada una. - -Una vez que se hayan establecido tus variables de entorno, puedes usar ``env()`` para leer datos del entorno:: - - $debug = env('APP_DEBUG', false); - -El segundo valor pasado a la función env es el valor predeterminado. Este valor se utilizará si no existe una -variable de entorno para la clave dada. - -.. _general-configuration: - -Configuración General ----------------------- - -A continuación se muestra una descripción de las variables y cómo afectan a tu aplicación CakePHP. - -- **debug** - Cambia la salida de depuración de CakePHP. ``false`` = Modo de producción. No se muestran mensajes de error o advertencias. ``true`` = Se muestran errores y advertencias. - -- **App.namespace** - El espacio de nombres para encontrar las clases de la aplicación. - - .. note:: - - Al cambiar el espacio de nombres en tu configuración, también deberás actualizar tu archivo **composer.json** para usar este espacio de nombres. Además, crea un nuevo autoloader ejecutando ``php composer.phar dumpautoload``. - -- **App.baseUrl** - Descomenta esta definición si **no** planeas usar mod_rewrite de Apache con CakePHP. No olvides eliminar tus archivos .htaccess también. - -- **App.base** - El directorio base en el que reside la aplicación. Si es ``false``, se detectará automáticamente. Si no es ``false``, asegúrate de que tu cadena comience con un `/` y **NO** termine con un `/`. Por ejemplo, `/basedir` es un valor válido para App.base. - -- **App.encoding** - Define qué codificación utiliza tu aplicación. Esta codificación se utiliza para definir la codificación en las vistas y codificar entidades. Debería coincidir con los valores de codificación especificados para tu base de datos. - -- **App.webroot** - El directorio webroot. - -- **App.wwwRoot** - La ruta de archivo al directorio webroot. - -- **App.fullBaseUrl** - El nombre de dominio completamente cualificado (incluyendo el protocolo) hasta la raíz de tu aplicación. Se utiliza al generar URLs absolutas. Por defecto, este valor se genera utilizando la variable ``$_SERVER`` del entorno. Sin embargo, debes definirlo manualmente para optimizar el rendimiento o si te preocupa que las personas manipulen el encabezado "Host". En un contexto CLI (desde la línea de comandos), el `fullBaseUrl` no se puede leer de $_SERVER, ya que no hay un servidor web involucrado. Debes especificarlo tú mismo si necesitas generar URLs desde una terminal (por ejemplo, al enviar correos electrónicos). - -- **App.imageBaseUrl** - Ruta web al directorio público de imágenes dentro del webroot. Si estás utilizando un :term:`CDN`, debes configurar este valor con la ubicación del CDN. - -- **App.cssBaseUrl** - Ruta web al directorio público de CSS dentro del webroot. Si estás utilizando un :term:`CDN`, debes configurar este valor con la ubicación del CDN. - -- **App.jsBaseUrl** - Ruta web al directorio público de JavaScript dentro del webroot. Si estás utilizando un :term:`CDN`, debes configurar este valor con la ubicación del CDN. - -- **App.paths** - Configura rutas para recursos que no son de clase. Admite las subclaves ``plugins``, ``templates``, ``locales``, que permiten la definición de rutas para los archivos de plugins, plantillas de vista y archivos de traducción, respectivamente. - -- **App.uploadedFilesAsObjects** - Define si los archivos cargados se representan como objetos (``true``) o como arrays (``false``). Esta opción está habilitada de forma predeterminada. Consulta la sección :ref:`File Uploads ` en el capítulo de Objetos de Request & Response para obtener más información. - -- **Security.salt** - Una cadena aleatoria utilizada en el cifrado. Esta cadena también se utiliza como la sal de HMAC al hacer cifrado simétrico. - -- **Asset.timestamp** - Añade una marca de tiempo, que es la última vez que se modificó el archivo en particular, al final de las URLs de los archivos de activos (CSS, JavaScript, Imagen) cuando se utilizan los ayudantes adecuados. Valores válidos: - - - (bool) ``false`` - No hace nada (predeterminado) - - (bool) ``true`` - Añade la marca de tiempo cuando el modo de depuración es ``true`` - - (string) 'force' - Siempre añade la marca de tiempo. - -- **Asset.cacheTime** - Establece el tiempo de caché del archivo de activo. Esto determina el encabezado ``Cache-Control``, ``max-age`` y el tiempo de ``Expire`` del encabezado de HTTP para los activos. Esto puede tomar cualquier valor que la función `strtotime `_ tu versión de PHP pueda tomar. El valor predeterminado es ``+1 día``. - -Usar un CDN ------------ - -Para utilizar un CDN para cargar tus activos estáticos, cambia las variables ``App.imageBaseUrl``, ``App.cssBaseUrl``, ``App.jsBaseUrl`` para que apunten a la URI del CDN, por ejemplo: ``https://micdn.ejemplo.com/`` (nota la barra diagonal al final ``/``). - -Todas las imágenes, scripts y estilos cargados a través de HtmlHelper agregarán la ruta absoluta del CDN, coincidiendo con la misma ruta relativa utilizada en la aplicación. Ten en cuenta que hay un caso de uso específico cuando se utilizan activos basados en plugins: los plugins no utilizarán el prefijo del plugin cuando se utiliza una URI absoluta ``...BaseUrl``, por ejemplo, por defecto: - -* ``$this->Helper->assetUrl('TestPlugin.logo.png')`` resuelve a ``test_plugin/logo.png`` - -Si configuras ``App.imageBaseUrl`` como ``https://micdn.ejemplo.com/``: - -* ``$this->Helper->assetUrl('TestPlugin.logo.png')`` se resuelve a ``https://micdn.ejemplo.com/logo.png``. - -Configuración de la Base de Datos ---------------------------------- - -Consulta la :ref:`Configuración de la Base de Datos ` para obtener información sobre cómo configurar las conexiones a tu base de datos. - -Configuración de Caché ------------------------ - -Consulta la :ref:`Configuración de Caché ` para obtener información sobre cómo configurar la caché en CakePHP. - -Configuración de Manejo de Errores y Excepciones ------------------------------------------------- - -Consulta la :ref:`Configuración de Errores y Excepciones ` para obtener información sobre cómo configurar los manejadores de errores y excepciones. - -Configuración de Registro (Logs) --------------------------------- - -Consulta la :ref:`Configuración de Registro ` para obtener información sobre cómo configurar el registro (logs) en CakePHP. - -Configuración de Correo Electrónico ------------------------------------- - -Consulta la :ref:`Configuración de Correo Electrónico ` para obtener información sobre cómo configurar preajustes de correo electrónico en CakePHP. - -Configuración de Sesión ------------------------- - -Consulta la :ref:`Configuración de Sesión ` para obtener información sobre cómo configurar el manejo de sesiones en CakePHP. - -Configuración de Enrutamiento ------------------------------- - -Consulta la :ref:`Configuración de Rutas ` para obtener más información sobre cómo configurar el enrutamiento y crear rutas para tu aplicación. - -.. _additional-class-paths: - -Rutas de Clases Adicionales -============================ - -Las rutas de clases adicionales se configuran a través de los cargadores automáticos que utiliza tu aplicación. Cuando utilizas `composer` para generar tu cargador automático, puedes hacer lo siguiente para proporcionar rutas alternativas para los controladores en tu aplicación:: - - "autoload": { - "psr-4": { - "App\\Controller\\": "/ruta/a/directorio/con/carpetas/de/controladores/", - "App\\": "src/" - } - } - -El ejemplo anterior establecería rutas para los espacios de nombres `App` y `App\Controller`. Se buscará la primera clave y, si esa ruta no contiene la clase/archivo, se buscará la segunda clave. También puedes asignar un solo espacio de nombres a múltiples directorios de la siguiente manera:: - - "autoload": { - "psr-4": { - "App\\": ["src/", "/ruta/a/directorio/"] - } - } - -Rutas de Plugins, Plantillas de Vista y Localizaciones ------------------------------------------------------------ - -Dado que los plugins, las plantillas de vista y las localizaciones no son clases, no pueden tener un cargador automático configurado. CakePHP proporciona tres variables de configuración para establecer rutas adicionales para estos recursos. En tu **config/app.php**, puedes configurar estas variables:: - - return [ - // Otras configuraciones - 'App' => [ - 'paths' => [ - 'plugins' => [ - ROOT . DS . 'plugins' . DS, - '/ruta/a/otros/plugins/', - ], - 'templates' => [ - ROOT . DS . 'templates' . DS, - ROOT . DS . 'templates2' . DS, - ], - 'locales' => [ - ROOT . DS . 'resources' . DS . 'locales' . DS, - ], - ], - ], - ] - -Las rutas deben terminar con un separador de directorio, o no funcionarán correctamente. - -Configuración de Inflexión -============================== - -Consulta la documentación de :ref:`inflection-configuration` para obtener más información. - -Clase Configure -=================== - -.. php:namespace:: Cake\Core - -.. php:class:: Configure - -La clase Configure de CakePHP se puede utilizar para almacenar y recuperar valores específicos de la aplicación o en -tiempo de ejecución. Sin embargo, debes tener cuidado, ya que esta clase te permite almacenar cualquier cosa y usarla -en cualquier parte de tu código, lo que puede ser una tentación para romper el patrón MVC para el que CakePHP fue diseñado. -El principal objetivo de la clase Configure es mantener variables centralizadas que puedan compartirse entre varios objetos. -Recuerda intentar seguir el principio "convención sobre configuración" para no terminar rompiendo la estructura MVC que -CakePHP proporciona. - -Escritura de Datos de Configuración ------------------------------------ - -.. php:staticmethod:: write($clave, $valor) - -Utiliza ``write()`` para almacenar datos en la configuración de la aplicación:: - - Configure::write('Company.name', 'Pizza, Inc.'); - Configure::write('Company.slogan', 'Pizza for your body and soul'); - -.. note:: - - La :term:`notación de punto` utilizada en el parámetro ``$clave`` se puede utilizar para organizar tus configuraciones en grupos lógicos. - -El ejemplo anterior también se podría escribir en una sola llamada:: - - Configure::write('Company', [ - 'name' => 'Pizza, Inc.', - 'slogan' => 'Pizza for your body and soul' - ]); - -Puedes utilizar ``Configure::write('debug', $boolean)`` para alternar entre los modos de depuración y producción sobre la marcha. - -.. note:: - - Cualquier cambio en la configuración realizado mediante ``Configure::write()`` se mantiene en memoria y no persistirá entre solicitudes. - -Lectura de Datos de Configuración ---------------------------------- - -.. php:staticmethod:: read($clave = null, $predeterminado = null) - -Se utiliza para leer datos de configuración de la aplicación. Si se proporciona una clave, se devolverán los datos. Usando nuestros ejemplos anteriores de ``write()``, podemos leer esos datos de la siguiente manera:: - - # Devuelve 'Pizza, Inc.' - Configure::read('Company.name'); - - # Devuelve 'Pizza for your body and soul' - Configure::read('Company.slogan'); - - Configure::read('Company'); - # Devuelve: - ['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul']; - - # Devuelve 'fallback' ya que Company.nope no está definido. - Configure::read('Company.nope', 'fallback'); - -Si se deja el parámetro ``$clave`` como nulo, se devolverán todos los valores en Configure. - -.. php:staticmethod:: readOrFail($clave) - -Lee datos de configuración igual que :meth:`Cake\\Core\\Configure::read`, pero espera encontrar un par clave/valor. Si el par solicitado no existe, se lanzará una :class:`RuntimeException`. - - Configure::readOrFail('Company.name'); # Devuelve: 'Pizza, Inc.' - Configure::readOrFail('Company.geolocation'); # Lanzará una excepción - - Configure::readOrFail('Company'); - - # Devuelve: - ['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul']; - -Comprobación para ver si los Datos de Configuración están Definidos --------------------------------------------------------------------- - -.. php:staticmethod:: check($clave) - -Se utiliza para comprobar si una clave/ruta existe y tiene un valor distinto de nulo:: - - $existe = Configure::check('Company.name'); - -Eliminación de Datos de Configuración -------------------------------------- - -.. php:staticmethod:: delete($clave) - -Se utiliza para eliminar información de la configuración de la aplicación:: - - Configure::delete('Company.name'); - -Lectura y Eliminación de Datos de Configuración ------------------------------------------------- - -.. php:staticmethod:: consume($clave) - -Lee y elimina una clave de Configure. Esto es útil cuando deseas combinar la lectura y eliminación de valores en una sola operación. - -.. php:staticmethod:: consumeOrFail($clave) - -Consume datos de configuración de la misma manera que :meth:`Cake\\Core\\Configure::consume`, pero espera encontrar un par clave/valor. Si el par solicitado no existe, se lanzará una :class:`RuntimeException`. - - Configure::consumeOrFail('Company.name'); # Devuelve: 'Pizza, Inc.' - Configure::consumeOrFail('Company.geolocation'); # Lanzará una excepción - - Configure::consumeOrFail('Company'); - - # Devuelve: - ['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul'] - -Lectura y Escritura de Archivos de Configuración ------------------------------------------------- - -.. php:staticmethod:: setConfig($nombre, $motor) - -CakePHP viene con dos motores de archivos de configuración integrados. -:php:class:`Cake\\Core\\Configure\\Engine\\PhpConfig` es capaz de leer archivos -de configuración PHP, en el mismo formato que Configure ha leído históricamente. -:php:class:`Cake\\Core\\Configure\\Engine\\IniConfig` es capaz de leer archivos -de configuración ini. Consulta la `documentación de PHP `_ para -obtener más información sobre los detalles de los archivos ini. Para utilizar un -motor de configuración central, debes adjuntarlo a Configure utilizando :php::meth:`Configure::config()`:: - - use Cake\Core\Configure\Engine\PhpConfig; - - # Leer archivos de configuración desde config - Configure::config('default', new PhpConfig()); - - # Leer archivos de configuración desde otra ruta. - Configure::config('default', new PhpConfig('/ruta/a/tus/archivos/de/configuración/')); - -Puedes tener varios motores adjuntos a Configure, cada uno leyendo diferentes tipos o fuentes de archivos de configuración. Puedes interactuar con los motores adjuntos usando los métodos definidos en Configure. Para verificar qué alias de motor están adjuntos, puedes usar :meth:`Configure::configured()`:: - - # Obtén el array de alias para los motores adjuntos. - Configure::configured(); - - # Comprueba si un motor específico está adjunto - Configure::configured('default'); - -.. php:staticmethod:: drop($nombre) - -También puedes eliminar motores adjuntos. ``Configure::drop('default')`` eliminaría el alias del motor predeterminado. Cualquier intento futuro de cargar archivos de configuración con ese motor fallaría:: - - Configure::drop('default'); - -Carga de Archivos de Configuración ----------------------------------- - -.. php:staticmethod:: load($clave, $config = 'default', $merge = true) - -Una vez que hayas adjuntado un motor de configuración a Configure, puedes cargar archivos de configuración:: - - # Cargar my_file.php usando el objeto de motor 'default'. - Configure::load('my_file', 'default'); - -Los archivos de configuración cargados fusionan sus datos con la configuración en tiempo de ejecución existente en Configure. Esto te permite sobrescribir y agregar nuevos valores a la configuración en tiempo de ejecución existente. Al establecer ``$merge`` en ``true``, los valores nunca sobrescribirán la configuración existente. - -.. warning:: - Al fusionar archivos de configuración con `$merge = true`, la notación de puntos en las claves no se expande:: - - # config1.php - 'Clave1' => [ - 'Clave2' => [ - 'Clave3' => ['ClaveAnidada1' => 'Valor'], - ], - ], - - # config2.php - 'Clave1.Clave2' => [ - 'Clave3' => ['ClaveAnidada2' => 'Valor2'], - ] - - Configure::load('config1', 'default'); - Configure::load('config2', 'default', true); - - # Ahora Clave1.Clave2.Clave3 tiene el valor ['ClaveAnidada2' => 'Valor2'] - # en lugar de ['ClaveAnidada1' => 'Valor', 'ClaveAnidada2' => 'Valor2'] - -Creación o Modificación de Archivos de Configuración ------------------------------------------------------ - -.. php:staticmethod:: dump($clave, $config = 'default', $claves = []) - -Vuelca todos o algunos de los datos en Configure en un archivo o sistema de almacenamiento compatible con un motor de configuración. El formato de serialización lo decide el motor de configuración adjunto como $config. Por ejemplo, si el motor 'default' es una :class:`Cake\\Core\\Configure\\Engine\\PhpConfig`, el archivo generado será un archivo de configuración PHP que se puede cargar mediante el :class:`Cake\\Core\\Configure\\Engine\\PhpConfig` - -Dado que el motor 'default' es una instancia de PhpConfig. Guarda todos los datos en Configure en el archivo `mi_configuracion.php`:: - - Configure::dump('mi_configuracion', 'default'); - -Guarda solo la configuración de manejo de errores:: - - Configure::dump('error', 'default', ['Error', 'Exception']); - -``Configure::dump()`` se puede utilizar para modificar o sobrescribir archivos de configuración que se pueden leer con :meth:`Configure::load()` - -Almacenamiento de Configuración en Tiempo de Ejecución ------------------------------------------------------- - -.. php:staticmethod:: store($nombre, $configuracionCache = 'default', $datos = null) - -También puedes almacenar valores de configuración en tiempo de ejecución para usarlos en solicitudes futuras. Dado que configure solo recuerda valores para la solicitud actual, deberás almacenar cualquier información de configuración modificada si deseas usarla en solicitudes posteriores:: - - # Almacena la configuración actual en la clave 'usuario_1234' en la caché 'default'. - Configure::store('usuario_1234', 'default'); - -Los datos de configuración almacenados persisten en la configuración de caché con el nombre especificado. Consulta la documentación sobre :doc:`/core-libraries/caching` para obtener más información sobre el almacenamiento en caché. - -Restauración de Configuración en Tiempo de Ejecución ------------------------------------------------------ - -.. php:staticmethod:: restore($nombre, $configuracionCache = 'default') - -Una vez que hayas almacenado la configuración en tiempo de ejecución, probablemente necesitarás restaurarla para poder acceder a ella nuevamente. ``Configure::restore()`` hace precisamente eso:: - - # Restaura la configuración en tiempo de ejecución desde la caché. - Configure::restore('usuario_1234', 'default'); - -Al restaurar información de configuración, es importante restaurarla con la misma clave y configuración de caché que se usó para almacenarla. La información restaurada se fusiona con la configuración en tiempo de ejecución existente. - -Motores de Configuración ------------------------- - -CakePHP proporciona la capacidad de cargar archivos de configuración desde varias fuentes diferentes y cuenta con un sistema plugable para `crear tus propios motores de configuración -`__. Los motores de configuración integrados son: - -* `JsonConfig `__ -* `IniConfig `__ -* `PhpConfig `__ - -Por defecto, tu aplicación utilizará ``PhpConfig``. - -Desactivación de Tablas Genéricas -================================== - -Aunque utilizar clases de tabla genéricas, también llamadas auto-tablas, al crear rápidamente nuevas aplicaciones y hornear -modelos es útil, las clases de tabla genéricas pueden dificultar la depuración en algunos escenarios. - -Puedes verificar si se emitió alguna consulta desde una clase de tabla genérica a través del panel SQL de DebugKit. Si -aún tienes problemas para diagnosticar un problema que podría ser causado por las auto-tablas, puedes lanzar una excepción -cuando CakePHP utiliza implícitamente una ``Cake\ORM\Table`` genérica en lugar de tu clase concreta de la siguiente manera:: - - # En tu bootstrap.php - use Cake\Event\EventManager; - use Cake\Http\Exception\InternalErrorException; - - $seEjecutaCakeBakeShell = (PHP_SAPI === 'cli' && isset($argv[1]) && $argv[1] === 'bake'); - if (!$seEjecutaCakeBakeShell) { - EventManager::instance()->on('Model.initialize', function($event) { - $subject = $event->getSubject(); - if (get_class($subject) === 'Cake\ORM\Table') { - $mensaje = sprintf( - 'Clase de tabla faltante o alias incorrecto al registrar la clase de tabla para la tabla de base de datos %s.', - $subject->getTable()); - throw new InternalErrorException($mensaje); - } - }); - } - -.. meta:: - :title lang=es: Configuración - :keywords lang=es: finished configuration,legacy database,database configuration,value pairs,default connection,optional configuration,example database,php class,configuration database,default database,configuration steps,index database,configuration details,class database,host localhost,inflections,key value,database connection,piece of cake,basic web diff --git a/es/development/debugging.rst b/es/development/debugging.rst deleted file mode 100644 index 8314c84dc2..0000000000 --- a/es/development/debugging.rst +++ /dev/null @@ -1,196 +0,0 @@ -Depuración -########## - -La depuración es una parte inevitable y necesaria de cualquier ciclo de desarrollo. -Aunque CakePHP no ofrece ninguna herramienta que se conecte directamente -con algún IDE o editor, CakePHP proporciona varias herramientas para -asistirte en la depuración y exponer lo que se está ejecutando bajo el capó de -tu aplicación. - -Depuración Básica -================= - -.. php:function:: debug(mixed $var, boolean $showHtml = null, $showFrom = true) - :noindex: - -La función ``debug()`` es una función que está disponible globalmente y funciona -de manera similar a la función ``print_r()`` de PHP. La función ``debug()`` -te permite mostrar el contenido de una variable de varias maneras. -Primero, si deseas que los datos se muestren de una forma amigable con HTML, -debes establecer el segundo parámetro en ``true``. La función -también imprime la línea y el archivo de origen por defecto. - -El resultado de esta función solo se mostrará si la variable ``$debug`` en el archivo core es ``true``. - -Ver también ``dd()``, ``pr()`` y ``pj()``. - -.. php:function:: stackTrace() - -La función ``stackTrace()`` está disponible globalmente, esta permite mostrar -el seguimiento de pila donde sea que se llame. - -.. php:function:: breakpoint() - -Si tienes `Psysh ` _ instalado, puedes usar esta -función en entornos CLI para abrir una consola interactiva con el -ámbito local actual:: - - // Algún código - eval(breakpoint()); - -Abrirá una consola interactiva que puede ser usada para revisar variables locales -y ejecutar otro código. Puedes salir del depurador interactivo y reanudar la -ejecución original corriendo ``quit`` o ``q`` en la sesion interactiva. - - -Usando La Clase Debugger -======================== - -.. php:namespace:: Cake\Error - -.. php:class:: Debugger - -Para usar el depurador, primero asegúrate de que ``Configure::read('debug')`` sea ``true``. - -Imprimiendo Valores -=================== - -.. php:staticmethod:: dump($var, $depth = 3) - -Dump imprime el contenido de una variable. Imprimirá todas las -propiedades y métodos (si existen) de la variable que se le pase:: - - $foo = [1,2,3]; - - Debugger::dump($foo); - - // Salida - array( - 1, - 2, - 3 - ) - - // Objeto simple - $car = new Car(); - - Debugger::dump($car); - - // Salida - object(Car) { - color => 'red' - make => 'Toyota' - model => 'Camry' - mileage => (int)15000 - } - -Enmascarando Datos ------------------- - -Al volcar datos con ``Debugger`` o mostrar páginas de error, es posible que desees -ocultar claves sensibles como contraseñas o claves API. En tu ``config/bootstrap.php`` -puedes enmascarar claves específicas:: - - Debugger::setOutputMask([ - 'password' => 'xxxxx', - 'awsKey' => 'yyyyy', - ]); - -Registros Con Trazas De Pila -============================ - -.. php:staticmethod:: log($var, $level = 7, $depth = 3) - -Crea un registro de seguimiento de pila detallado al momento de la invocación. El -método ``log()`` imprime datos similar a como lo hace ``Debugger::dump()``, -pero al debug.log en vez de al buffer de salida. Ten en cuenta que tu directorio -**tmp** (y su contenido) debe ser reescribible por el servidor web para que ``log()`` -funcione correctamente. - -Generando seguimientos de pila -============================== - -.. php:staticmethod:: trace($options) - -Devuelve el seguimiento de pila actual. Cada línea de la pila incluye -cuál método llama, incluyendo el archivo y la línea en la que se originó -la llamada:: - - // En PostsController::index() - pr(Debugger::trace()); - - // Salida - PostsController::index() - APP/Controller/DownloadsController.php, line 48 - Dispatcher::_invoke() - CORE/src/Routing/Dispatcher.php, line 265 - Dispatcher::dispatch() - CORE/src/Routing/Dispatcher.php, line 237 - [main] - APP/webroot/index.php, line 84 - -Arriba está el seguimiento de pila generado al llamar ``Debugger::trace()`` en -una acción de un controlador. Leer el seguimiento de pila desde abajo hacia arriba -muestra el órden de las funciones (cuadros de pila). - -Obtener Un Extracto De Un Archivo -================================= - -.. php:staticmethod:: excerpt($file, $line, $context) - -Saca un extracto de un archivo en $path (el cual es una dirección absoluta), -resalta el número de la línea $line con el número $context de líneas alrededor de este. :: - - pr(Debugger::excerpt(ROOT . DS . LIBS . 'debugger.php', 321, 2)); - - // Mostrará lo siguiente. - Array - ( - [0] => * @access public - [1] => */ - [2] => function excerpt($file, $line, $context = 2) { - - [3] => $data = $lines = array(); - [4] => $data = @explode("\n", file_get_contents($file)); - ) - -Aunque este método es usado internamente, puede ser útil si estás -creando tus propios mensajes de error o entradas de registros para -situaciones customizadas. - -.. php:staticmethod:: Debugger::getType($var) - -Consigue el tipo de una variable. Los objetos devolverán el nombre -de su clase. - -Usando El Registro Para Depurar -=============================== - -Registrar mensajes es otra buena manera de depurar aplicaciones, puedes usar -:php:class:`Cake\\Log\\Log` para hacer registros en tu aplicación. Todos los -objetos que usen ``LogTrait`` tienen una instancia del método ``log()`` que -puede ser usado para registrar mensajes:: - - $this->log('Llegó aquí', 'debug'); - -Lo anterior escribiría ``Llegó aquí`` en el registro de depuración. Puedes usar -entradas de registro para ayudar a los métodos de depuración que involucran redireccionamientos -o búcles complejos. También puedes usar :php:meth:`Cake\\Log\\Log::write()` para -escribir mensajes de registro. Este método puede ser llamado estáticamente en -cualquier lugar de tu aplicación que un Log haya sido cargado:: - - // En el tope del archivo que quieras hacer registros. - use Cake\Log\Log; - - // En cualquier parte que Log haya sido importado. - Log::debug('Llegó aquí'); - -Kit De Depuración -================= - -DebugKit es un complemento que proporciona una serie de buenas herramientas de depuración. -Principalmente, provee una barra de herramientas en el HTML -renderizado, que porporciona una gran cantidad de información sobre tu aplicación -y la solicitud actual. Ver el capítulo :doc:`/debug-kit` para saber cómo instalar -y usar DebugKit. - -.. meta:: - :title lang=es: Depuración - :description lang=es: Depuración CakePHP con la clase Debugger, depurando, depuración básica y usar el plugin DebugKit. - :keywords lang=es: código extracto,seguimiento de pila,salida por defecto,enlace de error,error por defecto,solicitudes web,reporte de error,depurador,arreglos,maneras diferentes,extraer desde,cakephp,ide,opciones diff --git a/es/development/dependency-injection.rst b/es/development/dependency-injection.rst deleted file mode 100644 index dbfb3a740a..0000000000 --- a/es/development/dependency-injection.rst +++ /dev/null @@ -1,13 +0,0 @@ -Inyección de Dependencias -########################## - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta - página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón - **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección - superior para obtener información sobre el tema de esta página. diff --git a/es/development/errors.rst b/es/development/errors.rst deleted file mode 100644 index f581ff38e5..0000000000 --- a/es/development/errors.rst +++ /dev/null @@ -1,641 +0,0 @@ -Manejo de Errores y Excepciones -################################ - -Las aplicaciones de CakePHP vienen con la configuración predeterminada de manejo de errores y excepciones. -Los errores de PHP son capturados y mostrados o registrados. Las excepciones no capturadas se representan -automáticamente en páginas de error. - -.. _error-configuration: - -Configuración -============= - -La configuración de errores se realiza en el archivo **config/app.php** de tu aplicación. Por defecto, CakePHP -utiliza ``Cake\Error\ErrorTrap`` y ``Cake\Error\ExceptionTrap`` para manejar tanto errores de PHP como excepciones, -respectivamente. La configuración de errores te permite personalizar el manejo de errores para tu aplicación. -Las siguientes opciones son compatibles: - -* ``errorLevel`` - int - El nivel de errores que te interesa capturar. Usa las constantes de error de PHP integradas - y las máscaras de bits para seleccionar el nivel de error que te interesa. Consulta :ref:`deprecation-warnings` - para deshabilitar advertencias de obsolescencia. -* ``trace`` - bool - Incluir trazas para errores en los archivos de registro. Las trazas se incluirán en el - registro después de cada error. Esto es útil para encontrar dónde/cuándo se están generando los errores. -* ``exceptionRenderer`` - string - La clase responsable de representar excepciones no capturadas. Si eliges - una clase personalizada, debes colocar el archivo para esa clase en **src/Error**. Esta clase debe implementar - un método ``render()``. -* ``log`` - bool - Cuando es ``true``, las excepciones y sus trazas se registrarán en :php:class:`Cake\\Log\\Log`. -* ``skipLog`` - array - Un array de nombres de clases de excepción que no deben ser registrados. Esto es útil para - eliminar mensajes de registro comunes pero poco interesantes, como NotFoundExceptions. -* ``extraFatalErrorMemory`` - int - Establece el número de megabytes para aumentar el límite de memoria cuando - se encuentra un error fatal. Esto permite espacio para completar el registro o el manejo de errores. -* ``logger`` (antes de la versión 4.4.0, usa ``errorLogger``) - ``Cake\Error\ErrorLoggerInterface`` - La clase - responsable de registrar errores y excepciones no controladas. Por defecto, es ``Cake\Error\ErrorLogger``. -* ``errorRenderer`` - ``Cake\Error\ErrorRendererInterface`` - La clase responsable de representar errores. Se - elige automáticamente en función del SAPI de PHP. -* ``ignoredDeprecationPaths`` - array - Una lista de rutas compatibles con la sintaxis Glob que deben ignorar errores de - obsolescencia. Añadido en la versión 4.2.0 - -Por defecto, los errores de PHP se muestran cuando ``debug`` es ``true``, y se registran cuando debug es ``false``. -El manejador de errores fatal se llamará independientemente del nivel de ``debug`` o la configuración de ``errorLevel``, -pero el resultado será diferente según el nivel de ``debug``. El comportamiento predeterminado para errores fatales es -mostrar una página de error interno del servidor (``debug`` deshabilitado) o una página con el mensaje, archivo y línea (``debug`` habilitado). - -.. note:: - - Si utilizas un manejador de errores personalizado, las opciones compatibles dependerán de tu manejador. - -.. _deprecation-warnings: - -Advertencias de Obsolescencia -============================== - -CakePHP utiliza advertencias de obsolescencia para indicar cuándo se ha marcado como obsoleta alguna característica. También -recomendamos este sistema para su uso en tus plugins y código de aplicación cuando sea útil. Puedes activar advertencias de -obsolescencia con ``deprecationWarning()``:: - - deprecationWarning('5.0', 'El método example() está obsoleto. Usa getExample() en su lugar.'); - -Al actualizar CakePHP o plugins, es posible que te encuentres con nuevas advertencias de obsolescencia. Puedes desactivar -temporalmente las advertencias de obsolescencia de varias formas: - -#. Usar la configuración ``Error.errorLevel`` con ``E_ALL ^ E_USER_DEPRECATED`` para ignorar *todas* las advertencias de - obsolescencia. -#. Usar la opción de configuración ``Error.ignoredDeprecationPaths`` para ignorar advertencias con expresiones compatibles - con la sintaxis Glob. Por ejemplo:: - - 'Error' => [ - 'ignoredDeprecationPaths' => [ - 'vendors/company/contacts/*', - 'src/Models/*', - ], - ], - - Ignoraría todas las advertencias de obsolescencia de tu directorio ``Models`` y el plugin ``Contacts`` en tu aplicación. - -Cambiar el Manejo de Excepciones -================================= - -El manejo de excepciones en CakePHP ofrece varias formas de personalizar cómo se manejan las excepciones. Cada enfoque te brinda -diferentes niveles de control sobre el proceso de manejo de excepciones. - -#. *Escucha eventos* Esto te permite recibir notificaciones a través de eventos de CakePHP cuando se han manejado errores y excepciones. -#. *Plantillas personalizadas* Esto te permite cambiar las plantillas de vista renderizadas como lo harías con cualquier otra - plantilla en tu aplicación. -#. *Controlador personalizado* Esto te permite controlar cómo se renderizan las páginas de excepción. -#. *ExceptionRenderer personalizado* Esto te permite controlar cómo se realizan las páginas de excepción y el registro. -#. *Crea y registra tus propios manejadores* Esto te brinda control total sobre cómo se manejan, registran y representan los errores y - excepciones. Utiliza ``Cake\Error\ExceptionTrap`` y ``Cake\Error\ErrorTrap`` como referencia cuando implementes tus manejadores. - -Escuchar Eventos -================ - -Los manejadores ``ErrorTrap`` y ``ExceptionTrap`` activarán eventos de CakePHP cuando manejan errores. Puedes escuchar el evento ``Error.beforeRender`` para ser notificado de los errores de PHP. El evento ``Exception.beforeRender`` se desencadena cuando se maneja una excepción:: - - $errorTrap = new ErrorTrap(Configure::read('Error')); - $errorTrap->getEventManager()->on( - 'Error.beforeRender', - function (EventInterface $event, PhpError $error) { - // haz lo que necesites - } - ); - -Dentro de un manejador ``Error.beforeRender``, tienes algunas opciones: - -* Detener el evento para evitar la representación. -* Devolver una cadena para omitir la representación y usar la cadena proporcionada en su lugar. - -Dentro de un manejador ``Exception.beforeRender``, también tienes algunas opciones: - -* Detener el evento para evitar la representación. -* Establecer el atributo de datos ``exception`` con ``setData('exception', $err)`` - para reemplazar la excepción que se está representando. -* Devolver una respuesta desde el evento para omitir la representación y usar - la respuesta proporcionada en su lugar. - -.. _error-views: - -Plantillas Personalizadas -========================== - -El atrapador de excepciones predeterminado representa todas las excepciones no capturadas que tu aplicación genera con la ayuda de ``Cake\Error\WebExceptionRenderer`` y tu ``ErrorController`` de la aplicación. - -Las vistas de página de error están ubicadas en **templates/Error/**. Todos los errores 4xx usan la plantilla **error400.php**, y los errores 5xx usan la plantilla **error500.php**. Tus plantillas de error tendrán las siguientes variables disponibles: - -* ``message`` El mensaje de la excepción. -* ``code`` El código de la excepción. -* ``url`` La URL de la solicitud. -* ``error`` El objeto de la excepción. - -En modo de depuración, si tu error se extiende de ``Cake\Core\Exception\CakeException``, los datos devueltos por ``getAttributes()`` se expondrán también como variables de vista. - -.. note:: - Necesitarás establecer ``debug`` en falso para ver tus plantillas **error404** y **error500**. En modo de depuración, verás la página de error de desarrollo de CakePHP. - -Diseño Personalizado para la Página de Error --------------------------------------------- - -Por defecto, las plantillas de error usan **templates/layout/error.php** para un diseño. Puedes usar la propiedad ``layout`` para elegir un diseño diferente:: - - // dentro de templates/Error/error400.php - $this->layout = 'mi_error'; - -Lo anterior usaría **templates/layout/mi_error.php** como el diseño para tus páginas de error. - -Muchas excepciones generadas por CakePHP representarán plantillas de vista específicas en modo de depuración. Con la depuración desactivada, todas las excepciones generadas por CakePHP usarán **error400.php** o **error500.php** según su código de estado. - -Controlador Personalizado -========================= - -La clase ``App\Controller\ErrorController`` se utiliza para la representación de excepciones de CakePHP para renderizar la vista de la página de error y recibe todos los eventos estándar del ciclo de vida de la solicitud. Al modificar esta clase, puedes controlar qué componentes se utilizan y qué plantillas se representan. - -Si tu aplicación utiliza :ref:`rutas con prefijo `, puedes crear controladores de error personalizados para cada prefijo de enrutamiento. Por ejemplo, si tienes un prefijo ``Admin``, podrías crear la siguiente clase:: - - namespace App\Controller\Admin; - - use App\Controller\AppController; - use Cake\Event\EventInterface; - - class ErrorController extends AppController - { - /** - * Callback beforeRender. - * - * @param \Cake\Event\EventInterface $event Evento. - * @return void - */ - public function beforeRender(EventInterface $event) - { - $this->viewBuilder()->setTemplatePath('Error'); - } - } - -Este controlador solo se utilizaría cuando se encuentra un error en un controlador con prefijo y te permite definir lógica/plantillas específicas del prefijo según sea necesario. - -.. _custom-exceptionrenderer: - -ExceptionRenderer Personalizado -================================ - -Si deseas controlar todo el proceso de representación y registro de excepciones, puedes utilizar la opción ``Error.exceptionRenderer`` en **config/app.php** para elegir una clase que representará las páginas de excepciones. Cambiar el ExceptionRenderer es útil cuando quieres cambiar la lógica utilizada para crear un controlador de error, elegir la plantilla o controlar el proceso general de representación. - -Tu clase personalizada de ExceptionRenderer debe colocarse en **src/Error**. Supongamos que nuestra aplicación usa ``App\Exception\MissingWidgetException`` para indicar un widget faltante. Podríamos crear un ExceptionRenderer que represente páginas de error específicas cuando se maneja este error:: - - // En src/Error/AppExceptionRenderer.php - namespace App\Error; - - use Cake\Error\WebExceptionRenderer; - - class AppExceptionRenderer extends WebExceptionRenderer - { - public function missingWidget($error) - { - $response = $this->controller->getResponse(); - - return $response->withStringBody('Oops, ese widget está perdido.'); - } - } - - // En config/app.php - 'Error' => [ - 'exceptionRenderer' => 'App\Error\AppExceptionRenderer', - // ... - ], - // ... - -Lo anterior manejaría nuestro ``MissingWidgetException``, -y nos permitiría proporcionar lógica de visualización/manejo personalizado para esas excepciones de aplicación. - -Los métodos de representación de excepciones reciben la excepción manejada como argumento y deben devolver un objeto ``Response``. También puedes implementar métodos para agregar lógica adicional al manejar errores de CakePHP:: - - // En src/Error/AppExceptionRenderer.php - namespace App\Error; - - use Cake\Error\WebExceptionRenderer; - - class AppExceptionRenderer extends WebExceptionRenderer - { - public function notFound($error) - { - // Haz algo con objetos NotFoundException. - } - } - -Cambiar la Clase ErrorController ----------------------------------- - -El ExceptionRenderer dicta qué controlador se utiliza para la representación de excepciones. Si quieres cambiar qué controlador se utiliza para representar excepciones, puedes anular el método ``_getController()`` en tu ExceptionRenderer:: - - // en src/Error/AppExceptionRenderer - namespace App\Error; - - use App\Controller\SuperCustomErrorController; - use Cake\Controller\Controller; - use Cake\Error\WebExceptionRenderer; - - class AppExceptionRenderer extends WebExceptionRenderer - { - protected function _getController(): Controller - { - return new SuperCustomErrorController(); - } - } - - // en config/app.php - 'Error' => [ - 'exceptionRenderer' => 'App\Error\AppExceptionRenderer', - // ... - ], - - - // ... - -.. index:: excepciones de aplicación - -Crear tus Propias Excepciones de Aplicación -============================================ - -Puedes crear tus propias excepciones de aplicación utilizando cualquiera de las `excepciones SPL incorporadas `_, ``Exception`` -en sí, o :php:exc:`Cake\\Core\\Exception\\Exception`. -Si tu aplicación contiene la siguiente excepción:: - - use Cake\Core\Exception\CakeException; - - class MissingWidgetException extends CakeException - { - } - -Podrías proporcionar errores de desarrollo detallados, creando -**templates/Error/missing_widget.php**. Cuando estás en modo de producción, el error anterior se trataría como un error 500 y usaría la plantilla **error500**. - -Las excepciones que son subclases de ``Cake\Http\Exception\HttpException``, usarán su código de error como código de estado HTTP si el código de error está entre ``400`` y ``506``. - -El constructor para :php:exc:`Cake\\Core\\Exception\\CakeException` te permite pasar datos adicionales. Estos datos adicionales se interpolan en el ``_messageTemplate``. Esto te permite crear excepciones ricas en datos que proporcionen más contexto sobre tus errores:: - - use Cake\Core\Exception\CakeException; - - class MissingWidgetException extends CakeException - { - // Los datos del contexto se interpolan en esta cadena de formato. - protected $_messageTemplate = 'Parece que falta %s.'; - - // También puedes establecer un código de excepción predeterminado. - protected $_defaultCode = 404; - } - - throw new MissingWidgetException(['widget' => 'Puntiagudo']); - -Cuando se representa, tu plantilla de vista tendría una variable ``$widget`` establecida. Si lanzas la excepción como una cadena o usas su método ``getMessage()``, obtendrás ``Parece que falta Puntiagudo.``. - -.. note:: - - Antes de CakePHP 4.2.0, usa la clase ``Cake\Core\Exception\Exception`` en lugar de ``Cake\Core\Exception\CakeException`` - -Registro de Excepciones ------------------------- - -Usando el manejo de excepciones incorporado, puedes registrar todas las excepciones que son tratadas por ErrorTrap configurando la opción ``log`` en ``true`` en tu **config/app.php**. Al habilitar esto, se registrarán todas las excepciones en :php:class:`Cake\\Log\\Log` y en los registradores configurados. - -.. note:: - - Si estás utilizando un manejador de excepciones personalizado, esta configuración no tendrá - ningún efecto, a menos que la referencies dentro de tu implementación. - -.. php:namespace:: Cake\Http\Exception - -.. _built-in-exceptions: - -Excepciones Incorporadas para CakePHP -====================================== - -Excepciones HTTP ------------------ - -Hay varias excepciones incorporadas en CakePHP, además de las excepciones internas del framework, hay varias -excepciones para métodos HTTP. - -.. php:exception:: BadRequestException - :nocontentsentry: - - Usado para el error 400 Bad Request. - -.. php:exception:: UnauthorizedException - :nocontentsentry: - - Usado para el error 401 Unauthorized. - -.. php:exception:: ForbiddenException - :nocontentsentry: - - Usado para el error 403 Forbidden. - -.. php:exception:: InvalidCsrfTokenException - :nocontentsentry: - - Usado para el error 403 causado por un token CSRF inválido. - -.. php:exception:: NotFoundException - :nocontentsentry: - - Usado para el error 404 Not found. - -.. php:exception:: MethodNotAllowedException - :nocontentsentry: - - Usado para el error 405 Method Not Allowed. - -.. php:exception:: NotAcceptableException - :nocontentsentry: - - Usado para el error 406 Not Acceptable. - -.. php:exception:: ConflictException - :nocontentsentry: - - Usado para el error 409 Conflict. - -.. php:exception:: GoneException - :nocontentsentry: - - Usado para el error 410 Gone. - -Para más detalles sobre los códigos de estado 4xx del protocolo HTTP, consulta :rfc:`2616#section-10.4`. - -.. php:exception:: InternalErrorException - :nocontentsentry: - - Usado para el error 500 Internal Server Error. - -.. php:exception:: NotImplementedException - :nocontentsentry: - - Usado para el error 501 Not Implemented Errors. - -.. php:exception:: ServiceUnavailableException - :nocontentsentry: - - Usado para el error 503 Service Unavailable. - -Para más detalles sobre los códigos de estado 5xx del protocolo HTTP, consulta :rfc:`2616#section-10.5`. - -Puedes lanzar estas excepciones desde tus controladores para indicar estados de error o errores HTTP. Un ejemplo de uso de las excepciones HTTP podría ser renderizar páginas 404 para los elementos que no se han encontrado:: - - use Cake\Http\Exception\NotFoundException; - - public function ver($id = null) - { - $articulo = $this->Articulos->findById($id)->first(); - if (empty($articulo)) { - throw new NotFoundException(__('Artículo no encontrado')); - } - $this->set('articulo', $articulo); - $this->viewBuilder()->setOption('serialize', [' - -articulo']); - } - -Usar excepciones para errores HTTP te permite mantener tu código limpio y dar respuestas RESTful a aplicaciones de clientes y usuarios. - -Uso de Excepciones HTTP en tus Controladores --------------------------------------------- - -Puedes lanzar cualquiera de las excepciones relacionadas con HTTP desde las acciones de tu controlador para indicar estados de error. Por ejemplo:: - - use Cake\Network\Exception\NotFoundException; - - public function ver($id = null) - { - $articulo = $this->Articulos->findById($id)->first(); - if (empty($articulo)) { - throw new NotFoundException(__('Artículo no encontrado')); - } - $this->set('articulo', 'articulo'); - $this->viewBuilder()->setOption('serialize', ['articulo']); - } - -Lo anterior causaría que el manejador de excepciones configurado capture y -procese la :php:exc:`NotFoundException`. Por defecto, esto creará una página de error -y registrará la excepción. - -Otras Excepciones Incorporadas ------------------------------- - -Además, CakePHP utiliza las siguientes excepciones: - -.. php:namespace:: Cake\View\Exception - -.. php:exception:: MissingViewException - :nocontentsentry: - - No se pudo encontrar la clase de vista elegida. - -.. php:exception:: MissingTemplateException - :nocontentsentry: - - No se pudo encontrar el archivo de plantilla elegido. - -.. php:exception:: MissingLayoutException - :nocontentsentry: - - No se pudo encontrar el diseño elegido. - -.. php:exception:: MissingHelperException - :nocontentsentry: - - No se pudo encontrar el ayudante elegido. - -.. php:exception:: MissingElementException - :nocontentsentry: - - No se pudo encontrar el archivo de elemento elegido. - -.. php:exception:: MissingCellException - :nocontentsentry: - - No se pudo encontrar la clase de celda elegida. - -.. php:exception:: MissingCellViewException - :nocontentsentry: - - No se pudo encontrar el archivo de vista de celda elegido. - -.. php:namespace:: Cake\Controller\Exception - -.. php:exception:: MissingComponentException - :nocontentsentry: - - No se pudo encontrar el componente configurado. - -.. php:exception:: MissingActionException - :nocontentsentry: - - No se pudo encontrar la acción del controlador solicitada. - -.. php:exception:: PrivateActionException - :nocontentsentry: - - Acceder a acciones con prefijos privados/protegidos/_. - -.. php:namespace:: Cake\Console\Exception - -.. php:exception:: ConsoleException - :nocontentsentry: - - Una clase de biblioteca de consola encontró un error. - -.. php:namespace:: Cake\Database\Exception - -.. php:exception:: MissingConnectionException - :nocontentsentry: - - Falta una conexión de modelo. - -.. php:exception:: MissingDriverException - :nocontentsentry: - - No se pudo encontrar un controlador de base de datos. - -.. php:exception:: MissingExtensionException - :nocontentsentry: - - Falta una extensión de PHP para el controlador de base de datos. - -.. php:namespace:: Cake\ORM\Exception - -.. php:exception:: MissingTableException - :nocontentsentry: - - No se pudo encontrar la tabla de un modelo. - -.. php:exception:: MissingEntityException - :nocontentsentry: - - No se pudo encontrar la entidad de un modelo. - -.. php:exception:: MissingBehaviorException - :nocontentsentry: - - No se pudo encontrar el comportamiento de un modelo. - -.. php:exception:: PersistenceFailedException - :nocontentsentry: - - No se pudo guardar/eliminar una entidad al usar :php:meth:`Cake\\ORM\\Table::saveOrFail()` o - :php:meth:`Cake\\ORM\\Table::deleteOrFail()`. - -.. php:namespace:: Cake\Datasource\Exception - -.. php:exception:: RecordNotFoundException - :nocontentsentry: - - No se pudo encontrar el registro solicitado. Esto también establecerá las cabeceras de respuesta HTTP en 404. - -.. php:namespace:: Cake\Routing\Exception - -.. php:exception:: MissingControllerException - :nocontentsentry: - - No se pudo encontrar el controlador solicitado. - -.. php:exception:: MissingRouteException - :nocontentsentry: - - No se pudo hacer coincidir la URL solicitada o no se pudo analizar. - -.. php:namespace:: Cake\Core\Exception - -.. php:exception:: Exception - :nocontentsentry: - - Clase base de excepción en CakePHP. Todas las excepciones de capa de framework lanzadas por - CakePHP extenderán esta clase. - -Estas clases de excepción se extienden de :php:exc:`Exception`. -Al extender Exception, puedes crear tus propios errores de 'framework'. - -.. php:method:: responseHeader($header = null, $value = null) - :nocontentsentry: - - Consulta :php:func:`Cake\\Network\\Request::header()` - -Todas las excepciones Http y Cake extienden la clase Exception, que tiene un método -para agregar encabezados a la respuesta. Por ejemplo, al lanzar un 405 -MethodNotAllowedException, el rfc2616 dice:: - - "La respuesta DEBE incluir un encabezado Allow que contenga una lista de métodos válidos - para el recurso solicitado." - -Manejo Personalizado de Errores de PHP -====================================== - -Por defecto, los errores de PHP se representan en la consola o en la salida HTML, y también se registran. -Si es necesario, puedes cambiar la lógica de manejo de errores de CakePHP con la tuya propia. - -Registro de Errores Personalizado ---------------------------------- - -Los manejadores de errores utilizan instancias de ``Cake\Error\ErrorLoggingInterface`` para crear -mensajes de registro y registrarlos en el lugar apropiado. Puedes reemplazar el -registrador de errores utilizando el valor de configuración ``Error.errorLogger``. Un ejemplo de registrador de errores:: - - namespace App\Error; - - use Cake\Error\ErrorLoggerInterface; - use Cake\Error\PhpError; - use Psr\Http\Message\ServerRequestInterface; - use Throwable; - - /** - * Registra errores y excepciones no manejadas en `Cake\Log\Log` - */ - class ErrorLogger implements ErrorLoggerInterface - { - /** - * @inheritDoc - */ - public function logError( - PhpError $error, - ?ServerRequestInterface $request, - bool $includeTrace = false - ): void { - // Registra errores de PHP - } - - /** - * @inheritDoc - */ - public function logException( - ?ServerRequestInterface $request, - bool $includeTrace = false - ): void { - // Registra excepciones. - } - } - -**Renderizado Personalizado de Errores** - -CakePHP incluye renderizadores de errores tanto para entornos web como de consola. Sin embargo, si deseas reemplazar la lógica que renderiza los errores, puedes crear una clase personalizada:: - - // src/Error/CustomErrorRenderer.php - namespace App\Error; - - use Cake\Error\ErrorRendererInterface; - use Cake\Error\PhpError; - - class CustomErrorRenderer implements ErrorRendererInterface - { - public function write(string $out): void - { - // enviar el error renderizado al flujo de salida apropiado - } - - public function render(PhpError $error, bool $debug): string - { - // Convertir el error en una cadena de salida. - } - } - -El constructor de tu renderizador recibirá un array con la configuración almacenada en `Error`. Conecta tu renderizador de errores personalizado a CakePHP a través del valor de configuración `Error.errorRenderer`. Al reemplazar el manejo de errores, deberás tener en cuenta tanto los entornos web como los de línea de comandos. - -.. meta:: - :title lang=es: Manejo de Errores y Excepciones - :keywords lang=en: stack traces,error constants,error array,default displays,anonymous functions,error handlers,default error,error level,exception handler,php error,error handler,write error,core classes,exception handling,configuration error,application code,callback,custom error,exceptions,bitmasks,fatal error, http status codes diff --git a/es/development/rest.rst b/es/development/rest.rst deleted file mode 100644 index 59f11a72b4..0000000000 --- a/es/development/rest.rst +++ /dev/null @@ -1,167 +0,0 @@ -REST -#### - -Muchos de los nuevos programadores de aplicaciones se están dando cuenta -de la necesidad de abrir el núcleo de la funcionalidad a un mayor público. -Proporcionando acceso fácil y sin restricciones al núcleo de su API puede -ayudar a que su plataforma sea aceptada, y permite realizar mashups y fácil -integración con otros sistemas. - -Si bien existen otras soluciones, REST es una excelente manera de -proporcionar un fácil acceso a la lógica que ha creado para su aplicación. -Es simple, generalmente basado en XML (estamos hablando de simple XML, nada -como un envoltorio de SOAP), y depende de los encabezados HTTP por dirección. -Exponer una API utilizando REST en CakePHP es simple. - -La Configuración Simple -======================= - -La forma más rapida para empezar a utilizar REST es agregar unas líneas para -configurar la `resource routes ` en su archivo **config/routes.php**. - -Una vez que la ruta se ha configurado para mapear las solicitudes REST a -cierto controlador de acciones, se puede proceder a crear la lógica de nuestro -controlador de acciones. Un controlador básico podría visualizarse de la siguiente forma:: - - // src/Controller/RecipesController.php - class RecipesController extends AppController - { - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('RequestHandler'); - } - - public function index() - { - $recipes = $this->Recipes->find('all'); - $this->set('recipes', $recipes); - $this->viewBuilder()->setOption('serialize', ['recipes']); - } - - public function view($id) - { - $recipe = $this->Recipes->get($id); - $this->set('recipe', $recipe); - $this->viewBuilder()->setOption('serialize', ['recipe']); - } - - public function add() - { - $this->request->allowMethod(['post', 'put']); - $recipe = $this->Recipes->newEntity($this->request->getData()); - if ($this->Recipes->save($recipe)) { - $message = 'Saved'; - } else { - $message = 'Error'; - } - $this->set([ - 'message' => $message, - 'recipe' => $recipe, - ]); - $this->viewBuilder()->setOption('serialize', ['recipe', 'message']); - } - - public function edit($id) - { - $this->request->allowMethod(['patch', 'post', 'put']); - $recipe = $this->Recipes->get($id); - $recipe = $this->Recipes->patchEntity($recipe, $this->request->getData()); - if ($this->Recipes->save($recipe)) { - $message = 'Saved'; - } else { - $message = 'Error'; - } - $this->set([ - 'message' => $message, - 'recipe' => $recipe, - ]); - $this->viewBuilder()->setOption('serialize', ['recipe', 'message']); - } - - public function delete($id) - { - $this->request->allowMethod(['delete']); - $recipe = $this->Recipes->get($id); - $message = 'Deleted'; - if (!$this->Recipes->delete($recipe)) { - $message = 'Error'; - } - $this->set('message', $message); - $this->viewBuilder()->setOption('serialize', ['message']); - } - } - -Los controladores RESTful a menudo usan extensiones parseadas para mostrar diferentes vistas, -basado en diferentes tipos de solicitudes. Como estamos tratando con solicitudes REST, -estaremos haciendo vistas XML. Puedes realizar vistas en JSON usando el CakePHP -:doc:`/views/json-and-xml-views`. Mediante el uso de :php:class:`XmlView` se puede -definir una opción de ``serialize``. Esta opción se usa para definir qué variables de vistas -`` XmlView`` deben serializarse en XML. - -Si se quiere modificar los datos antes de convertirlos en XML, no se debería definir la -opción ``serialize``, y en lugar de eso, se debería usar archivos plantilla. Colocaremos -las vistas REST de nuestro RecipesController dentro de **templates/Recipes/xml**. también -podemos utilizar el :php:class:`Xml` para una salida XML rápida y fácil en esas vistas. -De esta forma, así podría verse nuestra vista de índice:: - - // templates/Recipes/xml/index.php - // Realizar un formateo y manipulacion en - // $recipes array. - $xml = Xml::fromArray(['response' => $recipes]); - echo $xml->asXML(); - - -Al entregar un tipo de contenido específico usando :php:meth:`Cake\\Routing\\Router::extensions()`, -CakePHP busca automáticamente un asistente de vista que coincida con el tipo. Como estamos utilizando -XML como tipo de contenido, no hay un asistente incorporado, sin embargo, si creara uno, se cargaría -automáticamente para nuestro uso en esas vistas. - -El XML procesado terminará pareciéndose a esto:: - - - - 234 - 2008-06-13 - 2008-06-14 - - 23423 - Billy - Bob - - - 245 - Yummy yummmy - - - ... - - -Crear la lógica para la acción de edición es un poco más complicado, pero no mucho. -Ya que se está proporcionando una API que genera XML como salida, es una opción natural -recibir XML como entrada. No te preocupes, las clases :php:class:`Cake\\Controller\\Component\\RequestHandler` -y :php:class:`Cake\\Routing\\Router` hacen las cosas mucho más fáciles. Si un POST o -una solicitud PUT tiene un tipo de contenido XML, entonces la entrada se ejecuta a través de la clase de CakePHP -:php:class:`Xml`, y la representación del arreglo de los datos se asigna a ``$this->request->getData()``. -Debido a esta característica, el manejo de datos XML y POST se hace en continuamente en paralelo: no se -requieren cambios en el controlador o el código del modelo. Todo lo que necesita debe terminar en -``$this->request->getData()``. - -Aceptando Entradas en otros formatos -==================================== - -Por lo general, las aplicaciones REST no solo generan contenido en formatos de datos alternativos, sino que también -acepta datos en diferentes formatos. En CakePHP, el :php:class:`RequestHandlerComponent` ayuda a fácilitar esto. -Por defecto, decodificará cualquier entrada de datos en JSON / XML para solicitudes POST / PUT y proporcionar una -versión del arreglo de esos datos en ``$this->request->getData()``. También puedes conectar deserializadores -adicionales para formatos alternativos si los necesitas, usando: :php:meth:`RequestHandler::addInputType()`. - -Enrutamiento RESTful -==================== - -El enrutador de CakePHP fácilita la conexión de rutas de recursos RESTful. Ver la sección -`resource-routes` para más información. - -.. meta:: - :title lang=es: REST - :keywords lang=es: programadores de aplicaciones,rutas por defecto,funcionalidad principal,formato resultante,mashups,base de datos de recetas,metodo de respuesta,fácil acceso,config,soap,recetas,lógica,audiencia,cakephp,ejecutandose,api diff --git a/es/development/routing.rst b/es/development/routing.rst deleted file mode 100644 index 61f27d709e..0000000000 --- a/es/development/routing.rst +++ /dev/null @@ -1,1753 +0,0 @@ -Routing -####### - -.. php:namespace:: Cake\Routing - -.. php:class:: RouterBuilder - -El enrutamiento te provee de herramientas que permiten mapear URLs a acciones -de un controlador. Al definir rutas, puedes separar cómo está implementada tu -aplicación y cómo están estructuradas sus URLs. - -El enrutamiento en CakePHP también abarca la idea de enrutamiento inverso, donde -una matriz de parámetros se puede transformar en una cadena URL. Al utilizar el -enrutamiento inverso, puedes refactorizar la estructura de tus URLs sin necesidad -de actualizar todo tu código. - -.. index:: routes.php - -Vistazo rápido -============== - -Esta sección te enseñará los usos más comunes del enrutamiento en CakePHP con -ejemplos. Normalmente, deseas mostrar algo como una página de destino, por lo que -tendrás que añadir esto a tu archivo **config/routes.php**:: - - /** @var \Cake\Routing\RouteBuilder $routes */ - $routes->connect('/', ['controller' => 'Articles', 'action' => 'index']); - -Esto ejecutará el método index que se encuentra en ``ArticlesController`` cuando -se visite la página principal de tu sitio. A veces necesitas rutas dinámicas que -aceptarán múltiples parámetos, por ejemplo cuando necesites una ruta para ver -el contenido de un artículo:: - - $routes->connect('/articles/*', ['controller' => 'Articles', 'action' => 'view']); - -La ruta anterior aceptará cualquier URL que se parezca a ``/article/15`` e invocará -el método ``view(15)`` de ``ArticlesController``. Esto no prevendrá que las personas -intenten acceder a URLs como ``/articles/foobar``. Si quieres, puedes restringir -algunos parámetros que se ajusten a una expresión regular:: - - // Utilizando una interfaz fluida - $routes->connect( - '/articles/{id}', - ['controller' => 'Articles', 'action' => 'view'], - ) - ->setPatterns(['id' => '\d+']) - ->setPass(['id']); - - // Utilizando una matriz de opciones - $routes->connect( - '/articles/{id}', - ['controller' => 'Articles', 'action' => 'view'], - ['id' => '\d+', 'pass' => ['id']] - ); - -En el ejemplo anterior se cambió el comparador asterisco por un nuevo marcador de -posición ``{id}``. Utilizar marcadores de posición nos permite valiadr partes de -la URL, en este caso utilizamos la expresión regular ``\d+`` por lo que sólo los -dígitos son comparados. Finalmente, le indicamos al enrutador que trate el marcador -de posición ``id`` como un argumento de función para el método ``view()`` -especificando la opción ``pass``. -Hablaremos más sobre el uso de esta opción más adelante. - -El enrutador de CakePHP también puede revertir rutas de coincidencia. Esto quiere -decir que desde una matriz que contiene parámetros de coincidencia es capaz de generar -una cadena de URL:: - - use Cake\Routing\Router; - - echo Router::url(['controller' => 'Articles', 'action' => 'view', 'id' => 15]); - // Obtendrás como salida - /articles/15 - -Las rutas también pueden etiquetarse con un nombre único, esto te permite referenciarlas -rápidamente cuando creas enlaces en lugar de especificar cada uno de los parámetros de -la ruta:: - - // En routes.php - $routes->connect( - '/upgrade', - ['controller' => 'Subscriptions', 'action' => 'create'], - ['_name' => 'upgrade'] - ); - - use Cake\Routing\Router; - - echo Router::url(['_name' => 'upgrade']); - // Obtendrás como salida - /upgrade - -Para ayudar a mantener tu código de enrutamiento :term:`DRY`, el Enrutador tiene el concepto -de 'ámbitos'. -Un ámbito define un segmento de ruta común y, opcionalmente, rutas predeterminadas. -Cualquier ruta conectada dentro de un ámbito heredará la ruta y valores por defecto -de su ámbito:: - - $routes->scope('/blog', ['plugin' => 'Blog'], function (RouteBuilder $routes) { - $routes->connect('/', ['controller' => 'Articles']); - }); - -La rua anterior coincidiría con ``/blog/`` y la enviaría a -``Blog\Controller\ArticlesController::index()``. - -El esqueleto de la aplicación viene con algunas rutas de inicio. Una vez has añadido -tus tuyas propias, puedes eliminar las rutas por defecto si no las necesitas. - -.. index:: {controller}, {action}, {plugin} -.. index:: greedy star, trailing star -.. _connecting-routes: -.. _routes-configuration: - -Conectando Rutas -================ - -Para mantener tu código :term:`DRY` debes utilizar 'ámbitos de ruta'. Los ámbitos -de ruta no sólo te facilitan mantener tu código DRY, sino que ayudan al Enrutador a -optimizar sus operaciones. Este método se aplica por defecto al ámbito ``/``. Para -crear un ámbito y conectar algunas rutas utilizarás el método ``scope()``:: - - // En config/routes.php - use Cake\Routing\RouteBuilder; - use Cake\Routing\Route\DashedRoute; - - $routes->scope('/', function (RouteBuilder $routes) { - // Conecta las rutas alternativas genéricas. - $routes->fallbacks(DashedRoute::class); - }); - -El método ``connect()`` acepta hasta tres parámetros: la plantilla de URL para la que -deseas conincidencias, los valores predeterminados para los elementos de tu ruta. -Las opciones frecuentemente incluyen reglas de expresión regular que para ayudar al -enrutador a coincidir con elementos de la URL. - -El formato básico para la definición de una ruta es:: - - $routes->connect( - '/url/template', - ['targetKey' => 'targetValue'], - ['option' => 'matchingRegex'] - ); - -El primer parámetro se utiliza para indicarle al enrutador qué tipo de URL se -está intentando controlar. La URL es una cadena normal delimitada por barras -diagonales, pero también puede contener un comodín (\*) o :ref:`route-elements`. -El uso de un comodín le indica al enrutador que puede aceptar cualquier argumento -adicional que se le proporcione. Las rutas sin \* sólo coincidirán con el patrón -de plantilla exacto proporcionado. - -Una vez que hayas especificado una URL, utiliza los dos últimos parámetros de -``connect()`` para indicar a CakePHP qué debe hacer con la solicitud cuando -haya coincidencia. El segundo parámetro define la ruta 'objetivo'. Esto se puede -definir como una matriz o como una cadena de destino. Algunos ejemplos de ruta -objetivo son:: - - // Matriz de destino a un controlador de aplicación - $routes->connect( - '/users/view/*', - ['controller' => 'Users', 'action' => 'view'] - ); - $routes->connect('/users/view/*', 'Users::view'); - - // Matriz de destino a un controlador de plugin con prefijo - $routes->connect( - '/admin/cms/articles', - ['prefix' => 'Admin', 'plugin' => 'Cms', 'controller' => 'Articles', 'action' => 'index'] - ); - $routes->connect('/admin/cms/articles', 'Cms.Admin/Articles::index'); - -La primera ruta que conectamos coincide con las URL que comienzan con ``/users/view`` -y asigna esas solucitudes al ``UsersController->view()``. El ``/*`` indica al enrutador -para pasar cualquier segmento adicional como argumentos del método. Por ejemplo, -``/users/view/123`` se asignaría a ``UsersController->view(123)``. - -El ejemplo anterior también ilustra los destinos de cadena. Los destinos de cadena -proporcionan una forma compacta de definir el destino de una ruta. Los destinos de -cadena tienen la siguiente sintaxis:: - - [Plugin].[Prefix]/[Controller]::[action] - -Algunos ejemplos de destino de cadena son:: - - // Controlador de aplicación - 'Articles::view' - - // Controlador de aplicación con prefijo - Admin/Articles::view - - // Controlador de plugin - Cms.Articles::edit - - // Controlador de plugin con prefijo - Vendor/Cms.Management/Admin/Articles::view - -Anteriormente, usamos el asterisco final (``/*``) para capturar segmentos de ruta adicionales, -también está el doble asterisco final (``/**``). Utilizando el doble asterisco final, -capturará el resto de una URL como un único argumento. Esto es útil cuando se desea -utilizar un argumento que incluye ``/``:: - - $routes->connect( - '/pages/**', - ['controller' => 'Pages', 'action' => 'show'] - ); - -La URL entrante ``/pages/the-example-/-and-proof`` daría como resultado el paso de un -único argumento ``the-example-/-and-proof``. - -El segundo parámetro de ``connect()`` puede definir cualquier parámetro para componer -los parámetros de ruta po predeterminado:: - - $routes->connect( - '/government', - ['controller' => 'Pages', 'action' => 'display', 5] - ); - -Este ejemplo utiliza el segundo parámetro de ``connect()`` para definir los parámetros -predeterminados. Si creas una aplicación que presenta productos para diferentes categorías -de clientes, podrías considerar crear una ruta. Esto permite enlazar ``/government`` en -lugar de ``/pages/display/5``. - -Un uso común del enrutamiento es renombrar los controladores y sus acciones. En lugar de -acceder a nuestro controlador de usuarios como ``/users/some-action/5``, nos gustaría acceder -a él a través de ``/cooks/some-action/5``. La siguiente ruta se encarga de eso:: - - $routes->connect( - '/cooks/{action}/*', ['controller' => 'Users'] - ); - -Esto indica al enrutador que cualquier URL que empieze por ``/cooks/`` deberá ser -enviada al ``UsersController``. La acción invocada dependerá del valor del parámetro ``{action}``. -Utilizando :ref:`route-elements`, puedes crear rutas variables que aceptan entradas del usuario -o variables. La ruta anterior también utiliza el asteristo final. El asterisco final indica que -esta ruta debe aceptar cualquier argumento posicional adicional dado. Estos argumentos estarán -disponibles en la matriz :ref:`passed-arguments`. - -Al generar URL también se utilizan rutas. Utilizando -``['controller' => 'Users', 'action' => 'some-action', 5]`` como una URL, generará -``/cooks/some-action/5`` si la ruta anterior es la primera coincidencia encontrada. - -Las ruts que hemos conectado hasta ahora coincidirán con cualquier tipo de petición HTTP. Si estás -contruyendo un API REST, a menudo querrás asignar acciones HTTP a diferentes métodos de -controlador. El ``RouteBuilder`` proporciona métodos auxiliares que facilitan la definición -de rutas para tipos de peticiones HTTP específicas más simples:: - - // Crea una ruta que sólo responde a peticiones GET. - $routes->get( - '/cooks/{id}', - ['controller' => 'Users', 'action' => 'view'], - 'users:view' - ); - - // Crea una ruta que sólo responde a peticiones PUT - $routes->put( - '/cooks/{id}', - ['controller' => 'Users', 'action' => 'update'], - 'users:update' - ); - -Las rutas anteriores asignan la misma URL a diferentes acciones del controlador según -el tipo de petición HTTP utilizada. Las solicitudes GET irán a la acción 'view', mientras -que las solicitudes PUT irán a la acción UPDATE. Existen métodos auxiliares HTTP para: - -* GET -* POST -* PUT -* PATCH -* DELETE -* OPTIONS -* HEAD - -Todos estos métodos devuelven una instancia de ruta, lo que permite aprovechar la -:ref:`fluent setterts ` para configurar aún más la ruta. - -.. _route-elements: - -Elementos de ruta ------------------ - -Puedes especificar tus propios elementos de ruta y al hacerlo podrás definir los -lugares de la URL donde los parámetros para las acciones del controlador deben estar. -Cuando se realiza una solicitud, los valores para estos elementos de ruta se encuentran -en ``$this->request->getParam()`` en el controlador. -Cuando defines un elemento de ruta personalizado, opcionalmente puedes especificar -una expresión regular; esto le dice a CakePHP como saber si la URL está formada -correctamente o no. Si eliges no proporcionar una expresión regular, cualquier caracter -que no sea ``/`` será tratado como parte del parámetro:: - - $routes->connect( - '/{controller}/{id}', - ['action' => 'view'] - )->setPatterns(['id' => '[0-9]+']); - - $routes->connect( - '/{controller}/{id}', - ['action' => 'view'], - ['id' => '[0-9]+'] - ); - -El ejemplo anterior ilustra cómo crear una forma rápida de ver modelos desde cualquier -controlador creando una URL que se parezca a ``/controllername/{id}``. La URL proporcionada -a ``connect()`` especifica dos elementos de ruta: ``{controller}`` y ``{id}``. El elemento -``{controller}`` es un elemento de ruta predeterminado de CakePHP, por lo que el enrutador -conoce cómo identificar y emparejar los nombres de controladores en la URL. El elemento -``{id}`` es un elemento de ruta personalizado y debe aclararse especificando una expresión -regular en el tercer parámetro de ``connect()``. - -CakePHP no produce automáticamente URL en minúsculas y con guiones cuando utiliza el -parámetro ``{controller}``. Si necesitas hacer esto, el ejemplo anterior podría ser -reescrito así:: - - use Cake\Routing\Route\DashedRoute; - - // Crea un constructor con una clase de ruta diferente - $routes->scope('/', function (RouteBuilder $routes) { - $routes->setRouteClass(DashedRoute::class); - $routes->connect('/{controller}/{id}', ['action' => 'view']) - ->setPatterns(['id' => '[0-9]+']); - - $routes->connect( - '/{controller}/{id}', - ['action' => 'view'], - ['id' => '[0-9]+'] - ); - }); - -La clase ``DashedRoute`` se asegurará de que los parámetros ``{controller}`` y -``{plugin}`` están correctamente en minúsculas y con guiones. - -.. note:: - - Los patrones utilizados por los elementos de ruta no deben contener - ningún grupo de captura. Si lo hacen, el enrutador no funcionará - correctamente. - -Una vez que se ha definido esta ruta, al solicitar ``/apples/5`` se llamará al método -``view()`` de ApplesController. Dento del método ``view()``, necesitarás acceder al ID -pasado en ``$this->request->getParam('id')``. - -Si tienes un único controlador en tu aplicación y no quieres que el nombre del -controlador aparezca en la URL, puedes asignar todas las URL a acciones en tu -controlador. Por ejemplo, para asignar todas las URL a acciones del contolador -``home``, para tener una URL como ``/demo`` en lugar de ``/home/demo``, puedes -hacer lo siguiente:: - - $routes->connect('/{action}', ['controller' => 'Home']); - -Si quieres proporcionar una URL que no distinga entre mayúsculas y minúsculas, -puedes utilizar modificadores en línea de expresiones regulares:: - - $routes->connect( - '/{userShortcut}', - ['controller' => 'Teachers', 'action' => 'profile', 1], - )->setPatterns(['userShortcut' => '(?i:principal)']); - -Un ejemplo más y serás un profesional del enrutamiento:: - - $routes->connect( - '/{controller}/{year}/{month}/{day}', - ['action' => 'index'] - )->setPatterns([ - 'year' => '[12][0-9]{3}', - 'month' => '0[1-9]|1[012]', - 'day' => '0[1-9]|[12][0-9]|3[01]' - ]); - -Esto es bastante complicado, pero muestra cuán poderosas pueden ser las rutas. La URL -proporcionada tiene cuatro elementos de ruta. El primero nos resulta familiar: es -un elemento de ruta por defecto que incica a CakePHP que espere un nombre de controlador. - -A continuación, especificamos algunos valores predeterminados. Independientemente -del controlador, queremos que se llame a la acción ``index()``. - -Finalmente, especificamos algunas expresiones regulares que coincidirán con año, mes -y día en forma numérica. Ten en cuenta que los paréntesis (captura de grupos) no se -admiten en expresiones regulares. Aún podemos especificar alternativas, como se -indicó anteriormente, pero no agrupadas entre paréntesis. - -Una vez definida, esta ruta coincidirá con ``/articles/2007/02/01``, -``/articles/2004/11/16``, entregando las solicitudes a la acción ``index()`` -de sus respectivos controladores, con los parámetros de fecha en -``$this->request->getParams()``. - -Elementos de Ruta Reservados ----------------------------- - -Hay varios elementos de ruta que tienen un significado especial en CakePHP, -y no deben usarse a menos que desee un significado especial - -* ``controller`` Se utiliza para nombrar el controlador de una ruta. -* ``action`` Se utiliza para nombrar la acción del controlador para una ruta. -* ``plugin`` Se utiliza para nombrar el complemento en el que se encuentra un controlador. -* ``prefix`` Usado para :ref:`prefix-routing` -* ``_ext`` Usado para :ref:`File extentions routing `. -* ``_base`` Se establece a ``false`` para eliminar la ruta base de la URL generada. Si - su aplicación no está en el directorio raíz, esto puede utilizarse para generar URL - que son 'cake relative'. -* ``_scheme`` Configurado para crear enlaces en diferentes esquemas como `webcal` o `ftp`. - El valor predeterminado es el esquema actual. -* ``_host`` Establece el host que se utilizará para el enlace. El valor predeterminado - es el host actual. -* ``_port`` Establece el puerto si necesitamos crear enlaces en puertos no estándar. -* ``_full`` Si es ``true`` el valor de ``App.fullBaseUrl`` mencionado en - :ref:`general-configuration` se atepondrá a la URL generada. -* ``#`` Permite configurar fragmentos de hash de URL. -* ``_https`` Establecerlo en ``true`` para convertir la URL generada a https o``false`` - para forzar http. Antes de 4.5.0 utilizar ``_ssl``. -* ``_method`` Define el tipo de petición/método a utilizar. Útil cuando trabajamos con - :ref:`resource-routes`. -* ``_name`` Nombre de la ruta. Si has configurado rutas con nombre, puedes utilizar - esta clave para especificarlo. - -.. _route-fluent-methods: - -Configurando Opciones de Ruta ------------------------------ - -Hay varias opciones de ruta que se pueden configurar en cada ruta. Después de -conectar una ruta, puedes utilizar sus métodos de creación fluidos para configurar -aún más la ruta. Estos métodos reemplazan muchas de las claves en el parámetro -``$options`` de ``connect()``:: - - $routes->connect( - '/{lang}/articles/{slug}', - ['controller' => 'Articles', 'action' => 'view'], - ) - // Permite peticiones GET y POST. - ->setMethods(['GET', 'POST']) - - // Sólo coincide con el subdominio del blog. - ->setHost('blog.example.com') - - // Establecer los elementos de ruta que deben convertirse en argumentos pasados - ->setPass(['slug']) - - // Establecer los patrones de coincidencia para los elementos de ruta - ->setPatterns([ - 'slug' => '[a-z0-9-_]+', - 'lang' => 'en|fr|es', - ]) - - // También permite archivos con extensión JSON - ->setExtensions(['json']) - - // Establecer lang para que sea un parámetro persistente - ->setPersist(['lang']); - -Pasar Parámetros a una Acción ------------------------------ - -Cuando conectamos rutas utilizando ::ref:`route-elements` es posible que desees -que los elementos enrutados se pasen como argumentos. La opción ``pass`` indica -qué elementos de ruta también deben estaar disponibles como argumentos pasados -a las funciones del controlador:: - - // src/Controller/BlogsController.php - public function view($articleId = null, $slug = null) - { - // Algún código aquí... - } - - // routes.php - $routes->scope('/', function (RouteBuilder $routes) { - $routes->connect( - '/blog/{id}-{slug}', // For example, /blog/3-CakePHP_Rocks - ['controller' => 'Blogs', 'action' => 'view'] - ) - // Definir los elementos de ruta en la plantilla de ruta - // para anteponerlos como argumentos de la función. El orden - // es importante ya que esto pasará los elementos `$id` y `$slug` - // como primer y segundo parámetro. Cualquier otro parámetro - // adicional pasado en tu ruta se agregará después de los - // argumentos de setPass(). - ->setPass(['id', 'slug']) - // Definir un patrón con el que `id` debe coincidir. - ->setPatterns([ - 'id' => '[0-9]+', - ]); - }); - -Ahora, gracias a las capacidades de enturamiento inverso, puedes pasar la matriz -de URL como se muestra a continuación y CakePHP sabrá cómo formar la URL como se -define en las rutas:: - - // view.php - // Esto devolverá un enlace a /blog/3-CakePHP_Rocks - echo $this->Html->link('CakePHP Rocks', [ - 'controller' => 'Blog', - 'action' => 'view', - 'id' => 3, - 'slug' => 'CakePHP_Rocks' - ]); - - // También podemos utilizar índices numéricos como parámetros. - echo $this->Html->link('CakePHP Rocks', [ - 'controller' => 'Blog', - 'action' => 'view', - 3, - 'CakePHP_Rocks' - ]); - -.. _path-routing: - -Uso del Enrutamiento de Ruta ----------------------------- - -Hablamos de objetivos de cadena anteriormente. Lo mismo también funciona para la -generación de URL usando ``Router::pathUrl()``:: - - echo Router::pathUrl('Articles::index'); - // salida: /articles - - echo Router::pathUrl('MyBackend.Admin/Articles::view', [3]); - // salida: /admin/my-backend/articles/view/3 - -.. tip:: - - La compatibilidad del IDE para el autocompletado del enrutamiento de ruta se puede habilitar con `CakePHP IdeHelper Plugin `_. - -.. _named-routes: - -Usar Rutas con Nombre ---------------------- - -A veces encontrarás que escribir todos los parámetros de la URL para una ruta es -demasiado detallado, o le gustaría aprovechar las mejoras de rendimiento que tienen -las rutas con nombre. Al conectar rutas, puedes especificar una opción ``_name``, -esta opción se puede utilizar en rutas inversas para identificar la ruta que deseas -utilizar:: - - // Conectar una ruta con nombre. - $routes->connect( - '/login', - ['controller' => 'Users', 'action' => 'login'], - ['_name' => 'login'] - ); - - // Nombrar una ruta específica según el tipo de petición - $routes->post( - '/logout', - ['controller' => 'Users', 'action' => 'logout'], - 'logout' - ); - - // Generar una URL utilizando una ruta con nombre. - $url = Router::url(['_name' => 'logout']); - - // Generar una URL utilizando una ruta con nombre, - // con algunos argumentos de cadena en la consulta. - $url = Router::url(['_name' => 'login', 'username' => 'jimmy']); - -Si tu plantilla de ruta contienen elementos de ruta como ``{controller}`` deberás -proporcionarlos como parte de las opciones de ``Router::url()``. - -.. note:: - - Los nombres de las rutas deben ser únicos en toda la aplicación. El mismo - ``_name`` no se puede utilizar dos veces, incluso si los nombres aparecen - dentro de un alcance de enrutamiento diferente. - -Al crear rutas con nombre, probablemente querrás ceñirte a algunas convenciones -para los nombres de las rutas. CakePHP facilita la creación de nombres de rutas -al permitir definir prefijos de nombres en cada ámbito:: - - $routes->scope('/api', ['_namePrefix' => 'api:'], function (RouteBuilder $routes) { - // El nombre de esta ruta será `api:ping` - $routes->get('/ping', ['controller' => 'Pings'], 'ping'); - }); - // Generar una URL para la ruta de ping - Router::url(['_name' => 'api:ping']); - - // Utilizar namePrefix con plugin() - $routes->plugin('Contacts', ['_namePrefix' => 'contacts:'], function (RouteBuilder $routes) { - // Conectar rutas. - }); - - // O con prefix() - $routes->prefix('Admin', ['_namePrefix' => 'admin:'], function (RouteBuilder $routes) { - // Conectar rutas. - }); - -También puedes utilizar la opción ``_namePrefix`` dentro de ámbitos anidados y -funciona como se esperaba:: - - $routes->plugin('Contacts', ['_namePrefix' => 'contacts:'], function (RouteBuilder $routes) { - $routes->scope('/api', ['_namePrefix' => 'api:'], function (RouteBuilder $routes) { - // Este nombre de ruta será `contacts:api:ping` - $routes->get('/ping', ['controller' => 'Pings'], 'ping'); - }); - }); - - // Generar una URL para la ruta de ping - Router::url(['_name' => 'contacts:api:ping']); - -Las rutas conectadas en ámbitos con nombre sólo se les agregarán nombres si la -ruta también tiene nombre. A las rutas sin nombre no se les aplicará el ``_namePrefix``. -Routes connected in named scopes will only have names added if the route is also -named. Nameless routes will not have the ``_namePrefix`` applied to them. - -.. index:: admin routing, prefix routing -.. _prefix-routing: - -Enrutamiento de Prefijo ------------------------ - -.. php:staticmethod:: prefix($name, $callback) - -Muchas aplicaciones requieren una sección de adminitración donde -los usuarios con privilegios puedan realizar cambios. Esto se hace -a menudo a través de una URL especial como ``/admin/users/edit/5``. -En CakePHP, el enrutamiento de prefijo puede ser habilitado utilizando -el método de ámbito ``prefix``:: - - use Cake\Routing\Route\DashedRoute; - - $routes->prefix('Admin', function (RouteBuilder $routes) { - // Todas las rutas aquí tendrán el prefijo `/admin`, y - // tendrán el elemento de ruta `'prefix' => 'Admin'` agregado que - // será necesario para generar URL para estas rutas - $routes->fallbacks(DashedRoute::class); - }); - -Los prefijos se asignan a subespacios de nombres en el espacio de nombres -``Controller`` en tu aplicación. Al tener prefijos como controladores separados, -puedes crear controladores más pequeños y simples. El comportamiento que es común -a los controladores con y sin prefijo se puede encapsular mediante herencia, -:doc:`/controllers/components`, o traits. Utilizando nuestro ejemplo de usuarios, -acceder a la URL ``/admin/users/edit/5`` llamaría al médito ``edit()`` de nuestro -**src/Controller/Admin/UsersController.php** pasando 5 como primer parámetro. -El archivo de vista utilizado sería **templates/Admin/Users/edit.php** - -Puedes asignar la URL /admin a tu acción ``index()`` del controlador pages utilizando -la siguiente ruta:: - - $routes->prefix('Admin', function (RouteBuilder $routes) { - // Dado que te encuentras en el ámbito de admin, - // no necesitas incluir el prefijo /admin ni el - // elemento de ruta Admin. - $routes->connect('/', ['controller' => 'Pages', 'action' => 'index']); - }); - -Al crear rutas de prefijo, puedes establecer parámetros de ruta adicionales -utilizando el argumento ``$options``:: - - $routes->prefix('Admin', ['param' => 'value'], function (RouteBuilder $routes) { - // Las rutas conectadas aquí tienen el prefijo '/admin' y - // tienen configurada la clave de enrutamiento 'param'. - $routes->connect('/{controller}'); - }); - -Los prefijos con varias palabras se convierten de forma predeterminada utilizando la -inflexión dasherize, es decir, ``MyPrefix`` se asignará a ``my-prefix`` en la URL. -Asegúrate de establecer una ruta para dichos prefijos si deseas utilizar un formato -diferente como, por ejemplo, subrayado:: - - $routes->prefix('MyPrefix', ['path' => '/my_prefix'], function (RouteBuilder $routes) { - // Las rutas conectadas aquí tiene el prefijo '/my_prefix' - $routes->connect('/{controller}'); - }); - -También puedes definir prefijos dentro del alcance de un plugin:: - - $routes->plugin('DebugKit', function (RouteBuilder $routes) { - $routes->prefix('Admin', function (RouteBuilder $routes) { - $routes->connect('/{controller}'); - }); - }); - -Lo anterior crearía una plantilla de ruta como ``/debug-kit/admin/{controller}``. -La ruta conectada tendría establecidos los elementos de ruta ``plugin`` y ``prefix``. - -Al definir prefijos, puedes anidar varios prefijos si es necesario:: - - $routes->prefix('Manager', function (RouteBuilder $routes) { - $routes->prefix('Admin', function (RouteBuilder $routes) { - $routes->connect('/{controller}/{action}'); - }); - }); - -Lo anterior crearía una plantilla de ruta como ``/manager/admin/{controller}/{action}``. -La ruta conectada tendría establecido el elemento de ruta ``prefix`` a ``Manager/Admin``. - -El prefijo actual estará disponible desde los métodos del controlador a través de -``$this->request->getParam('prefix')`` - -Cuando usamos rutas de prefijo es importante configurar la opción ``prefix`` y -utilizar el mismo formato CamelCased que se utiliza in el método ``prefix()``. -A continuación se explica cómo crear este enlace utilizando el helper HTML:: - - // Ve a una ruta de prefijo - echo $this->Html->link( - 'Manage articles', - ['prefix' => 'Manager/Admin', 'controller' => 'Articles', 'action' => 'add'] - ); - - // Deja un prefijo - echo $this->Html->link( - 'View Post', - ['prefix' => false, 'controller' => 'Articles', 'action' => 'view', 5] - ); - -.. index:: plugin routing - -Crear Enlaces a Rutas de Prefijo --------------------------------- - -Puedes crear enlaces que apunten a un prefijo añadiendo la clave del prefijo a la matriz -de URL:: - - echo $this->Html->link( - 'New admin todo', - ['prefix' => 'Admin', 'controller' => 'TodoItems', 'action' => 'create'] - ); - -Al utilizar anidamiento, es necesario encadenarlos entre sí:: - - echo $this->Html->link( - 'New todo', - ['prefix' => 'Admin/MyPrefix', 'controller' => 'TodoItems', 'action' => 'create'] - ); - -Esto se vincularía a un controlador con el espacio de nombre ``App\Controller\Admin\MyPrefix`` y -la ruta de archivo ``src/Controller/Admin/MyPrefix/TodoItemsController.php``. - -.. note:: - - Aquí el prefijo siempre es CamelCased, incluso si el resultado del enrutamiento - es discontinuo. - La propia ruta hará la inflexión si es necesario. - -Enrutamiento de Plugin ----------------------- - -.. php:staticmethod:: plugin($name, $options = [], $callback) - -Las rutas para :doc:`/plugins` deben crearse utilizando el método ``plugin()``. -Este método crea un nuevo ámbito de enrutamiento para las rutas del plugin:: - - $routes->plugin('DebugKit', function (RouteBuilder $routes) { - // Las rutas conectadas aquí tienen el prefijo '/debug-kit' y - // el elemento de ruta plugin configurado a 'DebugKit'. - $routes->connect('/{controller}'); - }); - -Cuando creamos ámbitos de plugin, puedes personalizar el elemento de ruta utilizado -con la opción ``path``:: - - $routes->plugin('DebugKit', ['path' => '/debugger'], function (RouteBuilder $routes) { - // Las rutas conectadas aquí tiene el prefijo '/debugger' y - // el elemento de ruta plugin configurado a 'DebugKit'. - $routes->connect('/{controller}'); - }); - -Al utilizar ámbitos, puedes anidar ámbitos de plugin dentro de ámbitos de prefijos:: - - $routes->prefix('Admin', function (RouteBuilder $routes) { - $routes->plugin('DebugKit', function (RouteBuilder $routes) { - $routes->connect('/{controller}'); - }); - }); - -Lo anteior crearía una ruta similar a ``/admin/debug-kit/{controller}``. -Tendría configurados los elementos de ruta ``prefix`` y ``plugin``. En la sección -:ref:`plugin-routes` hay más información sobre la creación de rutas de plugin. - -Crear Enlaces a Rutas de Plugin -------------------------------- - -Puedes crear enlaces que apunten a un plugin añadiendo la clave plugin a tu -matrix de URL:: - - echo $this->Html->link( - 'New todo', - ['plugin' => 'Todo', 'controller' => 'TodoItems', 'action' => 'create'] - ); - -Por el contrario, si la solicitud activa es una solicitud de plugin y deseas crear -un enlace que no tenga plugin puedes hacer lo siguiente:: - - echo $this->Html->link( - 'New todo', - ['plugin' => null, 'controller' => 'Users', 'action' => 'profile'] - ); - -Estableciendo ``'plugin' => null`` le indicas al Enrutador que quieres -crear un enlace que no forme parte de un plugin. - -Enrutamiento SEO-Friendly -------------------------- - -Algunos desarrolladores prefieren utilizar guiones en las URL, ya que se -percibe que dan un mejor posicionamiento en los motores de búsqueda. -La clase ``DashedRoute`` se puede utilizar en tu aplicación con la capacidad -de enrutar plugin, controlador y acciones camelizadas a una URL con guiones. - -Por ejemplo, si tenemos un plugin ``ToDo``, con un controlador ``TodoItems``, y -una acción ``showItems()``, se podría acceder en ``/to-do/todo-items/show-items`` -con la siguiente conexión de enrutador:: - - use Cake\Routing\Route\DashedRoute; - - $routes->plugin('ToDo', ['path' => 'to-do'], function (RouteBuilder $routes) { - $routes->fallbacks(DashedRoute::class); - }); - -Coincidencia de Métodos HTTP Específicos ----------------------------------------- - -Las rutas pueden coincidir con métodos HTTP específicos utilizando los métodos -del helper HTTP:: - - $routes->scope('/', function (RouteBuilder $routes) { - // Esta ruta sólo coincide con peticiones POST. - $routes->post( - '/reviews/start', - ['controller' => 'Reviews', 'action' => 'start'] - ); - - // Coincide con múltiples tipos de peticiones - $routes->connect( - '/reviews/start', - [ - 'controller' => 'Reviews', - 'action' => 'start', - ] - )->setMethods(['POST', 'PUT']); - }); - -Puedes hacer coincidir múltiples métodos HTTP utilizando una matriz. Dada que el -parámetro ``_method`` es una clave de enturamiento, participa tanto en el análisis -como en la generación de URL. Para generar URLs para rutas específicas de un método -necesitarás incluir la clave ``_method`` al generar la URL:: - - $url = Router::url([ - 'controller' => 'Reviews', - 'action' => 'start', - '_method' => 'POST', - ]); - -Coincidencia con Nombres de Dominio Específicos ------------------------------------------------ - -Las rutas pueden utilizar la opción ``_host`` para coincidir sólo con dominios -específicos. Puedes utilizar el comodín ``*.`` para coincidir con cualquier -subdominio:: - - $routes->scope('/', function (RouteBuilder $routes) { - // Esta ruta sólo coincide en http://images.example.com - $routes->connect( - '/images/default-logo.png', - ['controller' => 'Images', 'action' => 'default'] - )->setHost('images.example.com'); - - // Esta ruta sólo coincide en http://*.example.com - $routes->connect( - '/images/old-logo.png', - ['controller' => 'Images', 'action' => 'oldLogo'] - )->setHost('*.example.com'); - }); - -La opción ``_host`` también se utiliza en la generación de URL. Si tu opción -``_host`` especifica un dominio exacto, ese dominio se incluirá en la URL -generada. Sin embargo, si utilizas un comodín, tendrás que indicar el parámetro -``_host`` al generar la URL:: - - // Si tienes esta ruta - $routes->connect( - '/images/old-logo.png', - ['controller' => 'Images', 'action' => 'oldLogo'] - )->setHost('images.example.com'); - - // Necesitas esto para generar la URL - echo Router::url([ - 'controller' => 'Images', - 'action' => 'oldLogo', - '_host' => 'images.example.com', - ]); - -.. index:: file extensions -.. _file-extensions: - -Enrutamiento de Extensiones de Archivo --------------------------------------- -.. php:staticmethod:: extensions(string|array|null $extensions, $merge = true) - -Para manejar diferentes extensiones de archivo en tus URL, puedes definir las -extensiones utilizando el método :php:meth:`Cake\\Routing\\RouteBuilder::setExtensions()`:: - - $routes->scope('/', function (RouteBuilder $routes) { - $routes->setExtensions(['json', 'xml']); - }); - -Esto habilitará ls extensiones nombradas para todas las rutas que se estén conectando -en ese ámbito **después** de la llamada a ``setExtensions()``, incluidas aquellas que -se estén conectando en ámbitos anidados. - -.. note:: - - Configurar las extensiones debe ser lo primero que hagas en un ámbito, ya que - las extensiones sólo se aplicarán a rutas conectadas **después** de configurar - las extensiones. - - También ten en cuenta que los ámbitos reabiertos **no** heredarán las extensiones - definidas en ámbitos abiertos anteriormente. - -Al utilizar extensiones, le indicas al enrutador que elimine cualquier extensión de -archivo coincidente en la URL y luego analice lo que queda. Si deseas crear una URL como -/page/title-of-page.html, crearías su ruta usando:: - - $routes->scope('/page', function (RouteBuilder $routes) { - $routes->setExtensions(['json', 'xml', 'html']); - $routes->connect( - '/{title}', - ['controller' => 'Pages', 'action' => 'view'] - )->setPass(['title']); - }); - -Luego, para crear enlaces que correspondan con las rutas, simplemente usa:: - - $this->Html->link( - 'Link title', - ['controller' => 'Pages', 'action' => 'view', 'title' => 'super-article', '_ext' => 'html'] - ); - -.. _route-scoped-middleware: - -Middleware de Ámbito de Ruta -============================ - -Si bien el Middleware se puede aplicar a toda tu aplicación, aplicar middleware -a ámbitos de enrutamiento específicos ofrece más flexibilidad, ya que puedes aplicar -middleware sólo donde sea necesario, lo que permite que tu middleware no se preocupe -por cómo y dónde se aplica. - -.. note:: - - El middleware con ámbito aplicado se ejecutará mediante :ref:`RoutingMiddleware `, - normalmente al final de la cola de middleware de tu aplicación. - -Antes de que se pueda aplicar middleware a tu acplicación, es necesario -registrarlo en la colección de rutas:: - - // en config/routes.php - use Cake\Http\Middleware\CsrfProtectionMiddleware; - use Cake\Http\Middleware\EncryptedCookieMiddleware; - - $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware()); - $routes->registerMiddleware('cookies', new EncryptedCookieMiddleware()); - -Una vez registrado, el middleware con ámbito se podrá aplicar -a ámbitos específicos:: - - $routes->scope('/cms', function (RouteBuilder $routes) { - // Activa CSRF y cookies middleware - $routes->applyMiddleware('csrf', 'cookies'); - $routes->get('/articles/{action}/*', ['controller' => 'Articles']); - }); - -En situaciones en las que tienes ámbitos anidados, los ámbitos internos -heredarán el middleware aplicado en el ámbito contenedor:: - - $routes->scope('/api', function (RouteBuilder $routes) { - $routes->applyMiddleware('ratelimit', 'auth.api'); - $routes->scope('/v1', function (RouteBuilder $routes) { - $routes->applyMiddleware('v1compat'); - // Definir las rutas aquí. - }); - }); - -En el ejemplo anterior, las rutas definidas en ``/v1`` tendrán aplicado el -middleware 'ratelimit', 'auth.api' y 'v1compat'. Si vuelves a abrir un ámbito, -el middleware aplicado a las rutas en cada ámbito quedará aislado:: - - $routes->scope('/blog', function (RouteBuilder $routes) { - $routes->applyMiddleware('auth'); - // Conecta las acciones authenticadas para el blog aquí. - }); - $routes->scope('/blog', function (RouteBuilder $routes) { - // Conecta las acciones públicas para el blog aquí. - }); - -En el ejemplo anterior, los dos usos del alcance ``/blog`` no comparten middleware. -Sin embargo, ambos ámbitos heredarán el middleware definido en los ámbitos que los -engloban. - -Agrupación de Middleware ------------------------- - -Para ayudar a mantener tu código de ruta :abbr:`DRY (Do not Repeat Yourself)` el middleware -se puede combinar en grupos. Una vez combinados, los grupos pueden aplicarse como middleware:: - - $routes->registerMiddleware('cookie', new EncryptedCookieMiddleware()); - $routes->registerMiddleware('auth', new AuthenticationMiddleware()); - $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware()); - $routes->middlewareGroup('web', ['cookie', 'auth', 'csrf']); - - // Aplicar el grupo - $routes->applyMiddleware('web'); - -.. _resource-routes: - -Enrutamiento RESTful -==================== - -El enrutador ayuda a generar rutas RESTful para tus controladores. Las rutas RESTful -son útiles cuando estás creando API endpoints para tus aplicaciones. Si quisiéramos -permitir el acceso REST a un controlador de recetas, haríamos algo como esto:: - - // En config/routes.php... - - $routes->scope('/', function (RouteBuilder $routes) { - $routes->setExtensions(['json']); - $routes->resources('Recipes'); - }); - -La primera línea configura una serie de rutas predeterminadas para el acceso REST -donde el método especifica el formato de resultado deseado, por ejemplo, xml, json -y rss. Estas rutas son sensible al método de solicitud HTTP. - -=========== ===================== ================================ -HTTP format URL.format Acción del controlador invocada -=========== ===================== ================================ -GET /recipes.format RecipesController::index() ------------ --------------------- -------------------------------- -GET /recipes/123.format RecipesController::view(123) ------------ --------------------- -------------------------------- -POST /recipes.format RecipesController::add() ------------ --------------------- -------------------------------- -PUT /recipes/123.format RecipesController::edit(123) ------------ --------------------- -------------------------------- -PATCH /recipes/123.format RecipesController::edit(123) ------------ --------------------- -------------------------------- -DELETE /recipes/123.format RecipesController::delete(123) -=========== ===================== ================================ - -.. note:: - - El patrón predeterminado para los ID de recursos sólo coincide con números - enterors o UUID. Si tus ID son diferentes, tendrás que proporcionar un - patrón de expresión regular a través de la opción ``id``, por ejemplo - ``$builder->resources('Recipes', ['id' => '.*'])``. - -El método HTTP utilizado se detecta desde algunas fuentes diferentes. -Las fuentes en orden de preferencia son: - -#. La variable POST ``_method`` -#. El encabezado The ``X_HTTP_METHOD_OVERRIDE``. -#. El encabezado ``REQUEST_METHOD`` - -La variable POST ``_method`` es útil para utilizar un navegador como -cliente REST (o cualquier otra cosa que pueda realizar POST). -Simplemente, establece el valor de ``_method()`` con el nombre del -método de la solicitud HTTP que deseas emular. - -Crear Rutas de Recursos Anidadas --------------------------------- - -Una vez hayas conectado recursos en un alcance, también puedes conectar rutas para -subrecursos. Las rutas de subrecursos estarán precedidas por el nombre del recurso -original y un parámetro de identificación. Por ejemplo:: - - $routes->scope('/api', function (RouteBuilder $routes) { - $routes->resources('Articles', function (RouteBuilder $routes) { - $routes->resources('Comments'); - }); - }); - -Generará rutas de recursos tanto para ``articles`` como para ``comments``. -Las rutas de comments se verán así:: - - /api/articles/{article_id}/comments - /api/articles/{article_id}/comments/{id} - -Puedes obtener el ``article_id`` en ``CommentsController`` mediante:: - - $this->request->getParam('article_id'); - -De forma predeterminada, las rutas de recursos se asignan al mismo prefijo que el -ámbito contenedor. Si tienes controladores de recursos anidados y no anidados, puedes -utilizar un controlador diferente en cada contexto mediante el uso de prefijos:: - - $routes->scope('/api', function (RouteBuilder $routes) { - $routes->resources('Articles', function (RouteBuilder $routes) { - $routes->resources('Comments', ['prefix' => 'Articles']); - }); - }); - -Lo anterior asignará el recurso 'Comments' a -``App\Controller\Articles\CommentsController``. Tener controladores separados -te permite mantener la lógica del controlador más simple. Los prefijos creados de -esta manera son compatibles con :ref:`prefix-routing`. - -.. note:: - - Si bien puedes anidar recursos con la profundidas que necesites, no se recomienda - anidar más de dos recursos juntos. - -Limitar las Rutas Creadas -------------------------- - -Por defecto, CakePHP conectará 6 rutas para cada recurso. Si deseas conectar -sólo rutas de recursos específicas podrás utilizar la opción ``only``:: - - $routes->resources('Articles', [ - 'only' => ['index', 'view'] - ]); - -Crearía rutas de recurso de sólo lectura. Los nombres de las rutas son -``create``, ``update``, ``view``, ``index``, and ``delete``. - -El **nombre de ruta y acción del controlador utilizados** predeterminados son -los siguientes: - -============== ================================= -Nombre de ruta Acción del controlador utilizada -============== ================================= -create add --------------- --------------------------------- -update edit --------------- --------------------------------- -view view --------------- --------------------------------- -index index --------------- --------------------------------- -delete delete -============== ================================= - - -Cambiar las Acciones del Controlador Utilizadas ------------------------------------------------ - -Es posible que debas cambiar los nombres de las acciones del controlador que se -utilizan al conectar rutas. Por ejemplo, si tu acción ``edit()`` se llama ``put()`` -puedes utilizar la clave ``actions`` para renombrar las acciones utilizadas:: - - $routes->resources('Articles', [ - 'actions' => ['update' => 'put', 'create' => 'add'] - ]); - -Lo anterior utilizaría ``put()`` para la acción ``edit()`` y ``add()`` -en lugar de ``create()``. - -Mapeo de Rutas de Recursos Adicionales --------------------------------------- - -Puedes asignar métodos de recursos adicionales utilizando la opción ``map``:: - - $routes->resources('Articles', [ - 'map' => [ - 'deleteAll' => [ - 'action' => 'deleteAll', - 'method' => 'DELETE' - ] - ] - ]); - // Esto conectaría /articles/deleteAll - -Además de las rutas predeterminadas, esto también conectaría una ruta para -`/articles/delete-all`. De forma predeterminada, el segmento de ruta coincidirá -con el nombre de la clave. Puedes utilizar la clave 'path' dentro de la defición -del recurso para personalizar el nombre de la ruta:: - - $routes->resources('Articles', [ - 'map' => [ - 'updateAll' => [ - 'action' => 'updateAll', - 'method' => 'PUT', - 'path' => '/update-many', - ], - ], - ]); - // Esto conectaría /articles/update-many - -Si defines 'only' and 'map', asegúrate de que tus métodos asignados también están -en la lista 'only'. - -Enrutamiento de Recursos Prefijados ------------------------------------ - -[[Continuar]] -Las rutas de recursos pueden conectarse a los controladores en prefijos de -enrutamiento conectando rutas en un ámbito prefijado or utilizando la opción ``prefix``:: - - $routes->resources('Articles', [ - 'prefix' => 'Api', - ]); - -.. _custom-rest-routing: - -Clases de Ruta Personalizada para Rutas de Recursos ---------------------------------------------------- - -Puedes proporcionar la clave ``coneectOptions`` en la matriz ``$options`` para -``resources()`` para proporcionar la configuración personalizada utilizada por -``connect()``:: - - $routes->scope('/', function (RouteBuilder $routes) { - $routes->resources('Books', [ - 'connectOptions' => [ - 'routeClass' => 'ApiRoute', - ] - ]; - }); - - -Inflexión de URL para Rutas de Recursos ---------------------------------------- - -De forma predeterminada, los fragmentos de URL de los controladores con varias -palabras están en la forma con guiones del nombre del controlador. Por ejemplo, -el fragmento de URL de ``BlogPostsController`` sería **/blog-posts**. - -Puedes especificar un tipo de inflexión alternativo utilizando la opción ``inflect``:: - - $routes->scope('/', function (RouteBuilder $routes) { - $routes->resources('BlogPosts', [ - 'inflect' => 'underscore' // Will use ``Inflector::underscore()`` - ]); - }); - -Lo anterior generará una URL del tipo: **/blog_posts**. - -Cambiar el Elemento de Ruta ---------------------------- - -De forma predeterminada, las rutas de recursos utilizan una forma inflexionada -del nombre del recurso para el segmento de URL. Puedes configurar un segmento -de ruta personalizado con la opción ``path``:: - - $routes->scope('/', function (RouteBuilder $routes) { - $routes->resources('BlogPosts', ['path' => 'posts']); - }); - -.. index:: passed arguments -.. _passed-arguments: - -Argumentos Pasados -================== - -Los argumentos pasados son argumentos adicionales or segmentos de ruta -que se utilizan al realizar una solicitud. A menudo se utilizan para -pasar parámetros a los métodos de tu controlador:: - - http://localhost/calendars/view/recent/mark - -En el ejemplo anterior, tanto ``recent`` como ``mark`` se pasan como argumentos a -``CalendarsController::view()``. Los argumentos pasados se entregan a tus -controladores de tres maneras. En primer lugar, como argumentos para el método de -acción llamado y, en segundo lugar, están disponibles en -``$this->request->getParams('pass')`` como una matriz indexada numéricamente. Al -utilizar rutas personalizadas, también puedes forzar que parámetros particulares -entren en los argumentos pasados. - -Si visitara la URL mencionada anteriormente, y tuviera una acción de controlador -similar a:: - - class CalendarsController extends AppController - { - public function view($arg1, $arg2) - { - debug(func_get_args()); - } - } - -Otendrías el siguiente resultado:: - - Array - ( - [0] => recent - [1] => mark - ) - -Estos mismos datos también están disponibles en ``$this->request->getParam('pass')`` -en tus controladores, vistas y helpers. Los valores en la matriz de paso están -indexados numéricamente según el orden en el que aparecen en la URL llamada:: - - debug($this->request->getParam('pass')); - -Cualquiera de los anteriores generaría:: - - Array - ( - [0] => recent - [1] => mark - ) - -Al generar URL, utilizando un :term:`arreglo de enrutamiento` agregas argumentos -pasados como valores sin claves de cadena en la matriz:: - - ['controller' => 'Articles', 'action' => 'view', 5] - -Dado que ``5`` es una clave numérica, se trata como un argumento pasado. - -Generando URL -============= - -.. php:staticmethod:: url($url = null, $full = false) -.. php:staticmethod:: reverse($params, $full = false) - -Generar URL o enrutamiento inverso es una característica de CakePHP que se -utiliza para permitirte cambiar la estructura de tu URL sin tener que modificar -todo tu código. - -Si creas URL utilizando cadenas como:: - - $this->Html->link('View', '/articles/view/' . $id); - -Y luego decides que ``/articles`` realmente debería llamarse ``posts``, -tendría que ir por toda tu aplicación renombrando las URL. Sin embargo, -si definiste tu enlace como:: - - //`link()` utiliza internamente Router::url() y acepta una matriz de enrutamiento - - $this->Html->link( - 'View', - ['controller' => 'Articles', 'action' => 'view', $id] - ); - -o:: - - //'Router::reverse()' opera en la matriz de parámetos de la petición - //y producirá una cadena de url válida para `link()` - - $requestParams = Router::getRequest()->getAttribute('params'); - $this->Html->link('View', Router::reverse($requestParams)); - -Luego, cuando decidieras cambiar tus URL, podrías hacerlo definiendo una ruta. -Esto cambiaría tanto la asignación de URL entrante como las URL generadas. - -La elección de la técnica está determinada por qué tan bien se pueden predecir -los elementos de la matriz de enrutamiento. - -Utilizando ``Router::url()`` ----------------------------- - -``Router::url()`` te permite utilizar :term:`routing arrays ` -en situaciones donde los elementos requeridos de la matriz son fijos o se deducen -fácilmente. - -Proporcionará enrutamiento inverso cuando la url de destino esté bien definida:: - - $this->Html->link( - 'View', - ['controller' => 'Articles', 'action' => 'view', $id] - ); - -También es útil cuando el destino es desconocido, pero sigue un patrón bien definido:: - - $this->Html->link( - 'View', - ['controller' => $controller, 'action' => 'view', $id] - ); - -Los elementos con claves numéricas se tratan como :ref:`passed-arguments`. - -Al utilizar matrices de enrutamiento, puedes definir tanto los parámetros de -la cadena de consulta como los fragmentos de documentos utilizando claves -especiales:: - - $routes->url([ - 'controller' => 'Articles', - 'action' => 'index', - '?' => ['page' => 1], - '#' => 'top' - ]); - - // Generará una URL del tipo. - /articles/index?page=1#top - -También puedes utilizar cualquiera de los elementos de ruta especiales al generar URL: - -* ``_ext`` Se utiliza para enrutamiento de :ref:`file-extensions`. -* ``_base`` Establecer en ``false`` para eliminar la ruta base de la URL generada. - Si tu aplicación no está en el directorio raíz, esto se puede utilizar para generar - URL relativas a cake. -* ``_scheme`` Configurado para crear enlaces en diferentes esquemas como ``webcal`` o - ``ftp``. El valor predeterminado es el esquema actual. -* ``_host`` Establece el host que se utilizará en el enlace. El valor por defecto es el - del host actual. -* ``_port`` Establece el puerto si necesitas crear enlaces a puestos no estándar. -* ``_method`` Define el verbo HTTP para el que es la URL. -* ``_full`` Si es ``true`` el valor de ``App.fullBaseUrl`` mencionado en - :ref:`general-configuration` se antepondrá a las URL generadas. -* ``_https`` Establecer en ``true`` para convertir la URL generada a https o ``false`` - para forzar http. -* ``_name`` Nombre de la ruta. Si has configurado rutas con nombre, puedes utilizar esta - clave para especificarlas. - -Utilizando ``Router::reverse()`` --------------------------------- - -``Router::reverse()`` te permite utilizar los :ref:`request-parameters` en casos -donde la URL actual con algunas modificaciones es la base para el destino y los -elementos de la URL actual son impredecibles. - -Como ejemplo, imagina un blog que permite a los usuarios crear **Articles** y -**Comments**, y marcar ambos como *published* o *draft*. Ambas URL de la página index -pueden incluir el ID del usuario. La URL de **Comments** también puede incluir el -ID de un **Article** para identificar a qué artículo se refieren los comentarios. - -Aquí están las url para este escenario:: - - /articles/index/42 - /comments/index/42/18 - -Cuando el autor utilice estas páginas, sería conveniente incluir enlaces que -permitan mostrar la página con todos los resultados, sólo publicados o sólo -borrador. - -Para mantener el código DRY, sería mejor incluir los enlaces a través de un -elemento:: - - // element/filter_published.php - - $params = $this->getRequest()->getAttribute('params'); - - /* preparar la url para Borrador */ - $params = Hash::insert($params, '?.published', 0); - echo $this->Html->link(__('Draft'), Router::reverse($params)); - - /* Preparar la url para Publicados */ - $params = Hash::insert($params, '?.published', 1); - echo $this->Html->link(__('Published'), Router::reverse($params)); - - /* Preparar la url para Todos */ - $params = Hash::remove($params, '?.published'); - echo $this->Html->link(__('All'), Router::reverse($params)); - -Los enlaces generados por estas llamadas incluirían uno o dos parámetros -de paso dependiendo de la estructura de la URL actual. Y el código -funcionaría para cualquier URL futura, por ejemplo, si comenzara a usar -prefijos de ruta o si agregara más parámetros del paso. - -Matrices de Enrutamiento vs Parámetros de Solicitud ---------------------------------------------------- - -La diferencia significativa entre las dos matrices y su uso en estos métodos -de enrutamiento inverso está la forma en la que incluyen los parámetros -de paso. - -Las matrices de enrutamiento incluyen los parámetros de paso como valores -sin clave en la matriz:: - - $url = [ - 'controller' => 'Articles', - 'action' => 'View', - $id, //un parámetro de paso - 'page' => 3, //un argumento de consulta - ]; - -Los parámetros de consulta incluyen parámtros de paso en la clave 'pass' -de la matriz:: - - $url = [ - 'controller' => 'Articles', - 'action' => 'View', - 'pass' => [$id], //los parámetros de paso - '?' => ['page' => 3], //los parámtros de consulta - ]; - -Por lo tanto, si los deseas, es posible convertir los parámetros de solicitud -en una matriz de enrutamiento o viceversa. - -.. _asset-routing: - -Generando URL de Activos -======================== - -La clase ``Asset`` proporciona métodos para generar URL para los archivos css, -javascript, imágenes y otros archivos de activos estáticos de tu aplicación:: - - use Cake\Routing\Asset; - - // Generar una URL para APP/webroot/js/app.js - $js = Asset::scriptUrl('app.js'); - - // Generar una URL para APP/webroot/css/app.css - $css = Asset::cssUrl('app.css'); - - // Generar una URL para APP/webroot/image/logo.png - $img = Asset::imageUrl('logo.png'); - - // Generar una URL para APP/webroot/files/upload/photo.png - $file = Asset::url('files/upload/photo.png'); - -Los métodos anteriores también aceptan una matriz de opciones como segundo parámetro: - -* ``fullBase`` Agrega la URL completa con el nombre de dominio. -* ``pathPrefix`` Prefijo de ruta para URL relativas. -* ``plugin`` Puedes añdirlo como ``false`` para evitar que las rutas se traten como - un recurso de un plugin. -* ``timestamp`` Sobrescribe el valor de ``Asset.timestamp`` en Configure. Establecer - a ``false`` para omitir la generación de la marca de tiempo. Establecer a ``true`` - para aplicar marcas de tiempo cuando el debug is true. Set to ``'force'`` para habilitar - siempre la marca de tiempo independientemente del valor de debug. - -:: - - // Genera http://example.org/img/logo.png - $img = Asset::url('logo.png', ['fullBase' => true]); - - // Genera /img/logo.png?1568563625 - // Donde la marca de tiempo es la ultima hora de modificación del archivo - $img = Asset::url('logo.png', ['timestamp' => true]); - -Para generar URL de activo para archivos en un plugin utiliza -:term:`Sintaxis de plugin`:: - - // Genera `/debug_kit/img/cake.png` - $img = Asset::imageUrl('DebugKit.cake.png'); - -.. _redirect-routing: - -Redirección de Enrutamiento -=========================== - -La redirección de enrutamiento te permite emitir redirección de estado -HTTP 30x para rutas entrantes y apuntarlas a URL diferentes. Esto es útil -cuando deseas informar a las aplicaciones cliente que un recurso se ha -movido y no deseas exponer dos URL para el mismo contenido. - -Las rutas de redireccionamiento son diferentes de las rutas normales en que -realizan una redirección de encabezado real si se encuentra una coincidencia. -La redirección puede ocurrir a un destino dentro de tu aplicación o a una -ubicación externa:: - - $routes->scope('/', function (RouteBuilder $routes) { - $routes->redirect( - '/home/*', - ['controller' => 'Articles', 'action' => 'view'], - ['persist' => true] - // O ['persist'=>['id']] para el enrutamiento predeterminado - // donde la acción view espera $id como argumento. - ); - }) - -Redirige ``/home/*`` a ``/articles/view`` y pasa los parámetros a -``/articles/view``. El uso de una matriz como destino de -redireccionamiento te permite utilizar otras rutas para definir -hacia dónde se debe dirigir una cadena de URL. Puedes redirigir a -ubicaciones externas utilizando una cadena de URL como destino:: - - $routes->scope('/', function (RouteBuilder $routes) { - $routes->redirect('/articles/*', 'http://google.com', ['status' => 302]); - }); - -Esto redirigiría ``/articles/*`` a ``http://google.com`` con un estado -HTTP de 302. - -.. _entity-routing: - -Enrutamiento de Entidades -========================= - -El enrutamiento de entidades te permite utilizar una entidad, una matriz o un objeto -que implemente ``ArrayAccess`` como una fuente de parámetros de enrutamiento. Esto -te permite refactorizar rutas fácilmente y generar URL con menos código. Por ejemplo, -si comienzas con una ruta similar a:: - - $routes->get( - '/view/{id}', - ['controller' => 'Articles', 'action' => 'view'], - 'articles:view' - ); - -Puedes generar URL para esta ruta utilizando:: - - // $article es una entidad en el ámbito local. - Router::url(['_name' => 'articles:view', 'id' => $article->id]); - -Más adelante, es posible que desees exponer el slug del artículo en la URL con -fines de SEO. Para hacer esto necesitarás actualizar todos los lugares donde -generes una URL a la ruta ``articles:view``, lo que podría llevar algún tiempo. -Si utilizamos rutas de entidad, pasamos toda la entidad del artículo a la -generación de URL, lo que nos permite omitir cualquier reelaboración cuando las -URL requiren más parámetros:: - - use Cake\Routing\Route\EntityRoute; - - // Crea más rutas de entidad para el resto de este ámbito. - $routes->setRouteClass(EntityRoute::class); - - // Crea la ruta como antes. - $routes->get( - '/view/{id}/{slug}', - ['controller' => 'Articles', 'action' => 'view'], - 'articles:view' - ); - -Ahora podemos generar URL utilizando la clave ``_entity``:: - - Router::url(['_name' => 'articles:view', '_entity' => $article]); - -Esto extraerá las propiedades ``id`` y ``slug`` de la entidad proporcionada. - -.. _custom-route-classes: - -Clases de Ruta Personalizadas -============================= - -Las clases de ruta personalizadas te permiten ampliar y cambiar la forma en que -las rutas individuales analizan las solicitudes y manejan el enrutamiento inverso. -Las clases de ruta tienen algunas convenciones: - -* Se espera que las clases de ruta se encuentren en el espacio de nombres ``Routing\Route`` de tu aplicación o plugin. -* Las clases de ruta deben extender :php:class:`Cake\\Routing\\Route\\Route`. -* Las clases de ruta deben implementar uno o ambos ``match()`` y/o ``parse()``. - -El método ``parse()`` se utiliza para analizar una URL entrante. Debería generar -una matriz de parámetros de solicitud que pueda resolverse en un controlador -y acción. Este método devuelve ``null`` para indicar un error de coincidencia. - -El método ``match()`` se utiliza para hacer coincidir una matriz de parámetros -de URL y crear una cadena de URL. Si los parámertos de la URL no coinciden, se -debe devolver la ruta ``false``. - -Puedes utilizar una clase de ruta personalizada al realizar una ruta utilizando -la opción ``routeClass``:: - - $routes->connect( - '/{slug}', - ['controller' => 'Articles', 'action' => 'view'], - ['routeClass' => 'SlugRoute'] - ); - - // O configurando routeClass en su ámbito. - $routes->scope('/', function (RouteBuilder $routes) { - $routes->setRouteClass('SlugRoute'); - $routes->connect( - '/{slug}', - ['controller' => 'Articles', 'action' => 'view'] - ); - }); - -Esta ruta crearía una instancia de ``SlugRoute`` y te permitiría implementar -un manejo de parámetros personalizado. Puedes utilizar clases de ruta de plugin -utilizando el estándar :term:`Sintaxis de plugin`. - -Clase de Ruta Predeterminada ----------------------------- - -.. php:staticmethod:: setRouteClass($routeClass = null) - -Si desea utilizar una ruta de clase alternativa para tus rutas además de la -``Ruta`` predeterminada, puedes hacerlo llamando a ``RouterBuilder::setRouteClass()`` -antes de configurar cualquier ruta y evitar tener que especificar la opción -``routeClass`` para cada ruta. Por ejemplo utilizando:: - - use Cake\Routing\Route\DashedRoute; - - $routes->setRouteClass(DashedRoute::class); - -Hará que todas las rutas conectadas después de esto utilicen la clase de ruta -``DashedRoute``. Llamando al método sin un argumento devolverá la clase de ruta -predeterminada actual. - -Método de Respaldo/Alternativas -------------------------------- - -.. php:method:: fallbacks($routeClass = null) - -El método de respaldo es un atajo simple para definir rutas predeterminadas. -El método utiliza la clase de enrutamiento pasada para las reglas definidas o, -si no se proporciona ninguna clase, se utiliza la clase devuelta por -``RouterBuilder::setRouteClass()``. - -Llamar a alternativas así:: - - use Cake\Routing\Route\DashedRoute; - - $routes->fallbacks(DashedRoute::class); - -Es equivalente a las siguientes llamadas explícitas:: - - use Cake\Routing\Route\DashedRoute; - - $routes->connect('/{controller}', ['action' => 'index'], ['routeClass' => DashedRoute::class]); - $routes->connect('/{controller}/{action}/*', [], ['routeClass' => DashedRoute::class]); - -.. note:: - - El uso de la clase de ruta predeterminada (``Route``) con alternativas, - or cualquier ruta con elemento de ruta ``{plugin}`` o ``{controller}`` - dará como resultado una URL inconsistente. - -.. warning:: - Las plantillas de ruta alternativas son muy genéricas y permites generar y - analizar URL para controladore sy acciones que no existen. Las URL - alternativas también pueden introducir ambigüedad y duplicidad en tus URL. - - A medida que tu aplicaicón crece, se recomienda alejarse de las URL alternativas - y definir explícitamente las rutas en tu aplicación. - -Crear Parámetros de URL Persistentes -==================================== - -Puedes conectarte al proceso de generación de URL utilizando funciones de filtro -de URL. Las funciones de filtro se llaman *antes* de que las URL coincidan con las -rutas, esto te permite preparar las URL antes de enrutarlas. - -La devolución de llamada de las funciones de filtro deben contar con los siguientes -parámetos: - -- ``$params`` La matriz de parámetros de URL que se está procensando. -- ``$request`` La petición actual (instancia de ``Cake\Http\ServerRequest``). - -La función de filtro de URL *siempre* debería devolver los parámetros incluso si -no están modificados. - -Los filtros de URL te permiten implementar funciones como parámetros persistentes:: - - Router::addUrlFilter(function (array $params, ServerRequest $request) { - if ($request->getParam('lang') && !isset($params['lang'])) { - $params['lang'] = $request->getParam('lang'); - } - - return $params; - }); - -Las funciones de filtro se aplican en el orden en que están conectadas. - -Otro caso de uso es cambiar una determinada ruta en tiempo de ejecución -(por ejemplo, rutas de plugin):: - - Router::addUrlFilter(function (array $params, ServerRequest $request) { - if (empty($params['plugin']) || $params['plugin'] !== 'MyPlugin' || empty($params['controller'])) { - return $params; - } - if ($params['controller'] === 'Languages' && $params['action'] === 'view') { - $params['controller'] = 'Locations'; - $params['action'] = 'index'; - $params['language'] = $params[0]; - unset($params[0]); - } - - return $params; - }); - -Esto alterará la siguiente ruta:: - - Router::url(['plugin' => 'MyPlugin', 'controller' => 'Languages', 'action' => 'view', 'es']); - -En esto:: - - Router::url(['plugin' => 'MyPlugin', 'controller' => 'Locations', 'action' => 'index', 'language' => 'es']); - -.. warning:: - Si estás utilizando las funcionees de almacenamiento de caché de - :ref:`routing-middleware` debes definir los filtros de URL en tu aplicación - ``bootstrap()`` ya que los filtros no son parte de los datos almacenados en - caché. - -.. meta:: - :title lang=es: Enrutamiento - :keywords lang=es: controller actions,default routes,mod rewrite,code index,string url,php class,incoming requests,dispatcher,url url,meth,maps,match,parameters,array,config,cakephp,apache,router diff --git a/es/development/sessions.rst b/es/development/sessions.rst deleted file mode 100644 index 5f00b789e8..0000000000 --- a/es/development/sessions.rst +++ /dev/null @@ -1,393 +0,0 @@ -Sesiones -########### - -CakePHP proporciona una envoltura y una serie de funciones de utilidad sobre la extensión nativa de sesión de PHP. Las sesiones te permiten identificar usuarios únicos a lo largo de las solicitudes y almacenar datos persistentes para usuarios específicos. A diferencia de las cookies, los datos de sesión no están disponibles en el lado del cliente. En CakePHP, se evita el uso de `$_SESSION` y se prefiere el uso de las clases de Sesión. - -.. _session-configuration: - -Configuración de Sesión -========================= - -La configuración de sesión generalmente se define en **/config/app.php**. Las opciones disponibles son: - -* ``Session.timeout`` - El número de *minutos* antes de que el manejador de sesiones de CakePHP expire la sesión. -* ``Session.defaults`` - Te permite usar las configuraciones de sesión predeterminadas incorporadas como base para tu configuración de sesión. Consulta a continuación para ver las configuraciones predeterminadas. -* ``Session.handler`` - Te permite definir un manejador de sesiones personalizado. Los manejadores de sesiones de base de datos y caché utilizan esto. Consulta a continuación para obtener información adicional sobre los manejadores de sesiones. -* ``Session.ini`` - Te permite establecer configuraciones adicionales de ini de sesión para tu configuración. Esto, combinado con ``Session.handler``, reemplaza las características de manejo de sesiones personalizadas de las versiones anteriores. -* ``Session.cookie`` - El nombre de la cookie que se utilizará. De forma predeterminada, se establece en el valor configurado para ``session.name`` en php.ini. -* ``Session.cookiePath`` - La ruta URL para la cual se establece la cookie de sesión. Se mapea a la configuración ``session.cookie_path`` de php.ini. De forma predeterminada, se establece en la ruta base de la aplicación. - -Las configuraciones predeterminadas de CakePHP establecen ``session.cookie_secure`` en ``true``, cuando tu aplicación -está en un protocolo SSL. Si tu aplicación se sirve tanto desde protocolos SSL como no SSL, podrías tener problemas -con las sesiones que se pierden. Si necesitas acceder a la sesión en dominios SSL y no SSL, deberás deshabilitar esto:: - - Configure::write('Session', [ - 'defaults' => 'php', - 'ini' => [ - 'session.cookie_secure' => false - ] - ]); - -A partir de la versión 4.0 de CakePHP, también se establece el atributo `SameSite `__ en ``Lax`` de forma predeterminada para las cookies de sesión, lo que ayuda a proteger contra ataques CSRF. Puedes cambiar el valor predeterminado configurando la opción ``session.cookie_samesite`` en php.ini:: - - Configure::write('Session', [ - 'defaults' => 'php', - 'ini' => [ - 'session.cookie_samesite' => 'Strict', - ], - ]); - - -La ruta de la cookie de sesión se establece de forma predeterminada en la ruta base de la aplicación. Para cambiar esto, puedes usar el valor de ``session.cookie_path`` en ini. Por ejemplo, si quieres que tu sesión persista en todos los subdominios, puedes hacerlo así:: - - Configure::write('Session', [ - 'defaults' => 'php', - 'ini' => [ - 'session.cookie_path' => '/', - 'session.cookie_domain' => '.tudominio.com', - ], - ]); - -De forma predeterminada, PHP configura la cookie de sesión para que caduque tan pronto como se cierre el navegador, independientemente del valor configurado en ``Session.timeout``. El tiempo de espera de la cookie está controlado por el valor de ``session.cookie_lifetime`` en ini y se puede configurar así:: - - Configure::write('Session', [ - 'defaults' => 'php', - 'ini' => [ - // Invalidar la cookie después de 30 minutos sin visitar - // ninguna página en el sitio. - 'session.cookie_lifetime' => 1800 - ] - ]); - -La diferencia entre ``Session.timeout`` y el valor de ``session.cookie_lifetime`` es que este último depende de que el cliente diga la verdad acerca de la cookie. Si necesitas una comprobación de tiempo de espera más estricta, sin depender de lo que el cliente informa, deberías usar ``Session.timeout``. - -Ten en cuenta que ``Session.timeout`` corresponde al tiempo total de inactividad para un usuario (es decir, el tiempo sin visitar ninguna página donde se utilice la sesión), y no limita el total de minutos que un usuario puede permanecer en el sitio. - -Manejadores de Sesiones Incorporados y Configuración -===================================================== - -CakePHP viene con varias configuraciones de sesión incorporadas. Puedes usar estas configuraciones como base para tu configuración de sesión o crear una solución completamente personalizada. Para usar las configuraciones predeterminadas, simplemente configura la clave 'defaults' con el nombre del predeterminado que deseas utilizar. Luego puedes anular cualquier configuración secundaria declarándola en tu configuración de Sesión:: - - Configure::write('Session', [ - 'defaults' => 'php' - ]); - -Lo anterior usará la configuración de sesión 'php' incorporada. Puedes agregar partes o la totalidad de ella haciendo lo siguiente:: - - Configure::write('Session', [ - 'defaults' => 'php', - 'cookie' => 'mi_app', - 'timeout' => 4320 // 3 días - ]); - -Lo anterior anula el tiempo de espera y el nombre de la cookie para la configuración de sesión 'php'. Las configuraciones incorporadas son: - -* ``php`` - Guarda sesiones con las configuraciones estándar en tu archivo php.ini. -* ``cake`` - Guarda sesiones como archivos dentro de ``tmp/sessions``. Esta es una buena opción cuando estás en hosts que no te permiten escribir fuera de tu propio directorio de inicio. -* ``database`` - Utiliza las sesiones de base de datos incorporadas. Consulta a continuación para obtener más información. -* ``cache`` - Utiliza las sesiones de caché incorporadas. Consulta a continuación para obtener más información. - -Manejadores de Sesiones ---------------------------- - -Los manejadores de sesiones también se pueden definir en el array de configuración de la sesión. Al definir la clave de configuración 'handler.engine', puedes nombrar la clase o proporcionar una instancia del manejador. La clase/objeto debe implementar la interfaz nativa de PHP ``SessionHandlerInterface``. Implementar esta interfaz permitirá que ``Session`` mapee automáticamente los métodos para el manejador. Tanto los manejadores de sesiones de base de datos como de caché utilizan este método para guardar sesiones. Las configuraciones adicionales - -Para el manejador deben colocarse dentro del array del manejador. Luego puedes leer esos valores desde dentro de tu manejador:: - - 'Session' => [ - 'handler' => [ - 'engine' => 'DatabaseSession', - 'model' => 'SesionesPersonalizadas', - ], - ] - -Lo anterior muestra cómo podrías configurar el manejador de sesiones de base de datos con un modelo de aplicación. Al utilizar -nombres de clases como tu 'handler.engine', CakePHP esperará encontrar tu clase en el espacio de nombres ``Http\Session``. -Por ejemplo, si tenías una clase ``AppSessionHandler``, el archivo debería estar en **src/Http/Session/AppSessionHandler.php**, -y el nombre de la clase debería ser ``App\Http\Session\AppSessionHandler``. También puedes usar manejadores de sesiones desde -dentro de plugins, estableciendo el motor en ``MyPlugin.PluginSessionHandler``. - -Sesiones de Base de Datos -------------------------------- - -Si necesitas usar una base de datos para almacenar los datos de tu sesión, configúralo de la siguiente manera:: - - 'Session' => [ - 'defaults' => 'database' - ] - -Esta configuración requiere una tabla de base de datos con este esquema:: - - CREATE TABLE `sessions` ( - `id` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL, - `created` datetime DEFAULT CURRENT_TIMESTAMP, -- Opcional - `modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- Opcional - `data` blob DEFAULT NULL, -- para PostgreSQL, usa bytea en lugar de blob - `expires` int(10) unsigned DEFAULT NULL, - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8; - -Puedes encontrar una copia del esquema para la tabla de sesiones en el `esqueleto de la aplicación `_ en **config/schema/sessions.sql**. - -También puedes usar tu propia clase de ``Table`` para manejar el guardado de las sesiones:: - - 'Session' => [ - 'defaults' => 'database', - 'handler' => [ - 'engine' => 'DatabaseSession', - 'model' => 'SesionesPersonalizadas', - ], - ] - -Lo anterior le dirá a Session que use las configuraciones predeterminadas de 'database' y especifica que una tabla llamada ``SesionesPersonalizadas`` será la encargada de guardar la información de la sesión en la base de datos. - -.. _sessions-cache-sessions: - -Sesiones de Caché ------------------- - -La clase Cache se puede utilizar para almacenar sesiones también. Esto te permite almacenar sesiones en una caché como APCu o Memcached. Hay algunas advertencias al usar sesiones en caché, ya que si agotas el espacio de la caché, las sesiones comenzarán a caducar a medida que se eliminan registros. - -Para usar sesiones basadas en caché, puedes configurar tu configuración de Sesión así:: - - Configure::write('Session', [ - 'defaults' => 'cache', - 'handler' => [ - 'config' => 'session', - ], - ]); - -Esto configurará Session para usar la clase ``CacheSession`` como el delegado para guardar las sesiones. Puedes usar la clave 'config' para especificar qué configuración de caché usar. La configuración de caché predeterminada es ``'default'``. - -Bloqueo de Sesiones --------------------- - -El esqueleto de la aplicación viene preconfigurado con una configuración de sesión como esta:: - - 'Session' => [ - 'defaults' => 'php', - ], - -Esto significa que CakePHP manejará las sesiones según lo que esté configurado en tu ``php.ini``. -En la mayoría de los casos, esta será la configuración predeterminada, por lo que PHP guardará -cualquier sesión recién creada como un archivo en, por ejemplo, ``/var/lib/php/session``. - -Pero esto también significa que cualquier tarea computacionalmente intensiva, como consultar un gran -conjunto de datos combinado con una sesión activa, **bloqueará ese archivo de sesión**, lo que -bloqueará a los usuarios para, por ejemplo, abrir una segunda pestaña de tu aplicación para -hacer algo más mientras tanto. - -Para evitar este comportamiento, tendrás que cambiar la forma en que CakePHP maneja las sesiones -utilizando un manejador de sesiones diferente como :ref:`sessions-cache-sessions` combinado con -el :ref:`Motor Redis ` u otro motor de caché. - -.. tip:: - - Si deseas leer más sobre el Bloqueo de Sesiones, consulta `aquí `_. - -Configuración de Directivas de ini -==================================== - -Las configuraciones predeterminadas incorporadas intentan proporcionar una base común para la configuración de sesiones. -Es posible que necesites ajustar flags de ini específicos también. CakePHP expone la capacidad de personalizar las configuraciones -de ini tanto para las configuraciones predeterminadas como para las personalizadas. La clave ``ini`` en las configuraciones -de sesión te permite especificar valores de configuración individuales. Por ejemplo, puedes usarlo para controlar configuraciones como ``session.gc_divisor``:: - - Configure::write('Session', [ - 'defaults' => 'php', - 'ini' => [ - 'session.cookie_name' => 'MiCookie', - 'session.cookie_lifetime' => 1800, // Válido por 30 minutos - 'session.gc_divisor' => 1000, - 'session.cookie_httponly' => true - ] - ]); - -Creación de un Manejador de Sesiones Personalizado -=================================================== - -Crear un manejador de sesiones personalizado es sencillo en CakePHP. En este ejemplo, crearemos un -manejador de sesiones que almacene sesiones tanto en la Caché (APC) como en la base de datos. Esto -nos brinda lo mejor de ambas opciones: la entrada/salida rápida de APC, sin tener que preocuparnos -por las sesiones que desaparecen cuando la caché se llena. - -Primero necesitamos crear nuestra clase personalizada y ponerla en **src/Http/Session/ComboSession.php**. La clase debería verse algo así:: - - namespace App\Http\Session; - - use Cake\Cache\Cache; - use Cake\Core\Configure; - use Cake\Http\Session\DatabaseSession; - - class ComboSession extends DatabaseSession - { - protected $cacheKey; - - public function __construct() - { - $this->cacheKey = Configure::read('Session.handler.cache'); - parent::__construct(); - } - - // Lee datos de la sesión. - public function read($id): string - { - $result = Cache::read($id, $this->cacheKey); - if ($result) { - return $result; - } - - return parent::read($id); - - - } - - // Escribe datos en la sesión. - public function write($id, $data): bool - { - Cache::write($id, $data, $this->cacheKey); - - return parent::write($id, $data); - } - - // Destruye una sesión. - public function destroy($id): bool - { - Cache::delete($id, $this->cacheKey); - - return parent::destroy($id); - } - - // Elimina sesiones caducadas. - public function gc($expires = null): bool - { - return parent::gc($expires); - } - } - -Nuestra clase extiende el ``DatabaseSession`` incorporado para no tener que duplicar toda su lógica y comportamiento. Envolvemos -cada operación con una operación de :php:class:`Cake\\Cache\\Cache`. Esto nos permite obtener sesiones de la caché rápida y no -tener que preocuparnos por lo que sucede cuando llenamos la caché. En **config/app.php** haz que el bloque de sesión se vea así:: - - 'Session' => [ - 'defaults' => 'database', - 'handler' => [ - 'engine' => 'ComboSession', - 'model' => 'Session', - 'cache' => 'apc', - ], - ], - // Asegúrate de agregar una configuración de caché apc - 'Cache' => [ - 'apc' => ['engine' => 'Apc'] - ] - -Ahora nuestra aplicación comenzará a usar nuestro manejador de sesiones personalizado para leer y escribir datos de sesión. - -.. php:class:: Sesión - -.. _accessing-session-object: - -Acceso al Objeto de Sesión -=========================== - -Puedes acceder a los datos de sesión en cualquier lugar donde tengas acceso a un objeto de solicitud. Esto significa que la sesión es accesible desde: - -* Controladores -* Vistas -* Ayudantes (Helpers) -* Celdas (Cells) -* Componentes - -Un ejemplo básico de uso de sesión en controladores, vistas y celdas sería:: - - $nombre = $this->request->getSession()->read('Usuario.nombre'); - - // Si accedes a la sesión varias veces, - // probablemente querrás una variable local. - $sesion = $this->request->getSession(); - $nombre = $sesion->read('Usuario.nombre'); - -En los ayudantes, usa ``$this->getView()->getRequest()`` para obtener el objeto de solicitud; -en los componentes, usa ``$this->getController()->getRequest()``. - -Lectura y Escritura de Datos de Sesión -======================================= - -.. php:method:: read($clave, $predeterminado = null) - -Puedes leer valores de la sesión utilizando una sintaxis compatible con :php:meth:`Hash::extract()`. Ejemplo:: - - $sesion->read('Config.idioma', 'es'); - -.. php:method:: readOrFail($clave) - -Lo mismo que una envoltura de conveniencia alrededor de un valor de retorno no nulo:: - - $sesion->readOrFail('Config.idioma'); - -Esto es útil cuando sabes que esta clave debe estar configurada y no deseas tener que comprobar su existencia en el código mismo. - -.. php:method:: write($clave, $valor) - -``$clave`` debería ser la ruta separada por puntos a la que deseas escribir ``$valor``:: - - $sesion->write('Config.idioma', 'es'); - -También puedes especificar uno o varios hashes así:: - - $sesion->write([ - 'Config.tema' => 'azul', - 'Config.idioma' => 'es', - ]); - -.. php:method:: delete($clave) - -Cuando necesitas eliminar datos de la sesión, puedes usar ``delete()``:: - - $sesion->delete('Algo.valor'); - -.. php:staticmethod:: consume($clave) - -Cuando necesitas leer y eliminar datos de la sesión, puedes usar ``consume()``:: - - $sesion->consume('Algo.valor'); - -.. php:method:: check($clave) - -Si deseas ver si los datos existen en la sesión, puedes usar ``check()``:: - - if ($sesion->check('Config.idioma')) { - // Config.idioma existe y no es nulo. - } - -Destrucción de la Sesión -========================= - -.. php:method:: destroy() - -Destruir la sesión es útil cuando los usuarios cierran sesión. Para destruir una sesión, usa el método ``destroy()``:: - - $sesion->destroy(); - -Destruir una sesión eliminará todos los datos del lado del servidor en la sesión, pero **no** eliminará la cookie de la sesión. - -Rotación de Identificadores de Sesión -====================================== - -.. php:method:: renew() - -Mientras que el ``Plugin de Autenticación`` renueva automáticamente el ID de sesión cuando los usuarios inician sesión y cierran sesión, es posible que necesites rotar los ID de sesión manualmente. Para hacerlo, usa el método ``renew()``:: - - $sesion->renew(); - -Mensajes Flash -=============== - -Los mensajes flash son pequeños mensajes que se muestran a los usuarios una vez. A menudo se utilizan para presentar mensajes de error o confirmar que las acciones se realizaron con éxito. - -Para establecer y mostrar mensajes flash, debes usar el :doc:`Componente Flash ` y :doc:`Ayudante Flash `. - -.. meta:: - :title lang=es: Sesiones - :keywords lang=en: session defaults,session classes,utility features,session timeout,session ids,persistent data,session key,session cookie,session data,last session,core database,security level,useragent,security reasons,session id,attr,countdown,regeneration,sessions,config diff --git a/es/development/testing.rst b/es/development/testing.rst deleted file mode 100644 index b4c6a4991d..0000000000 --- a/es/development/testing.rst +++ /dev/null @@ -1,20 +0,0 @@ -Testing -####### - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. - -.. _running-tests: - -Running Tests -============= - -.. meta:: - :title lang=es: Testing - :keywords lang=es: web runner,phpunit,test database,database configuration,database setup,database test,public test,test framework,running one,test setup,de facto standard,pear,runners,array,databases,cakephp,php,integration diff --git a/es/elasticsearch.rst b/es/elasticsearch.rst deleted file mode 100644 index 78acd152fd..0000000000 --- a/es/elasticsearch.rst +++ /dev/null @@ -1,11 +0,0 @@ -ElasticSearch -############# - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. diff --git a/es/epub-contents.rst b/es/epub-contents.rst deleted file mode 100644 index 3d175d17ee..0000000000 --- a/es/epub-contents.rst +++ /dev/null @@ -1,64 +0,0 @@ -:orphan: - -Contenidos -########## - -.. toctree:: - :maxdepth: 3 - - intro - quickstart - appendices/migration-guides - tutorials-and-examples - contributing - - installation - development/configuration - development/routing - controllers/request-response - controllers - views - orm - - core-libraries/caching - bake - console-commands - development/debugging - deployment - core-libraries/email - development/errors - core-libraries/events - core-libraries/internationalization-and-localization - core-libraries/logging - core-libraries/form - controllers/pagination - plugins - development/rest - security - development/sessions - development/testing - core-libraries/validation - - core-libraries/app - core-libraries/collections - core-libraries/hash - core-libraries/httpclient - core-libraries/inflector - core-libraries/number - core-libraries/registry-objects - core-libraries/text - core-libraries/time - core-libraries/xml - - core-libraries/global-constants-and-functions - chronos - debug-kit - migrations - elasticsearch - appendices - -.. todolist:: - -.. meta:: - :title lang=es: Contenidos - :keywords lang=en: core libraries,ref search,commands,deployment,appendices,glossary,models diff --git a/es/index.rst b/es/index.rst deleted file mode 100644 index 3cc7a646a2..0000000000 --- a/es/index.rst +++ /dev/null @@ -1,52 +0,0 @@ -Bienvenido -########## - -CakePHP 5 es un ``framework`` de desarrollo web que funciona con PHP |phpversion| (min. PHP |minphpversion|). -Puedes leer :doc:`CakePHP de un Vistazo ` para introducirte en los fundamentos de CakePHP 3. - -El manual de CakePHP (``Cookbook``) es un proyecto de documentación abierto, -editable y mantenido por la comunidad. Fíjate en el icono del lápiz anclado -en el lado derecho de la página; te llevará al editor online de GitHub de la -página en la que estés permitiéndote contribuir con cualquier añadido, borrado -o corrección de la documentación. - -.. container:: offline-download - - **Lee el libro donde sea** - - .. image:: /_static/img/read-the-book.jpg - - Disfruta del manual de CakePHP en cualquier sitio. Disponible tanto en PDF - como en EPUB, puedes leerlo en más dispositivos y de manera offline. - - - `PDF <../_downloads/es/CakePHPBook.pdf>`_ - - `EPUB <../_downloads/es/CakePHP.epub>`_ - - `Original Source `_ - -Obtener ayuda -============= - -Si estás atascado hay varios lugares de :doc:`donde obtener ayuda -`. - -Primeros pasos -============== - -Aprender un nuevo framework puede ser intimidante y excitante al mismo tiempo. -Para ayudarte hemos creado un libro de recetas lleno de ejemplos y recetas -para completar las tareas habituales. Si eres nuevo deberías comenzar con la -:doc:`/guía de inicio rápido ` que te ofrecerá un tour por lo que ofrece CakePHP -y como funciona. - -Después de finalizar el tutorial Bookmarker (Favoritos), puedes conocer mejor los -elementos claves en una aplicación de CakePHP: - -* El :ref:`ciclo de vida de una petición CakePHP ` -* Las :doc:`convenciones ` que utiliza CakePHP. -* Los :doc:`controladores ` que manejan solicitudes y coordinan tus modelos y las respuestas que tu aplicación crea. -* Las :doc:`vistas ` de la capa de presentación de tu aplicación, que te dan herramientas poderosas para crear HTML, JSON y otras salidas que tu aplicación necesite. -* Los :doc:`modelos `, que son el ingrediente clave en cualquier aplicación. Manejan la validación y la lógica de dominio dentro de tu aplicación. - -.. meta:: - :title lang=es: .. CakePHP book archivo de documentación, creado por - :keywords lang=es: documento modelos,master documentación,capa presentación,proyecto documentación,guia de inicio rápido,fuente original,sphinx,liking,book,validez,convenciones,validación,cakephp,exactitud,almacenaje y recuperación,corazón,blog diff --git a/es/installation.rst b/es/installation.rst deleted file mode 100644 index 6a8cd489bc..0000000000 --- a/es/installation.rst +++ /dev/null @@ -1,572 +0,0 @@ -Instalación -########### - -CakePHP se instala rápida y fácilmente. ¡Los requisitos mínimos son -un servidor web y una copia de CakePHP, y ya! Aunque este manual se enfoca -principalmente en configurar Apache (ya que es el más utilizado), -puedes configurar CakePHP para que corra con una variedad de servidores web -como nginx, LightHTHPD o Microsoft IIS. - -Requisitos -========== - -- Servidor HTTP. Por ejemplo: Apache. mod\_rewrite es recomendado, pero - no requerido. -- PHP |minphpversion| o mayor. -- extensión mbstring. -- extensión intl. - -Técnicamente, una base de datos no es necesaria, pero imaginamos que la -mayoría de aplicaciones utiliza alguna. CakePHP soporta una gran variedad -de sistemas de bases de datos: - -- MySQL (5.1.10 o mayor). -- PostgreSQL. -- Microsoft SQL Server (2008 o mayor). -- SQLite 3. - -.. note:: - - Todos los drivers nativos necesitan PDO. Debes asegurarte de tener - las extensiones de PDO correctas. - -Licencia -======== - -CakePHP está licenciado bajo la -`Licencia MIT `_. Esto -significa que eres libre para modificar, distribuir y republicar el código -fuente con la condición de que las notas de copyright queden intactas. También -eres libre para incorporar CakePHP en cualquier aplicación comercial o de código -cerrado. - -Instalando CakePHP -================== - -CakePHP utiliza `Composer `_, una herramienta de manejo -de dependencias para PHP 5.3+, como el método de instalación oficialmente -soportado. - -Primero, necesitas descargar e instalar Composer, si no lo has hecho ya. -Si tienes instalado cURL, es tan fácil como correr esto en un terminal:: - - curl -s https://getcomposer.org/installer | php - -O, puedes descargar ``composer.phar`` desde el sitio web de -`Composer `__. - -Para sistemas Windows, puedes descargar el Instalador de Composer para Windows -`aquí `__. Para más -instrucciones acerca de esto, puedes leer el README del instalador de Windows -`aquí `__. - -Ya que has descargado e instalado Composer puedes generar una aplicación -CakePHP ejecutando:: - - php composer.phar create-project --prefer-dist cakephp/app:4.* [app_name] - -O si tienes Composer definido globalmente:: - - composer create-project --prefer-dist cakephp/app:4.* [app_name] - -Una vez que Composer termine de descargar el esqueleto y la librería core -de CakePHP, deberías tener una aplicación funcional de CakePHP instalada -vía Composer. Asegúrate de que los ficheros composer.json y composer.lock -se mantengan junto con el resto de tu código fuente. - -Ahora puedes visitar el destino donde instalaste la aplicación y ver los -diferentes avisos tipo semáforo de los ajustes. - -Mantente al día con los últimos cambios de CakePHP --------------------------------------------------- - -Si quieres mantenerte al corriente de los últimos cambios en CakePHP puedes -añadir las siguientes líneas al ``composer.json`` de tu aplicación:: - - "require": { - "cakephp/cakephp": "dev-master" - } - -Donde ```` es el nombre del branch que quieres seguir. Cada vez que -ejecutes ``php composer.phar update`` recibirás las últimas actualizaciones del -branch seleccionado. - -Instalación usando DDEV ------------------------ - -Otra manera rápida de instalar CakePHP es via `DDEV `_. -DDEV es una herramienta de código abierto para lanzar ambientes de desarrollo web en local. - -Si quieres configurar un nuevo proyecto, sólo necesitas ejecutar:: - - mkdir my-cakephp-app - cd my-cakephp-app - ddev config --project-type=cakephp --docroot=webroot - ddev composer create --prefer-dist cakephp/app:~5.0 - ddev launch - -Si tienes un proyecto existente:: - - git clone - cd - ddev config --project-type=cakephp --docroot=webroot - ddev composer install - ddev launch - -Por favor revisa la `Documentación de DDEV `_ para más detalles de cómo instalar / actualizar DDEV. - -.. note:: - - IMPORTANTE: Ésto no es un script de despliegue. Su objetivo es ayudar desarrolladores a - configurar ambientes de desarrollo rápidamente. En ningún caso su intención es que sea utilizado - en ambientes de producción. - -Permisos -======== - -CakePHP utiliza el directorio **tmp** para varias operaciones. Descripciones de -Modelos, el caché de las vistas y la información de la sesión son algunos -ejemplos de lo anterior. El directorio **logs** es utilizado para escribir -ficheros de log por el motor de ``FileLog`` por defecto. - -Asegúrate de que los directorios **logs**, **tmp** y todos sus subdirectorios -tengan permisos de escritura por el usuario del Servidor Web. La instalación -de CakePHP a través de Composer se encarga de este proceso haciendo que dichos -directorios tengan los permisos abiertos globalmente con el fin de que puedas -tener ajustado todo de manera más rápida. Obviamente, es recomendable que revises, y -modifiques si es necesario, los permisos tras la instalación vía Composer para -mayor seguridad. - -Un problema común es que **logs**, **tmp** y sus subdirectorios deben poder -ser modificados tanto por el usuario del Servidor Web como por el usuario de la -línea de comandos. En un sistema UNIX, si los usuarios mencionados difieren, -puedes ejecutar los siguientes comandos desde el directorio de tu aplicación -para asegurarte de que todo esté configurado correctamente: - -.. code-block:: console - - HTTPDUSER=`ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\ -f1` - setfacl -R -m u:${HTTPDUSER}:rwx tmp - setfacl -R -d -m u:${HTTPDUSER}:rwx tmp - setfacl -R -m u:${HTTPDUSER}:rwx logs - setfacl -R -d -m u:${HTTPDUSER}:rwx logs - -Configuración -============= - -Configurar una aplicación de CakePHP puede ser tan simple como -colocarla en el directorio raíz de tu Servidor Web, o tan complejo -y flexible como lo desees. Esta sección cubrirá los dos tipos -principales de instalación de CakePHP: Desarrollo y Producción. - -- Desarrollo: fácil de arrancar, las URL de la aplicación incluyen - el nombre del directorio de la aplicación de CakePHP y es menos segura. -- Producción: Requiere tener la habilidad de configurar el directorio raíz - del Servidor Web, cuenta con URL limpias y es bastante segura. - -Desarrollo -========== - -Este es el método más rápido para configurar CakePHP. En este ejemplo -utilizaremos la consola de CakePHP para ejecutar el servidor web nativo -de PHP para hacer que tu aplicación esté disponible en **http://host:port**. -Para ello ejecuta desde el directorio de la aplicación: - -.. code-block:: console - - bin/cake server - -Por defecto, sin ningún argumento, esto colocará tu aplicación en -**http://localhost:8765/**. - -Si tienes algún conflicto con **localhost** o el puerto **8765**, puedes indicarle -a la consola de CakePHP que corra el servidor de manera más específica -utilizando los siguientes argumentos: - -.. code-block:: console - - bin/cake server -H 192.168.13.37 -p 5673 - -Esto colocará tu aplicación en **http://192.168.13.37:5673/**. - -¡Eso es todo! Tu aplicación de CakePHP está corriendo perfectamente sin tener que -haber configurado el servidor web manualmente. - -.. note:: - -   Prueba ``bin/cake server -H 0.0.0.0`` si el servidor no es accesible desde otra máquina. - -.. warning:: - - El servidor de desarrollo *nunca* debe ser utilizado en un ambiente de - producción. Se supone que esto es un servidor básico de desarrollo y nada - más. - -Si prefieres usar un servidor web "real", Debes poder mover todos tus archivos -de la instalación de CakePHP (incluyendo los archivos ocultos) dentro la carpeta -raíz de tu servidor web. Debes entonces ser capaz de apuntar tu navegador al -directorio donde moviste los archivos y ver tu aplicación en acción. - -Producción -========== - -Una instalación de producción es una manera más flexible de montar una -aplicación de CakePHP. Utilizando este método, podrás tener un dominio entero -actuando como una sola aplicación de CakePHP. Este ejemplo te ayudará a instalar -CakePHP donde quieras en tu sistema de ficheros y tenerlo disponible en -``http://www.example.com``. Toma en cuenta que esta instalación requiere que -tengas los derechos de cambiar el directorio raíz (``DocumentRoot``) del -servidor web Apache. - -Después de instalar tu aplicación utilizando cualquiera de los métodos -mencionados en el directorio elegido - asumiremos que has escogido /cake_install -- tu estructura de ficheros debe ser la siguiente:: - - /cake_install/ - bin/ - config/ - logs/ - plugins/ - src/ - tests/ - tmp/ - vendor/ - webroot/ (este directorio es ajutado como el DocumentRoot) - .gitignore - .htaccess - .travis.yml - composer.json - index.php - phpunit.xml.dist - README.md - -Si utilizas Apache debes configurar la directiva ``DocumentRoot`` del -dominio a: - -.. code-block:: apacheconf - - DocumentRoot /cake_install/webroot - -Si tu configuración del Servidor Web es correcta debes tener tu -aplicación disponible ahora en http://www.example.com. - -A rodar! -======== - -Muy bien, ahora veamos a CakePHP en acción. Dependiendo de los ajustes -que hayas utilizado, deberías dirigirte en tu navegador a http://example.com/ -o http://localhost:8765/. En este punto, encontrarás la página principal de -CakePHP y un mensaje que te dice el estado actual de tu conexión a la base -de datos. - -¡Felicidades! Estás listo para -:doc:`Crear tu primera aplicación de CakePHP `. - -.. _url-rewriting: - -URL Rewriting -============= - -Apache ------- - -Mientras que CakePHP está diseñado para trabajar con mod\_rewrite -recién sacado del horno, usualmente hemos notado que algunos usuarios -tienen dificultades para lograr que todo funcione bien en sus sistemas. - -Aquí hay algunas cosas que puedes tratar de conseguir para que -funcione correctamente. -La primera mirada debe ir a httpd.conf. (Asegura de que estás editando el -httpd.conf del sistema en lugar del httpd.conf de un usuario o sitio específico) - -Hay archivos que pueden variar entre diferentes distribuciones y versiones de Apache. -Debes también mirar en https://cwiki.apache.org/confluence/display/httpd/DistrosDefaultLayout para -obtener información. - -#. Asegura de que un archivo .htaccess de sobreescritura esté permitido - y que *AllowOverride* esté ajustado en *All* para el correcto *DocumentRoot*. - Debes ver algo similar a: - - .. code-block:: apacheconf - - # Cada directorio al que Apache puede acceder puede ser configurado - # con sus respectivos permitidos/denegados servicios y características - # en ese directorios (y subdirectorios). - # - # Primero, configuramos el por defecto para ser muy restrictivo con sus - # ajustes de características. - - Options FollowSymLinks - AllowOverride All - # Order deny,allow - # Deny from all - - -#. Asegura que tú estás cargando mod\_rewrite correctamente. Debes - ver algo similar a esto: - - .. code-block:: apacheconf - - LoadModule rewrite_module libexec/apache2/mod_rewrite.so - - En muchos sistemas esto estará comentado por defecto, así que - solo debes remover el símbolo # al comienzo de la línea. - - Después de hacer los cambios, reinicia Apache para asegurarte que los - ajustes estén activados. - - Verifica que tus archivos .htaccess está actualmente en directorio - correcto. Algunos sistemas operativos tratan los archivos que empiezan - con '.' como ocultos y, por lo tanto, no podrás copiarlos. - -#. Asegúrate que tu copia de CakePHP provenga desde la sección descargas del - sitio o de nuestro repositorio de Git, y han sido desempacados correctamente, - revisando los archivos .htaccess. - - El directorio app de CakePHP (Será copiado en la raíz de tu - aplicación por bake): - - .. code-block:: apacheconf - - - RewriteEngine on - RewriteRule ^$ webroot/ [L] - RewriteRule (.*) webroot/$1 [L] - - - El directorio webroot de CakePHP (Será copiado a la raíz de tu aplicación web - por bake): - - .. code-block:: apacheconf - - - RewriteEngine On - RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule ^ index.php [L] - - - Si tu sitio aún tiene problemas con mod\_rewrite, querrás probar - modificar los ajustes para el Servidor Virtual. En Ubuntu, edita el - archivo **/etc/apache2/sites-available/default** (la ubicación - depende de la distribución). En este archivo, debe estar - ``AllowOverride None`` cambiado a``AllowOverride All``, así tendrás: - - .. code-block:: apacheconf - - - Options FollowSymLinks - AllowOverride All - - - Options Indexes FollowSymLinks MultiViews - AllowOverride All - Order Allow,Deny - Allow from all - - - En macOS, otra solución es usar la herramienta - `virtualhostx `_ - para crear servidores virtuales y apuntarlos a tu carpeta. - - Para muchos servicios de alojamiento (GoDaddy, 1and1), tu servidor - web estará actualmente sirviendo desde un directorio de usuario que - actualmente usa mod\_rewrite. Si tú estás instalando CakePHP en la carpeta - de usuario (http://example.com/~username/cakephp/), o alguna otra - estructura de URL que ya utilice mod\_rewrite, necesitarás agregar una - declaración a los archivos .htaccess que CakePHP usa (.htaccess, - webroot/.htaccess). - - Esto puede ser agregado a la misma sección con la directiva - RewriteEngine, entonces por ejemplo, tu .htaccess en el webroot - debería verse algo así: - - .. code-block:: apacheconf - - - RewriteEngine On - RewriteBase /path/to/app - RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule ^ index.php [L] - - - Los detalles de estos cambios dependerán de tu configuración, y puede - incluir algunas líneas adicionales que no están relacionadas con CakePHP. - Por favor dirígete a la documentación en línea de Apache para más información. - -#. (Opcional) Para mejorar la configuración de producción, debes prevenir - archivos adicionales inválidos que sean tomados por CakePHP. Modificando tu .htaccess - del webroot a algo cómo esto: - - .. code-block:: apacheconf - - - RewriteEngine On - RewriteBase /path/to/app/ - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_URI} !^/(webroot/)?(img|css|js)/(.*)$ - RewriteRule ^ index.php [L] - - - Lo anterior simplemente previene que archivos adicionales incorrectos sean enviados - a index.php en su lugar muestre la página 404 de tu servidor web. - - Adicionalmente, puedes crear una página 404 que concuerde, o usar la página 404 - incluida en CakePHP agregando una directiva ``ErrorDocument``: - - .. code-block:: apacheconf - - ErrorDocument 404 /404-not-found - -Nginx ------ - -Nginx no hace uso de un archivo .htaccess como Apache, por esto es necesario -crear la reescritura de URL en la configuracion de *sites-available*. Esto -usualmente se encuentra en ``/etc/nginx/sites-available/your_virtual_host_conf_file``. -Dependiendo de la configuración, necesitarás modificar esto, pero por lo menos, -necesitas PHP corriendo como una instancia FastCGI: - -.. code-block:: nginx - - server { - listen 80; - server_name www.example.com; - rewrite ^(.*) http://example.com$1 permanent; - } - - server { - listen 80; - server_name example.com; - - # root directive should be global - root /var/www/example.com/public/webroot/; - index index.php; - - access_log /var/www/example.com/log/access.log; - error_log /var/www/example.com/log/error.log; - - location / { - try_files $uri $uri/ /index.php?$args; - } - - location ~ \.php$ { - try_files $uri =404; - include /etc/nginx/fastcgi_params; - fastcgi_pass 127.0.0.1:9000; - fastcgi_index index.php; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - } - } - -En algunos servidores (Como Ubuntu 14.04) la configuración anterior no funcionará -recién instalado, y de todas formas la documentación de nginx recomienda -una forma diferente de abordar esto -(https://nginx.org/en/docs/http/converting_rewrite_rules.html). Puedes intentar -lo siguiente (Notarás que esto es un bloque de servidor {}, en vez de dos, -pese a que si quieres que example.com resuelva a tu aplicación CakePHP en adición -a www.example.com consulta el enlace de nginx anterior): - -.. code-block:: nginx - - server { - listen 80; - server_name www.example.com; - rewrite 301 http://www.example.com$request_uri permanent; - - # root directive should be global - root /var/www/example.com/public/webroot/; - index index.php; - - access_log /var/www/example.com/log/access.log; - error_log /var/www/example.com/log/error.log; - - location / { - try_files $uri /index.php?$args; - } - - location ~ \.php$ { - try_files $uri =404; - include /etc/nginx/fastcgi_params; - fastcgi_pass 127.0.0.1:9000; - fastcgi_index index.php; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - } - } - -IIS7 (Windows) --------------- - -IIS7 no soporta de manera nativa los archivos .htaccess. Mientras haya -*add-ons* que puedan agregar soporte a estos archivos, puedes también importar -las reglas htaccess en IIS para usar las redirecciones nativas de CakePHP. Para hacer -esto, sigue los siguientes pasos: - -#. Usa el `Intalador de plataforma Web de Microsoft `_ - para instalar el `Modulo de Redirreción 2.0 `_ de URLs - o descarga directamente (`32-bit `_ / - `64-bit `_). -#. Crear un nuevo archivo llamado web.config en tu directorio de raíz de CakePHP. -#. Usando Notepad o cualquier editor de XML, copia el siguiente código - en tu nuevo archivo web.config: - -.. code-block:: xml - - - - - - - - - - - - - - - - - - - - - - - -Una vez el archivo web.config es creado con las reglas de redirección amigables -de IIS, los enlaces, CSS, JavaScript y redirecciones de CakePHP deberían -funcionar correctamente. - -No puedo usar Redireccionamientos de URL ----------------------------------------- - -Si no quieres o no puedes obtener mod\_rewirte (o algún otro módulo -compatible) en el servidor a correr, necesitarás usar -el decorador de URL incorporado en CakePHP. En **config/app.php**, -descomentar la línea para que se vea así:: - - 'App' => [ - // ... - // 'baseUrl' => env('SCRIPT_NAME'), - ] - -También remover estos archivos .htaccess:: - - /.htaccess - webroot/.htaccess - -Esto hará tus URL verse así -www.example.com/index.php/controllername/actionname/param antes que -www.example.com/controllername/actionname/param. - -.. _GitHub: https://github.com/cakephp/cakephp -.. _Composer: https://getcomposer.org - -.. meta:: - :title lang=es: Instalación - :keywords lang=en: apache mod rewrite,microsoft sql server,tar bz2,directorio tmp,base de datos,copiar archivos,tar gz,fuente de la aplicación,actual liberación,servidores web,microsoft iis,anuncios de derechos de autor,motor de base de datos,reparación de errores,lighthttpd,repositorio,mejoras,código fuente,cakephp,incorporate diff --git a/es/intro.rst b/es/intro.rst deleted file mode 100644 index 76d034d827..0000000000 --- a/es/intro.rst +++ /dev/null @@ -1,172 +0,0 @@ -CakePHP de un vistazo -##################### - -CakePHP está diseñado para hacer tareas habituales de desarrollo web simples y -fáciles. Proporciona una caja de herramientas todo-en-uno y para que puedas -empezar rápidamente, las diferentes partes de CakePHP trabajan correctamente de -manera conjunta o separada. - -El objetivo de este artículo es introducirte en los conceptos generales de -CakePHP y darte un rápido vistazo sobre como esos conceptos están implementados -en CakePHP. Si estás deseando comenzar un proyecto puedes :doc:`empezar con el tutorial -`, o :doc:`profundizar en la documentación -`. - -Convenciones sobre configuración -================================ - -CakePHP proporciona una estructura organizativa básica que cubre los nombres de -las clases, archivos, tablas de base de datos y otras convenciones más. Aunque -lleva algo de tiempo aprender las convenciones, siguiéndolas CakePHP evitará -que tengas que hacer configuraciones innecesarias y hará que la estructura de la -aplicación sea uniforme y que el trabajo con varios proyectos sea sencillo. El -capítulo de :doc:`convenciones ` muestra las que son utilizadas -en CakePHP. - -La capa Modelo -============== - -La capa Modelo representa la parte de tu aplicación que implementa la lógica de -negocio. Es la responsable de obtener datos y convertirlos en los conceptos que -utiliza tu aplicación. Esto incluye procesar, validar, asociar u otras tareas -relacionadas con el manejo de datos. - -En el caso de una red social la capa modelo se encargaría de tareas como guardar -los datos del usuario, las asociaciones de amigos, almacenar y obtener fotos, -buscar sugerencias de amistad, etc. Los objetos modelo serían "Amigo", -"Usuario", "Comentario" o "Foto". Si quisiéramos obtener más datos de nuestra -tabla ``usuarios`` podríamos hacer lo siguiente:: - - use Cake\ORM\Locator\LocatorAwareTrait; - - $usuarios = $this->getTableLocator()->get('Usuarios'); - $resultset = $usuarios->find()->all(); - foreach ($resultset as $row) { - echo $row->nombreusuario; - } - -Como te habrás dado cuenta no hemos necesitado escribir ningún código previo -para empezar a trabajar con nuestros datos. Al utilizar las convenciones CakePHP -usará clases estándar para tablas y clases de entidad que no hayan sido definidas. - -Si queremos crear un nuevo usuario y guardarlo (con validaciones) podríamos hacer -algo como:: - - use Cake\ORM\Locator\LocatorAwareTrait; - - $usuarios = $this->getTableLocator()->get('Usuarios'); - $usuario = $usuarios->newEntity(['email' => 'mark@example.com']); - $usuarios->save($usuario); - -La capa Vista -============= - -La capa Vista renderiza una presentación de datos modelados. Separada de los -objetos Modelo, es la responsable de usar la información disponible para producir -cualquier interfaz de presentación que pueda necesitar tu aplicación. - -Por ejemplo, la vista podría usar datos del modelo para renderizar una plantilla -HTML que los contenga o un resultado en formato XML:: - - // En un archivo de plantilla de vista renderizaremos un 'element' para cada usuario. - -
    • - element('usuario', ['usuario' => $usuario]) ?> -
    • - - -La capa Vista proporciona varias extensiones como :ref:`view-templates`, -:ref:`view-elements` y :doc:`/views/cells` que te permiten reutilizar tu lógica -de presentación. - -Esta capa no se limita a representaciones HTML o texto de los datos. Puede utilizarse -para otros formatos habituales como JSON, XML y a través de una arquitectura -modular, cualquier otro formato que puedas necesitar como CSV. - -La capa Controlador -=================== - -La capa Controlador maneja peticiones de usuarios. Es la responsable de elaborar -una respuesta con la ayuda de las capas Modelo y Vista. - -Un controlador puede verse como un gestor que asegura que todos los recursos -necesarios para completar una tarea son delegados a los trabajadores oportunos. -Espera por las peticiones de los clientes, comprueba la validez de acuerdo con las -reglas de autenticación y autorización, delega la búsqueda o procesado de datos -al modelo, selecciona el tipo de presentación que el cliente acepta y finalmente -delega el proceso de renderizado a la capa Vista. Un ejemplo de controlador para -el registro de un usuario sería:: - - public function add() - { - $usuario = $this->Usuarios->newEmptyEntity(); - if ($this->request->is('post')) { - $usuario = $this->Usuarios->patchEntity($usuario, $this->request->getData()); - if ($this->Usuarios->save($usuario, ['validate' => 'registration'])) { - $this->Flash->success(__('Ahora estás registrado.')); - } else { - $this->Flash->error(__('Hubo algunos problemas.')); - } - } - $this->set('usuario', $usuario); - } - -Puedes fijarte en que nunca renderizamos una vista explícitamente. Las convenciones -de CakePHP se harán cargo de seleccionar la vista correcta y de renderizarla con -los datos que preparemos con ``set()``. - -.. _request-cycle: - -Ciclo de una petición CakePHP -============================= - -Ahora que te has familiarizado con las diferentes capas en CakePHP, revisemos -como funciona el ciclo de una petición: - -.. figure:: /_static/img/typical-cake-request.png - :align: center - :alt: Diagrama de flujo mostrando una petición típica de CakePHP - -El ciclo de petición típico de CakePHP comienza con un usuario solicitando una -página o recurso en tu aplicación. A un alto nivel cada petición sigue los -siguientes pasos: - -#. Las reglas de rescritura del servidor web envían la petición a **webroot/index.php**. -#. Tu aplicación es cargada y ligada a un ``HttpServer``. -#. Se inicializa el ``midleware`` de tu aplicación. -#. Una petición y respuesta son precesadas a través del ``Middleware PSR-7`` que tu aplicación utiliza. Normalmente, esto incluye la captura de errores y enrutamiento. -#. Si no recibe ninguna respuesta del ``middleware`` y la petición contiene información de enrutamiento, se selecciona un controlador y una acción. -#. La acción del controlador es ejecutada y el controlador interactúa con los Modelos y Componentes necesarios. -#. El controlador delega la creación de la respuesta a la Vista para generar la salida a partir de los datos del modelo. -#. La vista utiliza ``Helpers`` y ``Cells`` para generar el cuerpo y las cabeceras de la respuesta. -#. La respuesta es devuelta a través del :doc:`/controllers/middleware`. -#. El ``HttpServer`` envía la respuesta al servidor web. - -Esto es solo el comienzo -======================== - -Ojalá este repaso rápido haya despertado tu curiosidad. Otras funcionalidades -geniales de CakePHP son: - -* Un :doc:`framework para caché ` que se integra con - Memcached, Redis y otros métodos de caché. -* Poderosas :doc:`herramientas de generación de código - ` para que puedas comenzar inmediatamente. -* :doc:`Framework para la ejecución de pruebas integrado ` para que puedas asegurarte de que tu código funciona perfectamente. - -Los siguientes pasos obvios son :doc:`descargar CakePHP ` -y leer el :doc:`tutorial y crear algo asombroso `. - -Lecturas complementarias -======================== - -.. toctree:: - :maxdepth: 1 - - /intro/where-to-get-help - /intro/conventions - /intro/cakephp-folder-structure - -.. meta:: - :title lang=es: Empezando - :keywords lang=es: estructura de carpetas,nombres de tablas,petición inicial,tabla de base de datos,estructura organizativa,rst,nombres de archivo,convenciones,mvc,web página,sit diff --git a/es/intro/cakephp-folder-structure.rst b/es/intro/cakephp-folder-structure.rst deleted file mode 100644 index 9d1996d6b5..0000000000 --- a/es/intro/cakephp-folder-structure.rst +++ /dev/null @@ -1,65 +0,0 @@ -Estructura de carpetas de CakePHP -################################# - -Después de haber descargado el esqueleto de aplicación de CakePHP, estos son los -directorios de primer nivel que deberías ver: - -- La carpeta *bin* contiene los ejecutables por consola de Cake. -- La carpeta *config* contiene los documentos de - :doc:`/development/configuration` que utiliza CakePHP. Detalles de la conexión - a la Base de Datos, bootstrapping, archivos de configuración del core y otros, - serán almacenados aquí. -- La carpeta *plugins* es donde se almacenan los :doc:`/plugins` que utiliza tu - aplicación. -- La carpeta de *logs* contiene normalmente tus archivos de log, dependiendo de - tu configuración de log. -- La carpeta *src* será donde tu crearás tu magia: es donde se almacenarán los - archivos de tu aplicación. -- La carpeta *templates* contiene los archivos de presentación: - elementos, páginas de error, plantillas generales y plantillas de vistas. -- La carpeta *resources* contiene sub carpetas para varios tipos de archivos. -- La carpeta *locales* contiene sub carpetas para los archivos de traducción a otros idiomas. -- La carpeta *tests* será donde pondrás los test para tu aplicación. -- La carpeta *tmp* es donde CakePHP almacenará temporalmente la información. La - información actual que almacenará dependerá de cómo se configure CakePHP, pero - esta carpeta es normalmente utilizada para almacenar descripciones de modelos - y a veces información de sesión. -- La carpeta *vendor* es donde CakePHP y otras dependencias de la aplicación - serán instaladas por `Composer `_. Editar estos archivos no es - recomendado, ya que Composer sobreescribirá tus cambios en la próxima actualización. -- El directorio *webroot* es la raíz de los documentos públicos de tu - aplicación. Contiene todos los archivos que quieres que sean accesibles - públicamente. - - Asegúrate de que las carpetas *tmp* y *logs* existen y permiten escritura, en - caso contrario el rendimiento de tu aplicación se verá gravemente perjudicado. - En modo debug, CakePHP te avisará si este no es el caso. - -La carpeta src -============== - -La carpeta *src* de CakePHP es donde tú harás la mayor parte del desarrollo de -tu aplicación. Observemos más detenidamente dentro de la carpeta *src*. - -Command - Contiene los comandos de consola de tu aplicación. - Para más información mirar :doc:`/console-commands/commands`. -Console - Contiene los 'scripts' de instalación ejecutados por Composer. -Controller - Contiene los :doc:`/controllers` de tu aplicación y sus componentes. -Middleware - Contiene cualquier :doc:`/controllers/middleware` para tu aplicación. -Model - Contiene las tablas, entidades y comportamientos de tu aplicación. -View - Las clases de presentación se ubican aquí: plantillas de vistas, células y ayudantes. - -.. note:: - - La carpeta ``Command`` no está creada por defecto. - Puedes añadirla cuando la necesites. - -.. meta:: - :title lang=es: CakePHP Estructura de Carpetas - :keywords lang=es: librerías internas,configuración core,descripciones de modelos, vendors externos,detalles de conexión,estructura de carpetas,librerías,compromiso personal,conexión base de datos,internacionalización,archivos de configuración,carpetas,desarrollo de aplicaciones,léeme,lib,configurado,logs,config,third party,cakephp diff --git a/es/intro/conventions.rst b/es/intro/conventions.rst deleted file mode 100644 index a8e01df51f..0000000000 --- a/es/intro/conventions.rst +++ /dev/null @@ -1,250 +0,0 @@ -Convenciones CakePHP -#################### - -Somos muy fans de la convención por encima de la configuración. A pesar de que -toma algo de tiempo aprender las convenciones de CakePHP, ahorrarás tiempo -a la larga. Siguiendo las convenciones obtendrás funcionalidades gratuitas y -te liberarás de la pesadilla de mantener archivos de configuración. Las -convenciones también hacen que el desarrollo sea uniforme, permitiendo a -otros desarrolladores intervenir y ayudar fácilmente. - -Convenciones de Controlador -=========================== - -Los nombres de las clases Controlador son en plurar, en formato ``CamelCase``, -y finalizan con ``Controller``. Ejemplos de nombres son: ``UsuariosController`` -y ``CategoriasArticulosController``. - -Los métodos públicos de los Controladores a menudo se exponen como 'acciones' -accesibles a través de un navegador web. Tienen formato ``camelBacked``. Por ejemplo, ``/users/view-me`` mapea -al método ``viewMe()`` de ``UsersController`` sin tener que hacer nada en el -enrutamiento de la aplicación. Los métodos protegidos o privados no son -accesibles con el enrutamiento. - -Consideraciones URL para los nombres de Controladores -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Como acabas de ver, los controladores de una sola palabra mapean a una dirección -URL en minúscula. Por ejemplo: a ``UsuariosController`` (que debería estar -definido en **UsuariosController.php**) se puede acceder desde -http://example.com/usuarios. - -Aunque puedes enrutar controladores de múltiples palabaras de la forma que -desees, la convención es que tus URLs separen las palabras con guiones utilizando -la clase ``DashedRoute``, de este modo ``/categorias-articulos/ver-todas`` es -la forma correcta para acceder a la acción ``CategoriasArticulosController::verTodas()``. - -Cuando creas enlaces utilizando ``this->Html->link()`` puedes utilizar las -siguientes convenciones para el array url:: - - $this->Html->link('titulo-enlace', [ - 'prefix' => 'MiPrefijo' // CamelCase - 'plugin' => 'MiPlugin', // CamelCase - 'controller' => 'NombreControlador', // CamelCase - 'action' => 'nombreAccion' // camelBack - ] - -Para más información sobre URLs de CakePHP y el manejo de sus parámetros puedes -consultar :ref:`routes-configuration`. - -.. _file-and-classname-conventions: - -Convenciones de nombre de clase y archivo -========================================= - -En general, los nombres de los archivos coinciden con los nombres de las clases -y sigue el estándar PSR-4 para cargarse automáticamente. Los -siguientes son ejemplos de nombres de clases y de sus archivos: - -- La clase Controlador ``LatestArticlesController`` debería estar en un archivo llamado **LatestArticlesController.php** -- La clase Componente ``MyHandyComponent`` debería estar en un archivo llamado **MyHandyComponent.php** -- La clase Tabla ``OptionValuesTable`` debería estar en un archivo llamado **OptionValuesTable.php**. -- La clase Entidad ``OptionValue`` debería estar en un archivo llamado **OptionValue.php**. -- La clase Behavior ``EspeciallyFunkableBehavior`` debería estar en un archivo llamado **EspeciallyFunkableBehavior.php** -- La clase Vista ``SuperSimpleView`` debería estar en un archivo llamado **SuperSimpleView.php** -- La clase Helper ``BestEverHelper`` debería estar en un archivo llamado **BestEverHelper.php** - -Cada archivo deberá estar ubicado en la carpeta/namespace correcta dentro de -tu carpeta de tu aplicación. - -.. _model-and-database-conventions: - -Convenciones de base de datos -============================= - -Los nombres de las tablas correspondientes a los modelos de CakePHP son en plural -y con con '_'. Por ejemplo ``users``, ``menu_links`` y ``user_favorite_pages`` respectivamente. -Los nombres de tablas formadas por múltiples palabras sólo deben usar el plural en la última -palabra, por ejemplo, ``menu_links``. - -Los nombres de campos con dos o más palabras se escriben con '_', por ejemplo: ``first_name``. - -Las claves foráneas en relaciones ``1-n`` (``hasMany``) y ``1-1`` (``belongsTo/hasOne``) -son reconocidas por defecto mediante el nombre (en singular) de la tabla relacionada -seguido de ``_id``. De este modo si ``Users`` tiene varios ``Articles`` (relación -``hasMany``), la tabla ``articles`` se relacionará con la tabla ``users`` a través -de la clave foránea ``user_id``. Para una tabla como ``menu_links`` -cuyo nombre está formado por varias palabras, la clave foránea sería ``menu_link_id``. - -Las tablas de unión, usadas en las relaciones ``n-n`` (``BelongsToMany``) entre -modelos, deberían ser nombradas después de las tablas que unirán. Los nombres deberán -estar en plural y en orden alfabético: ``articles_tags`` en lugar de ``tags_articles`` -o ``article_tags``. *El comando ``bake`` no funcionará correctamente si ésta convención -no se sigue.* Si la tabla de unión guarda alguna información que no sean las claves -foráneas, debes crear la clase de la entidad y modelo para esa tabla. - -Además de utilizar claves auto-incrementales como claves primarias, también -puedes utilizar columnas UUID. CakePHP creará un único UUID de 36 caracteres -(:php:meth:`Cake\\Utility\\Text::uuid()`) cada vez que guardes un nuevo registro -usando el método ``Table::save()`` . - -Convenciones de modelo -====================== - -Los nombres de las clases para las tablas son en plural, formato ``CamelCase`` -y terminan en ``Table``. ``UsersTable``, ``MenuLinksTable`` y ``UserFavoritePagesTable`` -son ejemplos de nombres de clases que corresponden a las tablas ``users``, ``menu_links`` -y ``user_favorite_pages`` respectivamente. - -Los nombres de las clases para las entidades son en singular, formato ``CamelCase`` y no -tienen sufijo. ``User``, ``MenuLink`` y ``UserFavoritePage`` son ejemplos de nombres de clases -que corresponden a las entidades ``users``, ``menu_links`` y ``user_favorite_pages`` respectivamente. - -Convenciones de vistas -====================== - -Los archivos de las plantillas de vistas son nombrados según las -funciones de controlador que las muestran empleando '_'. La función ``viewAll()`` -de la clase ``ArticlesController`` mostrará la vista **templates/Articles/view_all.php**. - -El patrón base es **templates/Controller/nombre_funcion.php**. - -.. note:: - - Por defecto CakePHP usa palabras en inglés para las convenciones de nombres. - Si utilizas otro idioma CakePHP puede que no sea capaz de procesar - correctamente las conversiones (de singular a plural y viceversa). Si necesitas - añadir reglas para tu idioma para algunas palabras, puedes utilizar la clase - :php:class:`Cake\\Utility\\Inflector`. Además de definir tus reglas de - conversión personalizadas, esta clase te permite comprobar que CakePHP comprenda - tu sintaxis personalizada para palabras en plural y singular. Mira la documentación - sobre :doc:`/core-libraries/inflector` para más información. - -Convenciones de plugins -======================= - -Es útil añadir el prefijo "cakephp-" en el nombre del paquete para los plugins de CakePHP. -Esto hace que el nombre esté relacionado semánticamente al "framework" del que depende. - -**No** uses el espacio de nombre de CakePHP (cakephp) como nombre de "vendor", ya que es -un espacio reservado para los plugins que son propiedad de CakePHP. La convención es usar -letras en minúscula y guiones como separadores:: - - // Bad - cakephp/foo-bar - - // Good - your-name/cakephp-foo-bar - -Ver `lista asombrosa de recomendaciones -`__ -par mas detalles. - -Resumen -======= - -Nombrando los elementos de tu aplicación empleando las convenciones de CakePHP -ganarás funcionalidad sin los fastidios y ataduras de mantenimiento de la -configuración. - -Un último ejemplo que enlaza todas las convenciones: - -- Tabla de base de datos: "articles", "menu_links" -- Clase Tabla: ``ArticlesTable``, ubicada en **src/Model/Table/ArticlesTable.php** -- Clase Entidad: ``Article``, ubicada en **src/Model/Entity/Article.php** -- Clase Controlador: ``ArticlesController``, ubicada en - **src/Controller/ArticlesController.php** -- Plantilla vista, ubicada en **templates/Articles/index.php** - -Usando estas convenciones, CakePHP sabe que una petición a http://example.com/articles/ -hace una llamada a la función ``index()`` de la clase ``ArticlesController``, -donde el modelo ``Articles`` está disponible automáticamente y enlazada automáticamente -a la tabla ``articles`` en la base de datos . Ninguna de estas relaciones han sido -configuradas de ningún modo salvo creando clases y archivos que has tenido que crear de -todas formas. - -+----------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Ejemplo | articles | menu_links | | -+----------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Tabla base | articles | menu_links | Nombres de tablas que se corresponden a modelos | -| de datos | | | son en plural y con guión bajo '_'. | -+----------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Archivo | ArticlesController.php | MenuLinksController.php | | -+----------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Tabla | ArticlesTable.php | MenuLinksTable.php | Los nombres de clase de las tablas son en plural, | -| | | | formato 'CamelCased' y acaban con el sufijo 'Table' | -+----------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Entidad | Article.php | MenuLink.php | Los nombres de clase de las entidades son en | -| | | | singular y 'CamelCased': Article and MenuLink | -+----------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Clase | ArticlesController | MenuLinksController | | -+----------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Controlador | ArticlesController | MenuLinksController | Plural, CamelCased, acaba en 'Controller' | -+----------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Plantillas | Articles/index.php | MenuLinks/index.php | Los archivos de plantillas de vistas son nombrados | -| de | Articles/add.php | MenuLinks/add.php | según las funciones que el controlador muestra, | -| vistas | Articles/edit.php | MenuLinks/add.php | en minúscula y guión bajo | -+----------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Comportamiento | ArticlesBehavior.php | MenuLinksBehavior.php | | -+----------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Vista | ArticlesView.php | MenuLinksView.php | | -+----------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Ayudante | ArticlesHelper.php | MenuLinksHelper.php | | -+----------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Componente | ArticlesComponent.php | MenuLinksComponent.php | | -+----------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Plugin | Mal: cakephp/articles | cakephp/menu-links | Útil añadir el prefijo "cakephp-" a los plugins | -| | Bien: you/cakephp-articles | you/cakephp-menu-links | en el nombre del paquete. No uses el espacio de | -| | | | nombre (cakephp) como nombre de vendor ya que está | -| | | | para los plugins propiedad de CakePHP. La | -| | | | convención es usar letras minúsculas y guiones | -| | | | como separadores. | -+----------------+-----------------------------+-------------------------+------------------------------------------------------+ -| Cada fichero estará localizado en la 'carpeta/espacio de nombre' apropiado dentro de la carpeta de tu aplicación. | -+----------------+-----------------------------+-------------------------+------------------------------------------------------+ - -Database Convention Summary -=========================== -+-----------------+--------------------------------------------------------------+ -| Claves foráneas | Las relaciones son reconocidas por defecto como el | -| | nombre (singular) de la tabla relacionada, | -| hasMany | seguida de ``_id``. | -| belongsTo/ | Para Users 'hasMany' Articles, la tabla ``articles`` | -| hasOne | hará referencia a ``users`` a través de la | -| BelongsToMany | clave foránea ``user_id``. | -| | | -+-----------------+--------------------------------------------------------------+ -| Múltiples | ``menu_links`` cuyo nombre contiene múltiples palabras, | -| palabras | su clave foránea será ``menu_link_id``. | -+-----------------+--------------------------------------------------------------+ -| Auto Increment | Además de utilizar claves auto-incrementales como claves | -| | primarias, también puedes utilizar columnas UUID. | -| | CakePHP creará un único UUID de 36 caracteres | -| | usando (:php:meth:`Cake\\Utility\\Text::uuid()`) | -| | cada vez que guardes un nuevo registro | -| | usando el método ``Table::save()`` . | -+-----------------+--------------------------------------------------------------+ -| Join tables | Deberán ser nombradas según las tablas que unirán o el | -| | comando de 'bake' no funcionará y ordenarse alfabéticamente | -| | (``articles_tags`` en vez de ``tags_articles``). | -| | Si tiene campos adicionales que guardan información, debes | -| | crear un archivo de entidad y modelo para esa tabla. | -+-----------------+--------------------------------------------------------------+ - -Ahora que te has introducido en los fundamentos de CakePHP. puedes tratar de -realizar el tutorial :doc:`/tutorials-and-examples/cms/installation` para ver -como las cosas encajan juntas. - -.. meta:: - :title lang=es: Convenciones CakePHP - :keywords lang=es: experiencia desarrollo web,pesadilla mantenimiento,método index,legado sistemas,nombres métodos,clases php,sistema uniforme,archivos configuración,tenets,artículos,convenciones,controlador convencional,mejores prácticas,visibilidad,nuevos artículos,funcionalidad,lógica,cakephp,desarrolladores diff --git a/es/intro/where-to-get-help.rst b/es/intro/where-to-get-help.rst deleted file mode 100644 index 14f53280ed..0000000000 --- a/es/intro/where-to-get-help.rst +++ /dev/null @@ -1,144 +0,0 @@ -Donde obtener ayuda -################### - -La página oficial de CakePHP -============================ - -`https://cakephp.org `_ - -La página oficial de CakePHP es siempre un gran lugar para visitar. Proporciona -enlaces a las herramientas más utilizadas por desarrolladores, ``screencasts``, -oportunidades para hacer una donación y descargas. - -El Cookbook -=========== - -`https://book.cakephp.org `_ - -Este manual probablemente debería ser el primer lugar al que debas acudir -para obtener respuestas. Como muchos otros proyectos de código libre, nuevos -colaboradores se unen regularmente. Intenta encontrar por ti mismo las respuestas -a tus preguntas primero, puede que así tardes más en encontrar las respuestas, -pero permanecerán durante más tiempo - y además aliviarás nuestra carga de -soporte. Tanto el manual como la API tienen una versión online. - -La Bakery -========= - -`https://bakery.cakephp.org `_ - -La "panadería" (``bakery``) de CakePHP es un lugar de intercambio para todo -lo relacionado con CakePHP. Consúltala para tutoriales, casos de estudio y -ejemplos de código. Cuando estés familiarizado con CakePHP, accede y comparte -tus conocimientos con la comunidad y gana fortuna y fama de forma instantánea. - -La API -====== - -`https://api.cakephp.org/ `_ - -Directo al punto y directo para desarrolladores del núcleo de CakePHP, la API -(``Application Programming Interface``) es la documentación más completa para -todos los detalles esenciales del funcionamiento interno del ``framework``. -Es referencia directa al código, asi que trae tu sombrero de hélice. - -Los casos de prueba -=================== - -Si crees que la información proporcionada en la API no es suficiente, comprueba -el código de los casos de prueba proporcionados con CakePHP. Pueden servirte -como ejemplos prácticos de funciones y datos de una clase. :: - - tests/TestCase/ - -El canal IRC -============ - -**Canales IRC en irc.freenode.net:** - -- `#cakephp `_ -- Discusión general -- `#cakephp-docs `_ -- Documentación -- `#cakephp-bakery `_ -- Bakery -- `#cakephp-fr `_ -- Canal francés. - -Si estás atascado, péganos un grito en el canal IRC de CakePHP. -Alguien del `equipo de desarrollo `_ -está normalmente, especialmente durante las horas de día para usuarios de -América del Norte y del Sur. Estaremos encantados de escucharte, tanto si -necesitas ayuda como si quieres encontrar usuarios en tu zona o si quieres -donar tu nuevo coche deportivo de marca. - -.. _cakephp-official-communities: - -Foro oficial de CakePHP -======================= -`Foro oficial de CakePHP `_ - -Nuestro foro oficial donde puedes pedir ayuda, sugerir ideas y conversar sobre -CakePHP. Es un lugar perfecto para encontrar rápidamente respuestas y ayudar -a otros. Únete a la familia CakePHP registrándote. - -Stackoverflow -============= - -`https://stackoverflow.com/ `_ - -Etiqueta tus preguntas con ``cakephp`` y la versión específica que utilizas -para permitir encontrar a los usuarios de stackoverflow tus preguntas. - -Donde encontrar ayuda en tu idioma -================================== - -Portugúes de Brasil -------------------- - -- `Comunidad brasileña de CakePHP `_ - -Danés ------ - -- `Canal danés de CakePHP en Slack `_ - -Francés -------- - -- `Comunidad francesa de CakePHP `_ - -Alemán ------- - -- `Canal alemán de CakePHP en Slack `_ -- `Grupo alemán de CakePHP en Facebook `_ - -Iraní ------ - -- `Comunidad iraní de CakePHP `_ - -Holandés --------- - -- `Canal holandés de CakePHP en Slack `_ - -Japonés -------- - -- `Canal japonés de CakePHP en Slack `_ -- `Grupo japonés de CakePHP en Facebook `_ - -Portugués ---------- - -- `Grupo portugés de CakePHP en Google `_ - -Español -------- - -- `Canal español de CakePHP en Slack `_ -- `Canal IRC de CakePHP en español `_ -- `Grupo español de CakePHP en Google `_ - -.. meta:: - :title lang=es: Donde obtener ayuda - :description lang=es: Donde obtener ayuda con CakePHP: La página oficial de CakePHP, El Cookbook, La Bakery, La API, en los casos de prueba, el canal IRC, El grupo Google de CakePHP o CakePHP Questions. - :keywords lang=es: cakephp,ayuda cakephp,ayuda con cakephp,donde obtener ayuda,cakephp irc,cakephp preguntas,cakephp api,cakephp casos de prueba,proyectos código abierto,canal irc,código de referencia,canal irc,herramientas de desarrollo,caso de prueba,bakery diff --git a/es/migrations.rst b/es/migrations.rst deleted file mode 100644 index 3c135c3aa8..0000000000 --- a/es/migrations.rst +++ /dev/null @@ -1,13 +0,0 @@ -Migrations -########## - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta - página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón - **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección - superior para obtener información sobre el tema de esta página. diff --git a/es/orm.rst b/es/orm.rst deleted file mode 100644 index a24fb3a46d..0000000000 --- a/es/orm.rst +++ /dev/null @@ -1,111 +0,0 @@ -Acceso a la base de datos & ORM -############################### - -En CakePHP el acceso a la base de datos se hace por medio de dos objetos primarios. -El primer tipo de objeto son **repositories** -repositorios- o **table objects** -objetos de tabla-. -Estos objetos proveen acceso a colecciones de datos. Nos permiten guardar nuevos -registros, modificar y borrar existentes, definir relaciones y realizar operaciones en masa. -El segundo tipo de objeto son **entities** -entidades-. Las Entidades representan registros -individuales y permiten definir funcionalidad y comportamiento a nivel de registro/fila. - -Estas dos clases son responsables de manejar todo lo que sucede con datos, validez, interacción y -evolución en tu área de trabajo. - -El ORM incluído en CakePHP se especializa en base de datos relacionales, pero puede ser extendido -para soportar alternativas. - -El ORM de CakePHP toma ideas y conceptos de los modelos ActiveRecord y Datamapper. Aspira a -crear una implementación híbrida que combine aspectos de los dos modelos para crear un ORM rápido -y fácil de usar. - -Antes de comentar explorando el ORM, asegurate de configurar tu conexion :ref:`configure your -database connections `. - -Ejemplo rápido -============== - -Para comenzar no es necesario escribir código. Si has seguido las convenciones de nombres para las tablas puedes -comenzar a utilizar el ORM. Por ejemplo si quisieramos leer datos de nuestra tabla ``articles``:: - - use Cake\ORM\TableRegistry; - - // Prior to 3.6 use TableRegistry::get('Articles') - $articles = TableRegistry::getTableLocator()->get('Articles'); - $query = $articles->find(); - foreach ($query as $row) { - echo $row->title; - } - -Como se ve, no es necesario agregar código extra ni ninguna otra configuración, gracias al uso de las convenciones -de CakePHP. Si quisieramos modificar nuestra clase ArticlesTable para agregar asociaciones o definir métodos -adicionales deberiamos agregar las siguientes líneas en **src/Model/Table/ArticlesTable.php** :: - - namespace App\Model\Table; - - use Cake\ORM\Table; - - class ArticlesTable extends Table - { - - } - -Las clases Table usan una version en CamelCase del nombre de la tabla, con el sufijo ``Table``. -Una vez que tu clase fue creada, puedes obtener una referencia a esta usando :php:class:`~Cake\\ORM\\TableRegistry` como antes:: - - use Cake\ORM\TableRegistry; - - // Now $articles is an instance of our ArticlesTable class. - // Prior to 3.6 use TableRegistry::get('Articles') - $articles = TableRegistry::getTableLocator()->get('Articles'); - -Ahora que tenemos una clase Table concreta, probablemente querramos usar una clase Entity concreta. -Las clases Entity permiten definir métodos de acceso y mutación, lógica para registros individuales y mucho más. -Comenzaremos agregando las siguientes líneas en **src/Model/Entity/Article.php**:: - - namespace App\Model\Entity; - - use Cake\ORM\Entity; - - class Article extends Entity - { - - } - -Las Entity usan la version CamelCase en singular del nombre de la tabla como su nombre. Ahora que hemos creado una clase Entity, cuando -carguemos entidades de nuestra base de datos, vamos a obtener instancias de nuestra clase Article:: - - use Cake\ORM\TableRegistry; - - // Now an instance of ArticlesTable. - // Prior to 3.6 use TableRegistry::get('Articles') - $articles = TableRegistry::getTableLocator()->get('Articles'); - $query = $articles->find(); - - foreach ($query as $row) { - // Each row is now an instance of our Article class. - echo $row->title; - } - -CakePHP usa convenciones de nombres para asociar las clases Table y Entity. Si necesitas modificar qué entidad utilizada una tabla, -puedes usar el método ``entityClass()`` para especificar el nombre de una clase. - -Vea :doc:`/orm/table-objects` y :doc:`/orm/entities` para más información sobre como utilizar objetos Table y Entity en su aplicación. - -Más información -=============== - -.. toctree:: - :maxdepth: 2 - - orm/database-basics - orm/query-builder - orm/table-objects - orm/entities - orm/retrieving-data-and-resultsets - orm/validation - orm/saving-data - orm/deleting-data - orm/associations - orm/behaviors - orm/schema-system - console-commands/schema-cache diff --git a/es/orm/associations.rst b/es/orm/associations.rst deleted file mode 100644 index fa8b76ce1a..0000000000 --- a/es/orm/associations.rst +++ /dev/null @@ -1,11 +0,0 @@ -Associations - Linking Tables Together -###################################### - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. diff --git a/es/orm/behaviors.rst b/es/orm/behaviors.rst deleted file mode 100644 index d5b14b228a..0000000000 --- a/es/orm/behaviors.rst +++ /dev/null @@ -1,22 +0,0 @@ -Behaviors -######### - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. - -Core Behaviors -============== - -.. toctree:: - :maxdepth: 1 - - /orm/behaviors/counter-cache - /orm/behaviors/timestamp - /orm/behaviors/translate - /orm/behaviors/tree diff --git a/es/orm/behaviors/counter-cache.rst b/es/orm/behaviors/counter-cache.rst deleted file mode 100644 index 54d9f62ef6..0000000000 --- a/es/orm/behaviors/counter-cache.rst +++ /dev/null @@ -1,11 +0,0 @@ -CounterCache Behavior -##################### - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. diff --git a/es/orm/behaviors/timestamp.rst b/es/orm/behaviors/timestamp.rst deleted file mode 100644 index 8a04850cb6..0000000000 --- a/es/orm/behaviors/timestamp.rst +++ /dev/null @@ -1,15 +0,0 @@ -Timestamp Behavior -################## - -.. php:namespace:: Cake\Model\Behavior - -.. php:class:: TimestampBehavior - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. diff --git a/es/orm/behaviors/translate.rst b/es/orm/behaviors/translate.rst deleted file mode 100644 index fd63bfffe9..0000000000 --- a/es/orm/behaviors/translate.rst +++ /dev/null @@ -1,15 +0,0 @@ -Translate -######### - -.. php:namespace:: Cake\Model\Behavior - -.. php:class:: TranslateBehavior - -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. - - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. diff --git a/es/orm/behaviors/tree.rst b/es/orm/behaviors/tree.rst deleted file mode 100644 index 37f97c62cb..0000000000 --- a/es/orm/behaviors/tree.rst +++ /dev/null @@ -1,231 +0,0 @@ -Tree -#### - -.. php:namespace:: Cake\ORM\Behavior - -.. php:class:: TreeBehavior - -Muchas veces nos encontramos frente a la necesidad de tener que almacenar datos jerarquizados en una base de datos. Podría tomar la forma de categorías sin límite de subcategorías, datos relacionados con un sistema de menú multinivel o una representación literal de la jerarquía como un departamento en una empresa. - -Las bases de datos relacionales no son verdaderamente apropiadas para almacenar y recobrar este tipo de datos, pero existen algunas técnicas para hacerlas eficientes y trabajar con una información multinivel. - -El TreeBehavior le ayuda a mantener una estructura de datos jerárquica en la base de datos que puede ser solicitada fácilmente y ayuda a reconstruir los datos bajo una forma de árbol que permite encontrar y visualizar los procesos. - -Prerrequisitos -============== -Ese behavior requiere que las siguientes columnas estén presentes en la tabla: - -- ``parent_id`` (nullable) La columna que contiene el ID del registro padre -- ``lft`` (integer, signed) Utilizado para mantener la estructura en forma de árbol -- ``rght`` (integer, signed) Utilizado para mantener la estructura en forma de árbol - -Usted puede configurar el nombre de esos campos. Encontrará más información sobre la significación de los campos y sobre la manera de utilizarlos en este artículo que describe la `MPTT logic `_ - -Advertencia - -Por el momento, TreeBehavior no soporta las llaves primarias composites. - -Rápido vistazo -============== - -Active el Tree behavior agregándolo a la Tabla donde usted desea almacenar los datos jerarquizados en:: - - class CategoriesTable extends Table - { - public function initialize(array $config) - { - $this->addBehavior('Tree'); - } - } - -Tras agregarlas, puede dejar que CakePHP construya la estructura interna si la tabla ya contiene algunos registros:: - - // Prior to 3.6 use TableRegistry::get('Categories') - $categories = TableRegistry::getTableLocator()->get('Categories'); - $categories->recover(); - -Usted puede comprobar que funciona recuperando cualquier registro de la tabla y preguntando cuantos descendientes posee:: - - $node = $categories->get(1); - echo $categories->childCount($node); - -Obtener una lista plana de los descendientes de un nodo es igual de fácil:: - - $descendants = $categories->find('children', ['for' => 1]); - - foreach ($descendants as $category) { - echo $category->name . "\n"; - } - -En cambio, si necesita una lista enlazada donde los hijos de cada nodo están anidados en una jerarquía, usted puede utilizar el finder ‘threaded’:: - - $children = $categories - ->find('children', ['for' => 1]) - ->find('threaded') - ->toArray(); - - foreach ($children as $child) { - echo "{$child->name} has " . count($child->children) . " direct children"; - } - -Recorrer los resultados encadenados requiere generalmente funciónes recursivas, pero si usted necesita solamente un conjunto de resultados que contenga un campo único a partir de cada nivel para obtener una lista, en un `. CakePHP fournit une méthode simple à utiliser -pour générer des listes de données:: - - // Dans un controller ou dans une méthode de table. - $query = $articles->find('list'); - $data = $query->toArray(); - - // Les données ressemblent maintenant à ceci - $data = [ - 1 => 'Premier post', - 2 => 'Mon deuxième article', - ]; - -Sans autre option, les clés de ``$data`` correspondront à la clé primaire de -votre table, tandis que les valeurs seront celles du champ désigné dans le -paramètre 'displayField' de la table. Le 'displayField' par défaut est ``title`` -ou ``name``. Vous pouvez utiliser la méthode ``setDisplayField()`` de la table -pour configurer le champ à afficher:: - - class ArticlesTable extends Table - { - - public function initialize(array $config): void - { - $this->setDisplayField('label'); - } - } - -Au moment où vous appelez ``list``, vous pouvez configurer les champs utilisés -comme clé et comme valeur respectivement avec les options ``keyField`` et -``valueField``:: - - // Dans un controller ou dans une méthode de table. - $query = $articles->find('list', [ - 'keyField' => 'slug', - 'valueField' => 'label' - ]); - $data = $query->toArray(); - - // Les données ressemblent maintenant à - $data = [ - 'premier-post' => 'Premier post', - 'mon-deuxieme-article' => 'Mon deuxième article', - ]; - -Les résultats peuvent être regroupés. C'est utile quand vous voulez organiser -valeurs en sous-groupes, ou que vous voulez construire des elements -```` avec ``FormHelper``:: - - // Dans un controller ou dans une méthode de table. - $query = $articles->find('list', [ - 'keyField' => 'slug', - 'valueField' => 'label', - 'groupField' => 'author_id' - ]); - $data = $query->toArray(); - - // Les données ressemblent maintenant à - $data = [ - 1 => [ - 'premier-post' => 'Premier post', - 'mon-deuxieme-article' => 'Mon deuxième article', - ], - 2 => [ - // Les articles d'autres auteurs. - ] - ]; - -Vous pouvez aussi créer une liste de données à partir d'associations pouvant -être réalisées avec une jointure:: - - $query = $articles->find('list', [ - 'keyField' => 'id', - 'valueField' => 'author.name' - ])->contain(['Authors']); - -Les expressions ``keyField``, ``valueField``, et ``groupField`` font référence -au chemin des attributs dans les entités, et non sur des colonnes de la base de -données. Vous pouvez donc utiliser des champs virtuels dans les résultats de -``find(list)``. - -Personnaliser la Sortie Clé-Valeur ----------------------------------- - -Pour finir, il est possible d'utiliser les closures pour accéder aux méthodes de -mutation des entities dans vos finds list. :: - - // Dans votre Entity Authors, créez un champ virtuel - // à utiliser en tant que champ à afficher: - protected function _getLibelle() - { - return $this->_fields['first_name'] . ' ' . $this->_fields['last_name'] - . ' / ' . __('User ID %s', $this->_fields['user_id']); - } - -Cet exemple montre l'utilisation de la méthode accesseur ``_getLabel()`` -dans l'entity Author. :: - - // Dans vos finders/controller: - $query = $articles->find('list', [ - 'keyField' => 'id', - 'valueField' => function ($article) { - return $article->author->get('label'); - } - ]) - ->contain('Authors'); - - -Vous pouvez aussi récupérer le libellé directement dans la liste en utilisant. :: - - // Dans AuthorsTable::initialize(): - $this->setDisplayField('label'); // Va utiliser Author::_getLabel() - // Dans vos finders/controller: - $query = $authors->find('list'); // Va utiliser AuthorsTable::getDisplayField() - -Trouver des Données Filées -========================== - -Le finder ``find('threaded')`` retourne des entities imbriquées qui sont filées -ensemble grâce à un champ servant de clé. Par défaut, ce champ est -``parent_id``. Ce finder vous permet d'accéder aux données stockées dans une -table de style 'liste adjacente'. Toutes les entities qui correspondent à un -``parent_id`` donné sont placées sous l'attribut ``children``:: - - // Dans un controller ou dans une méthode table. - $query = $comments->find('threaded'); - - // Expanded les valeurs par défaut - $query = $comments->find('threaded', [ - 'keyField' => $comments->primaryKey(), - 'parentField' => 'parent_id' - ]); - $results = $query->toArray(); - - echo count($results[0]->children); - echo $results[0]->children[0]->comment; - -Les clés ``parentField`` et ``keyField`` peuvent être utilisées pour définir -les champs qui vont servir au filage. - -.. tip:: - Si vous devez gérer des données en arbre plus compliquées, utiliser de - préférence le :doc:`/orm/behaviors/tree`. - -.. _custom-find-methods: - -Méthodes Finder Personnalisées -============================== - -Les exemples ci-dessus montrent comment utiliser les finders intégrés ``all`` -et ``list``. Cependant, il est possible et recommandé d'implémenter vos propres -méthodes finder. Les méthodes finder sont idéales pour rassembler des requêtes -utilisées couramment, ce qui vous permet de fournir une abstraction facile à -utiliser pour de nombreux détails de la requête. Les méthodes finder sont -définies en créant des méthodes nomméed par convention ``findFoo``, où ``Foo`` -est le nom que vous souhaitez donner à votre finder. Par exemple, si nous -voulons ajouter à notre table d'articles un finder servant à rechercher parmi -les articles d'un certain auteur, nous ferions ceci:: - - use Cake\ORM\Query; - use Cake\ORM\Table; - - class ArticlesTable extends Table - { - - public function findEcritPar(Query $query, array $options) - { - $user = $options['user']; - - return $query->where(['author_id' => $user->id]); - } - - } - - $query = $articles->find('ecritPar', ['user' => $userEntity]); - -Les méthodes finder peuvent modifier la requête comme il se doit, ou utiliser -l'argument ``$options`` pour personnaliser l'opération finder selon la logique -souhaitée. Vous pouvez aussi 'empiler' les finders, ce qui permet d'exprimer -sans effort des requêtes complexes. En supposant que vous ayez à la fois des -finders 'published' et 'recent', vous pourriez très bien faire ceci:: - - $query = $articles->find('published')->find('recent'); - -Bien que tous les exemples que nous avons cités jusqu'ici montrent des finders -qui s'appliquent sur des tables, il est aussi possible de définir des méthodes -finder sur des :doc:`/orm/behaviors`. - -Si vous devez modifier les résultats après qu'ils ont été récupérés, vous -pouvez utiliser une fonction :ref:`map-reduce`. Les fonctionnalités de map -reduce remplacent le callback 'afterFind' présent dans les précédentes versions -de CakePHP. - -.. _dynamic-finders: - -Finders Dynamiques -================== - -L'ORM de CakePHP fournit des finders construits dynamiquement, qui vous -permettent d'exprimer des requêtes simples sans écrire de code particulier. -Par exemple, admettons que vous recherchiez un utilisateur à partir de son -*username*. Vous pourriez procéder ainsi:: - - // Dans un controller - // Les deux appels suivants sont équivalents. - $query = $this->Users->findByUsername('joebob'); - $query = $this->Users->findAllByUsername('joebob'); - -Les finders dynamiques permettent même de filtrer sur plusieurs champs à la -fois:: - - $query = $users->findAllByUsernameAndApproved('joebob', 1); - -Vous pouvez aussi créer des conditions ``OR``:: - - $query = $users->findAllByUsernameOrEmail('joebob', 'joe@example.com'); - -Vous pouvez utiliser des conditions OR ou AND, mais vous ne pouvez pas combiner -les deux dans un même finder dynamique. Les autres options de requête comme -``contain`` ne sont pas non plus supportées par les finders dynamiques. Vous -devrez utiliser des :ref:`custom-find-methods` pour encapsuler des requêtes plus -complexes. Pour finir, vous pouvez aussi combiner les finders dynamiques avec -des finders personnalisés:: - - $query = $users->findTrollsByUsername('bro'); - -Ce qui se traduirait ainsi:: - - $users->find('trolls', [ - 'conditions' => ['username' => 'bro'] - ]); - -Une fois que vous avez un objet query créé à partir d'un finder dynamique, vous -devrez appeler ``first()`` si vous souhaitez récupérer le premier résultat. - -.. note:: - - Bien que les finders dynamiques facilitent la gestion des requêtes, ils - introduisent de petites contraintes. Vous ne pouvez pas appeler des méthodes - ``findBy`` à partir d'un objet Query. Si vous voulez enchaîner des finders, - le finder dynamique doit donc être appelé en premier. - -Récupérer les Données Associées -=============================== - -Pour récupérer des données associées, ou filtrer selon les données associées, il -y a deux façons de procéder: - -- utiliser des fonctions de requêtage de l'ORM de CakePHP telles que - ``contain()`` et ``matching()`` -- utiliser des fonctions de jointures telles que ``innerJoin()``, - ``leftJoin()``, et ``rightJoin()``. - -Vous devriez utiliser ``contain()`` quand vous voulez charger le modèle primaire -et ses données associées. Bien que ``contain()`` vous permette d'appliquer des -conditions supplémentaires sur les associations chargées, vous ne pouvez pas -filtrer le modèle primaire en fonction des données associées. Pour plus de -détails sur ``contain()``, consultez :ref:`eager-loading-associations`. - -Vous devriez utiliser ``matching()`` quand vous souhaitez filtrer le modèle -primaire en fonction des données associées. Par exemple, quand vous voulez -charger tous les articles auxquels est associé un tag spécifique. Pour plus de -détails sur ``matching()``, consultez :ref:`filtering-by-associated-data`. - -Si vous préférez utiliser les fonctions de jointure, vous pouvez consulter -:ref:`adding-joins` pour plus d'informations. - -.. _eager-loading-associations: - -Eager Loading des Associations Via Contain -========================================== - -Par défaut, CakePHP ne charge **aucune** donnée associée lors de l'utilisation -de ``find()``. Vous devez faire un 'contain' ou charger en eager chaque -association que vous souhaitez voir figurer dans vos résultats. - -.. start-contain - -L'eager loading aide à éviter la plupart des problèmes potentiels de performance -qui entourent le lazy loading dans un ORM. Les requêtes générées par eager -loading peuvent davantage tirer parti des jointures, ce qui permet de créer des -requêtes plus efficaces. Dans CakePHP, vous utilisez la méthode 'contain' pour -indiquer quelles associations doivent être chargées en eager:: - - // Dans un controller ou une méthode de table. - - // En option du find() - $query = $articles->find('all', ['contain' => ['Authors', 'Comments']]); - - // En méthode sur un objet query - $query = $articles->find('all'); - $query->contain(['Authors', 'Comments']); - -Ceci va charger les auteurs et commentaires liés à chaque article du *result -set*. Vous pouvez charger des associations imbriquées en utilisant les tableaux -imbriqués pour définir les associations à charger:: - - $query = $articles->find()->contain([ - 'Authors' => ['Addresses'], 'Comments' => ['Authors'] - ]); - -Au choix, vous pouvez aussi exprimer des associations imbriquées en utilisant la -notation par points:: - - $query = $articles->find()->contain([ - 'Authors.Addresses', - 'Comments.Authors' - ]); - -Vous pouvez charger les associations en eager aussi profondément que vous le -souhaitez:: - - $query = $produits->find()->contain([ - 'Shops.Cities.Countries', - 'Shops.Managers' - ]); - -Vous pouvez sélectionner des champs de toutes les associations en utilisant -plusieurs appels à ``contain()``:: - - $query = $this->find()->select([ - 'Realestates.id', - 'Realestates.title', - 'Realestates.description' - ]) - ->contain([ - 'RealestatesAttributes' => [ - 'Attributes' => [ - 'fields' => [ - // Les champs dépendant d'un alias doivent inclure le préfixe - // du modèle dans contain() pour être mappés correctement. - 'Attributes__name' => 'attr_name' - ] - ] - ] - ]) - ->contain([ - 'RealestatesAttributes' => [ - 'fields' => [ - 'RealestatesAttributes.realestate_id', - 'RealestatesAttributes.value' - ] - ] - ]) - ->where($condition); - -Si vous avez besoin de réinitialiser les *contain* sur une requête, vous pouvez -définir le second argument à ``true``:: - - $query = $articles->find(); - $query->contain(['Authors', 'Comments'], true); - -.. note:: - - Les noms d'association dans les appels à ``contain()`` doivent respecter la - casse (majuscules/minuscules) avec lequelle votre association a été définie, - et non pas selon le nom de la propriété utilisée pour accéder aux données - associées depuis l'entity. Par exemple, si vous avez déclaré une association - par ``belongsTo('Users')``, alors vous devez utiliser ``contain('Users')`` - et pas ``contain('users')`` ni ``contain('user')``. - -Passer des Conditions à Contain -------------------------------- - -Avec l'utilisation de ``contain()``, vous pouvez restreindre les données -retournées par les associations et les filtrer par conditions. Pour spécifier -des conditions, passez une fonction anonyme qui reçoit en premier argument la -query, de type ``\Cake\ORM\Query``:: - - // Dans un controller ou une méthode de table. - $query = $articles->find()->contain('Comments', function (Query $q) { - return $q - ->select(['contenu', 'author_id']) - ->where(['Comments.approved' => true]); - }); - -Cela fonctionne aussi pour la pagination au niveau du Controller:: - - $this->paginate['contain'] = [ - 'Comments' => function (Query $query) { - return $query->select(['body', 'author_id']) - ->where(['Comments.approved' => true]); - } - ]; - -.. warning:: - - Si vous constatez qu'il manque des entités associées, vérifiez que les - champs de clés étrangères sont bien sélectionnés dans la requête. Sans les - clés étrangères, l'ORM ne peut pas retrouver les lignes correspondantes. - -Il est aussi possible de restreindre les associations imbriquées en utilisant la -notation par point:: - - $query = $articles->find()->contain([ - 'Comments', - 'Authors.Profiles' => function (Query $q) { - return $q->where(['Profiles.is_published' => true]); - } - ]); - -Dans cet exemple, vous obtiendrez les auteurs même s'ils n'ont pas -de profil publié. Pour ne récupérer que les auteurs avec un profil publié, -utilisez :ref:`matching() `. - -Si vous avez des finders personnalisés dans votre table associée, -vous pouvez les utiliser à l'intérieur de ``contain()``:: - - // Récupère tous les articles, mais récupère seulement les commentaires qui - // sont approuvés et populaires. - $query = $articles->find()->contain('Comments', function ($q) { - return $q->find('approved')->find('popular'); - }); - -.. note:: - - Pour les associations ``BelongsTo`` et ``HasOne``, seules les clauses - ``where`` et ``select`` sont utilisées lors du chargement par ``contain()``. - Avec ``HasMany`` et ``BelongsToMany``, toutes les clauses sont valides, - telles que ``order()``. - -Vous pouvez contrôler plus que les simples clauses utilisées par ``contain()``. -Si vous passez un tableau avec l'association, vous pouvez surcharger -``foreignKey``, ``joinType`` et ``strategy``. Reportez-vous à -:doc:`/orm/associations` pour plus de détails sur la valeur par défaut et les -options de chaque type d'association. - -Vous pouvez passer ``false`` comme nouvelle valeur de ``foreignKey`` pour -désactiver complètement les contraintes liées aux clés étrangères. -Utilisez l'option ``queryBuilder`` pour personnaliser la requête quand vous -passez un tableau:: - - $query = $articles->find()->contain([ - 'Authors' => [ - 'foreignKey' => false, - 'queryBuilder' => function (Query $q) { - return $q->where(/* ... */); // Conditions complètes pour le filtrage - } - ] - ]); - -Si vous avez limité les champs que vous chargez avec ``select()`` mais que -vous souhaitez aussi charger les champs des associations avec contain, -vous pouvez passer l'objet association à ``select()``:: - - // Sélectionne id & title de articles, mais aussi tous les champs de Users. - $query = $articles->find() - ->select(['id', 'title']) - ->select($articles->Users) - ->contain(['Users']); - -Autre possibilité, si vous avez des associations multiples, vous pouvez utiliser -``enableAutoFields()``:: - - // Sélectionne id & title de articles, mais tous les champs de - // Users, Comments et Tags. - $query->select(['id', 'title']) - ->contain(['Comments', 'Tags']) - ->enableAutoFields(true) - ->contain(['Users' => function(Query $q) { - return $q->autoFields(true); - }]); - -Trier les Associations Contain ------------------------------- - -Quand vous chargez des associations HasMany et BelongsToMany, vous pouvez -utiliser l'option ``sort`` pour trier les données dans ces associations:: - - $query->contain([ - 'Comments' => [ - 'sort' => ['Comments.created' => 'DESC'] - ] - ]); - -.. end-contain - -.. _filtering-by-associated-data: - -Filtrer par les Données Associées Via Matching et Joins -======================================================= - -.. start-filtering - -Un cas de requête couramment utilisé avec les associations consiste à trouver -les enregistrements qui correspondent à certaines données associées. Par exemple -si vous avez une association 'Articles belongsToMany Tags', vous voudrez -probablement trouver les Articles qui portent le tag *CakePHP*. C'est -extrêmement simple à faire avec l'ORM de CakePHP:: - - // Dans un controller ou une méthode de table. - - $query = $articles->find(); - $query->matching('Tags', function ($q) { - return $q->where(['Tags.name' => 'CakePHP']); - }); - -Vous pouvez aussi appliquer cette stratégie aux associations HasMany. Par -exemple si 'Authors HasMany Articles', vous pouvez trouver tous les auteurs -ayant publié un article récemment en écrivant ceci:: - - $query = $authors->find(); - $query->matching('Articles', function ($q) { - return $q->where(['Articles.created >=' => new DateTime('-10 days')]); - }); - -La syntaxe de ``contain()``, qui doit déjà vous être familière, permet aussi de -filtrer des associations imbriquées:: - - // Dans un controller ou une méthode de table. - $query = $produits->find()->matching( - 'Shops.Cities.Countries', function ($q) { - return $q->where(['Countries.name' => 'Japon']); - } - ); - - // Récupère les articles qui ont été commentés par 'markstory', - // en passant une variable. - // Utiliser la notation par points plutôt que des appels imbriqués à matching() - $username = 'markstory'; - $query = $articles->find()->matching('Comments.Users', function ($q) use ($username) { - return $q->where(['username' => $username]); - }); - -.. note:: - - Dans la mesure où cette fonction va créer un ``INNER JOIN``, il serait - judicieux d'utiliser ``distinct`` dans la requête. Sinon, vous risquez - d'obtenir des doublons si les conditions posées ne l'excluent pas par - principe. Dans notre exemple, cela peut être le cas si un utilisateur - commente plusieurs fois le même article. - -Les données des associations qui correspondent aux conditions (données -*matchées*) seront disponibles -dans l'attribut ``_matchingData`` des entities. Si vous utilisez à la fois -``match`` et ``contain`` sur la même association, vous pouvez vous attendre à -avoir à la fois la propriété ``_matchingData`` et la propriété standard -d'association dans vos résultats. - -Utiliser innerJoinWith ----------------------- - -Utiliser la fonction ``matching()``, comme nous l'avons vu précédemment, va -créer un ``INNER JOIN`` avec l'association spécifiée et va aussi charger les -champs dans un ensemble de résultats. - -Il peut arriver que vous veuillez utiliser ``matching()`` mais que vous n'êtes -pas intéressé par le chargement des champs de l'association. Dans ce cas, vous -pouvez utiliser ``innerJoinWith()``:: - - $query = $articles->find(); - $query->innerJoinWith('Tags', function ($q) { - return $q->where(['Tags.name' => 'CakePHP']); - }); - -La méthode ``innerJoinWith()`` fonctionne de la même manière que ``matching()``, -ce qui signifie que vous pouvez utiliser la notation par points pour faire des -jointures pour les associations imbriquées profondément:: - - $query = $products->find()->innerJoinWith( - 'Shops.Cities.Countries', function ($q) { - return $q->where(['Countries.name' => 'Japon']); - } - ); - -Si vous voulez à la fois poser des conditions sur certains champs de -l'association et charger d'autres champs de cette même association, vous pouvez -parfaitement combiner ``innerJoinWith()`` et ``contain()``. -L'exemple ci-dessous filtre les Articles qui ont des Tags spécifiques et charge -ces Tags:: - - $filter = ['Tags.name' => 'CakePHP']; - $query = $articles->find() - ->distinct($articles->getPrimaryKey()) - ->contain('Tags', function (Query $q) use ($filter) { - return $q->where($filter); - }) - ->innerJoinWith('Tags', function (Query $q) use ($filter) { - return $q->where($filter); - }); - -.. note:: - Si vous utilisez ``innerJoinWith()`` et que vous voulez sélectionner des - champs de cette association avec ``select()``, vous devez utiliser un alias - pour les noms des champs:: - - $query - ->select(['country_name' => 'Countries.name']) - ->innerJoinWith('Countries'); - - Sinon, vous verrez les données dans ``_matchingData``, comme cela a été - décrit ci-dessous à propos de ``matching()``. C'est un angle mort de - ``matching()``, qui ne sait pas que vous avez sélectionné des champs. - -.. warning:: - Vous ne devez pas combiner ``innerJoinWith()`` and ``matching()`` pour la - même association. Cela produirait de multiple requêtes ``INNER JOIN`` et ne - réaliserait pas ce que vous en attendez. - -Utiliser notMatching --------------------- - -L'opposé de ``matching()`` est ``notMatching()``. Cette fonction va changer -la requête pour qu'elle filtre les résultats qui n'ont pas de relation avec -l'association spécifiée:: - - // Dans un controller ou une méthode de table. - - $query = $articlesTable - ->find() - ->notMatching('Tags', function ($q) { - return $q->where(['Tags.name' => 'ennuyeux']); - }); - -L'exemple ci-dessus va trouver tous les articles qui n'ont pas été taggés avec -le mot ``ennuyeux``. Vous pouvez aussi utiliser cette méthode avec les -associations HasMany. Vous pouvez, par exemple, trouver tous les auteurs qui -n'ont publié aucun article dans les 10 derniers jours:: - - $query = $authorsTable - ->find() - ->notMatching('Articles', function ($q) { - return $q->where(['Articles.created >=' => new \DateTime('-10 days')]); - }); - -Il est aussi possible d'utiliser cette méthode pour filtrer les enregistrements -qui ne matchent pas des associations imbriquées. Par exemple, vous pouvez -trouver les articles qui n'ont pas été commentés par un utilisateur précis:: - - $query = $articlesTable - ->find() - ->notMatching('Comments.Users', function ($q) { - return $q->where(['username' => 'jose']); - }); - -Puisque les articles n'ayant absolument aucun commentaire satisfont aussi cette -condition, vous aurez intérêt à combiner ``matching()`` et ``notMatching()`` -dans cette requête. L'exemple suivant recherchera les articles ayant au moins un -commentaire, mais non commentés par un utilisateur précis:: - - $query = $articlesTable - ->find() - ->notMatching('Comments.Users', function ($q) { - return $q->where(['username' => 'jose']); - }) - ->matching('Comments'); - -.. note:: - - Comme ``notMatching()`` va créer un ``LEFT JOIN``, vous pouvez envisager - d'appeler ``distinct`` sur la requête pour éviter d'obtenir des lignes - dupliquées. - -Gardez à l'esprit que le contraire de la fonction ``matching()``, -``notMatching()``, ne va pas ajouter de données à la propriété ``_matchingData`` -dans les résultats. - -Utiliser leftJoinWith ---------------------- - -Dans certaines situations, vous aurez à calculer un résultat à partir d'une -association, sans avoir à charger tous ses enregistrements. Par -exemple, si vous voulez charger le nombre total de commentaires d'un article, en -parallèle des données de l'article, vous pouvez utiliser la fonction -``leftJoinWith()``:: - - $query = $articlesTable->find(); - $query->select(['total_comments' => $query->func()->count('Comments.id')]) - ->leftJoinWith('Comments') - ->group(['Articles.id']) - ->enableAutoFields(true); - -Le résultat de cette requête contiendra les données de l'article et la propriété -``total_comments`` pour chacun d'eux. - -``leftJoinWith()`` peut aussi être utilisée avec des associations imbriquées. -C'est utile par exemple pour rechercher, pour chaque auteur, le nombre -d'articles taggés avec un certain mot:: - - $query = $authorsTable - ->find() - ->select(['total_articles' => $query->func()->count('Articles.id')]) - ->leftJoinWith('Articles.Tags', function ($q) { - return $q->where(['Tags.name' => 'redoutable']); - }) - ->group(['Authors.id']) - ->enableAutoFields(true); - -Cette fonction ne va charger aucune colonne des associations spécifiées dans les -résultats. - -.. end-filtering - -Changer les Stratégies de Récupération -====================================== - -Comme cela a été dit, vous pouvez personnaliser la stratégie (*strategy*) -utilisée par une association dans un ``contain()``. - -Si vous observez les options des :doc:`associations ` -``BelongsTo`` et ``HasOne``, vous constaterez que la stratégie par défaut 'join' -et le type de jointure (``joinType``) 'INNER' peuvent être remplacés par -'select':: - - $query = $articles->find()->contain([ - 'Comments' => [ - 'strategy' => 'select', - ] - ]); - -Cela peut être utile lorsque vous avez besoin de conditions qui ne font pas bon -ménage avec une jointure. Cela ouvre aussi la possibilité de requêter des tables -entre lesquelles vous n'avez pas l'autorisation de faire des jointures, par -exemple des tables se trouvant dans deux bases de données différentes. - -Habituellement, vous définisser la stratégie d'une association quand vous la -définissez, dans la méthode ``Table::initialize()``, mais vous pouvez changer -manuellement la stratégie de façon permanente:: - - $articles->Comments->setStrategy('select'); - -Récupération Avec la Stratégie de Sous-Requête ----------------------------------------------- - -Quand vos tables grandissent en taille, la récupération des associations -peut ralentir sensiblement, en particulier si vous faites de grandes requêtes en -une fois. Un bon moyen d'optimiser le chargement des associations ``hasMany`` et -``belongsToMany`` est d'utiliser la stratégie ``subquery``:: - - $query = $articles->find()->contain([ - 'Comments' => [ - 'strategy' => 'subquery', - 'queryBuilder' => function ($q) { - return $q->where(['Comments.approved' => true]); - } - ] - ]); - -Le résultat va rester le même que pour la stratégie par défaut, mais -ceci peut grandement améliorer la requête et son temps de récupération dans -certaines bases de données, en particulier cela va permettre de récupérer des -grandes portions de données en même temps dans les bases de données qui -limitent le nombre de paramètres liés par requête, comme **Microsoft SQL -Server**. - -Lazy loading des Associations -============================= - -CakePHP propose le chargement de vos associations en eager. Cependant il y a des -cas où vous aurez besoin de charger les associations en lazy. Consultez les -sections :ref:`lazy-load-associations` et -:ref:`loading-additional-associations` pour plus d'informations. - -Travailler sur les Résultats (Result Sets) -=========================================== - -Une fois qu'une requête est exécutée avec ``all()``, vous récupérez une -instance de :php:class:`Cake\\ORM\\ResultSet`. Cet objet propose des outils -puissants pour manipuler les données renvoyées par vos requêtes. Comme les -objets Query, un ResultSet est une -:doc:`Collection ` et vous -pouvez donc appeler sur celui-ci n'importe quelle méthode propre aux -collections. - -Les objets ResultSet vont charger les lignes paresseusement (*lazily*) à partir -de la requête préparée sous-jacente. Par défaut, les résultats seront mis en -mémoire tampon, vous permettant ainsi de parcourir les résultats plusieurs fois, -ou de mettre les résultats en cache et d'itérer dessus. Si vous travaillez sur -un ensemble de données trop large pour tenir en mémoire, vous pouvez désactiver -la mise en mémoire tampon depuis la requête pour travailler sur les résultats à -la volée:: - - $query->disableBufferedResults(); - -Stopper la mise en mémoire tampon appelle quelques mises en garde: - -#. Vous ne pourrez itérer les résultats qu'une seule fois. -#. Vous ne pourrez pas non plus itérer et mettre en cache les résultats. -#. La mise en mémoire tampon ne peut pas être désactivée pour les requêtes qui - chargent en eager les associations hasMany ou belongsToMany, puisque ces - types d'association nécessitent le chargement en eager de tous les résultats - afin de générer les requêtes dépendantes. - -.. warning:: - - Traiter les résultats à la volée continuera d'allouer l'espace mémoire - nécessaire pour la totalité des résultats lorsque vous utilisez PostgreSQL - et SQL Server. Ceci est dû à des limitations dans PDO. - -Les ResultSets vous permettent de mettre en cache, de sérialiser ou d'encoder en -JSON les résultats:: - - // Dans un controller ou une méthode de table. - $results = $query->all(); - - // Serialisé - $serialized = serialize($results); - - // Json - $json = json_encode($results); - -La sérialisation des ResultSets et l'encodage en JSON fonctionnent comme vous -pouvez vous y attendre. Les données sérialisées peuvent être désérialisées en un -ResultSet fonctionnel. La conversion en JSON respecte la configuration des -champs cachés et des champs virtuels dans tous les objets entity inclus dans le -ResultSet. - -Les ResultSets sont des objets 'Collection' et supportent les mêmes méthodes que -les :doc:`objets collection `. Par exemple, vous -pouvez extraire une liste des tags uniques sur une collection d'articles en -exécutant:: - - // Dans un controller ou une méthode de table. - $query = $articles->find()->contain(['Tags']); - - $reducer = function ($output, $value) { - if (!in_array($value, $output)) { - $output[] = $value; - } - - return $output; - }; - - $uniqueTags = $query->all() - ->extract('tags.name') - ->reduce($reducer, []); - -Ci-dessous quelques autres exemples de méthodes de collection utilisées avec des -ResultSets:: - - // Filtre les lignes sur une propriété calculée - $filtered = $results->filter(function ($row) { - return $row->is_recent; - }); - - // Crée un tableau associatif depuis les propriétés du résultat - $results = $articles->find()->contain(['Authors'])->all(); - - $authorsList = $results->combine('id', 'author.name'); - -Le chapitre :doc:`/core-libraries/collections` comporte plus de détails sur -ce qu'on peut faire avec les fonctionnalités des collections sur des ResultSets. -La section :ref:`format-results` montre comment ajouter des champs calculés, ou -remplacer le ResutSet. - -Récupérer le Premier & Dernier enregistrement à partir d'un ResultSet ---------------------------------------------------------------------- - -Vous pouvez utiliser les méthodes ``first()`` et ``last()`` pour récupérer -respectivement le premier et le dernier enregistrement d'un ResultSet:: - - $result = $articles->find('all')->all(); - - // Récupère le premier et/ou le dernier résultat. - $row = $result->first(); - $row = $result->last(); - -Récupérer une Ligne Spécifique d'un ResultSet ---------------------------------------------- - -Vous pouvez utiliser ``skip()`` et ``first()`` pour récupérer un enregistrement -arbitraire à partir d'un ensemble de résultats:: - - $result = $articles->find('all')->all(); - - // Récupère le 5ème enregistrement - $row = $result->skip(4)->first(); - -Vérifier si une Requête ou un ResultSet est vide ------------------------------------------------- - -Vous pouvez utiliser la méthode ``isEmpty()`` sur un objet Query ou ResultSet -pour savoir s'il contient au moins une ligne. Appeler ``isEmpty()`` sur un -objet Query va exécuter la requête:: - - // Vérifie une requête. - $query->isEmpty(); - - // Vérifie les résultats. - $results = $query->all(); - $results->isEmpty(); - -.. _loading-additional-associations: - -Charger des Associations Supplémentaires ----------------------------------------- - -Une fois que vous avez créé un ResultSet, vous pourriez vouloir charger en eager -des associations supplémentaires. C'est le moment idéal pour charger -paresseusement des données en eager. Vous pouvez charger des associations -supplémentaires en utilisant ``loadInto()``:: - - $articles = $this->Articles->find()->all(); - $withMore = $this->Articles->loadInto($articles, ['Comments', 'Users']); - -Vous pouvez charger en eager des données additionnelles dans une entity unique -ou une collection d'entities. - -.. _map-reduce: - -Modifier les Résultats avec Map/Reduce -====================================== - -La plupart du temps, les opérations ``find`` nécessitent un traitement -après-coup des données de la base de données. Les accesseurs (*getters*) des -entities peuvent s'occuper de générer la plupart des propriétés virtuelles ou -des formats de données particuliers ; néanmoins vous serez parfois amené à -changer la structure des données de façon plus radicale. - -Pour ces situations, l'objet ``Query`` propose la méthode ``mapReduce()``, qui -est une façon de traiter les résultats après qu'ils ont été récupérés dans la -base de données. - -Un exemple classique de changement de structure de données est le regroupement -des résultats selon certaines conditions. Nous pouvons utiliser la fonction -``mapReduce()`` pour cette tâche. Nous avons besoin de deux -fonctions appelées ``$mapper`` et ``$reducer``. -La fonction ``$mapper`` reçoit en premier argument un résultat de la base de -données, en second argument la clé d'itération et en troisième elle reçoit une -instance de la routine ``MapReduce`` en train d'être éxecutée:: - - $mapper = function ($article, $key, $mapReduce) { - $status = 'published'; - if ($article->isDraft() || $article->isInReview()) { - $status = 'unpublished'; - } - $mapReduce->emitIntermediate($article, $status); - }; - -Dans cet exemple, ``$mapper`` calcule le statut d'un article, soit publié soit -non publié. Ensuite il appelle ``emitIntermediate()`` sur l'instance -``MapReduce``. Cette méthode insère l'article dans la liste des articles soit -sous l'étiquette 'publié', soit sous l'étiquette 'non publié'. - -La prochaine étape dans le processus de map-reduce est la consolidation des -résultats finaux. La fonction ``$reducer`` sera appelée sur chaque statut créé -dans le mapper, de manière à ce que vous puissiez faire des traitements -supplémentaires. Cette fonction va recevoir la liste des articles d'un certain -"tas" en premier paramètre, le nom du tas à traiter en second paramètre, et à -nouveau, comme pour la fonction ``mapper()``, l'instance de la routine -``MapReduce`` en troisième paramètre. Dans notre exemple, nous n'avons pas -besoin de retraiter les listes d'articles une fois qu'elles sont constituées, -donc nous nous contentons d'émettre (*emit*) les résultats finaux:: - - $reducer = function ($articles, $status, $mapReduce) { - $mapReduce->emit($articles, $status); - }; - -Pour finir, nous pouvons passer ces deux fonctions pour exécuter le -regroupement:: - - $articlesByStatus = $articles->find() - ->where(['author_id' => 1]) - ->mapReduce($mapper, $reducer) - ->all(); - - foreach ($articlesByStatus as $status => $articles) { - echo sprintf("Il y a %d articles avec le statut %s", count($articles), $status); - } - -Ce qui va afficher la sortie suivante:: - - Il y a 4 articles avec le statut published - Il y a 5 articles avec le statut unpublished - -Bien sûr, ceci est un exemple simple qui pourrait être résolu d'une autre -façon sans l'aide d'un traitement map-reduce. Maintenant, regardons un autre -exemple dans lequel la fonction reducer sera nécessaire pour faire quelque -chose de plus que d'émettre les résultats. - -Pour calculer les mots mentionnés le plus souvent dans les articles contenant -des informations sur CakePHP, comme d'habitude nous avons besoin d'une fonction -mapper:: - - $mapper = function ($article, $key, $mapReduce) { - if (stripos($article['body'], 'cakephp') === false) { - return; - } - - $words = array_map('strtolower', explode(' ', $article['body'])); - foreach ($words as $word) { - $mapReduce->emitIntermediate($article['id'], $word); - } - }; - -Elle vérifie d'abord si le mot "cakephp" est dans le corps de l'article, et -ensuite coupe le corps en mots individuels. Chaque mot va créer son propre -``tas``, où chaque id d'article sera stocké. Maintenant réduisons nos -résultats pour extraire seulement le décompte du nombre de mots:: - - $reducer = function ($occurrences, $word, $mapReduce) { - $mapReduce->emit(count($occurrences), $word); - } - -Pour finir, nous mettons tout ensemble:: - - $nbMots = $articles->find() - ->where(['published' => true]) - ->andWhere(['publication_date >=' => new DateTime('2014-01-01')]) - ->disableHydration() - ->mapReduce($mapper, $reducer) - ->all(); - -Ceci pourrait retourner un tableau très grand si nous ne purgeons pas les petits -mots, mais cela pourrait ressembler à ceci:: - - [ - 'cakephp' => 100, - 'génial' => 39, - 'impressionnant' => 57, - 'remarquable' => 10, - 'hallucinant' => 83 - ] - -Un dernier exemple et vous serez un expert de map-reduce. Imaginez que vous -ayez une table ``amis`` et que vous souhaitiez trouver les "faux amis" -dans notre base de données ou, autrement dit, des gens qui ne se suivent pas -mutuellement. Commençons avec notre fonction ``mapper()``:: - - $mapper = function ($rel, $key, $mr) { - $mr->emitIntermediate($rel['target_user_id'], $rel['source_user_id']); - $mr->emitIntermediate(-$rel['source_user_id'], $rel['target_user_id']); - }; - -Le tableau intermédiaire ressemblera à ceci:: - - [ - 1 => [2, 3, 4, 5, -3, -5], - 2 => [-1], - 3 => [-1, 1, 6], - 4 => [-1], - 5 => [-1, 1], - 6 => [-3], - ... - ] - -La clé de premier niveau étant un utilisateur, les nombres positifs indiquent -que l'utilisateur suit d'autres utilisateurs et les nombres négatifs qu'il est -suivi par d'autres utilisateurs. - -Maintenant, il est temps de la réduire. Pour chaque appel au reducer, il va -recevoir une liste de followers par utilisateur:: - - $reducer = function ($friends, $user, $mr) { - $fakeFriends = []; - - foreach ($friends as $friend) { - if ($friend > 0 && !in_array(-$friend, $friends)) { - $fakeFriends[] = $friend; - } - } - - if ($fakeFriends) { - $mr->emit($fakeFriends, $user); - } - }; - -Et nous fournissons nos fonctions à la requête:: - - $fakeFriends = $friends->find() - ->disableHydration() - ->mapReduce($mapper, $reducer) - ->all(); - -Ceci retournerait un tableau ressemblant à:: - - [ - 1 => [2, 4], - 3 => [6] - ... - ] - -Ce tableau final signifie, par exemple, que l'utilisateur avec l'id -``1`` suit les utilisateurs ``2`` et ``4``, mais ceux-ci ne suivent pas -``1`` de leur côté. - -Empiler Plusieurs Opérations ----------------------------- - -L'utilisation de `mapReduce` dans une requête ne va pas l'exécuter -immédiatement. L'opération va être enregistrée pour être lancée dès que -l'on tentera de récupérer le premier résultat. -Ceci vous permet de continuer à chainer les méthodes et les filtres -sur la requête même après avoir ajouté une routine map-reduce:: - - $query = $articles->find() - ->where(['published' => true]) - ->mapReduce($mapper, $reducer); - - // Plus loin dans votre app: - $query->where(['created >=' => new DateTime('1 day ago')]); - -C'est particulièrement utile pour construire des méthodes finder personnalisées -comme décrit dans la section :ref:`custom-find-methods`:: - - public function findPublished(Query $query, array $options) - { - return $query->where(['published' => true]); - } - - public function findRecent(Query $query, array $options) - { - return $query->where(['created >=' => new DateTime('1 day ago')]); - } - - public function findCommonWords(Query $query, array $options) - { - // Comme dans l'exemple précédent sur la fréquence des mots - $mapper = ...; - $reducer = ...; - - return $query->mapReduce($mapper, $reducer); - } - - $motsCourant = $articles - ->find('commonWords') - ->find('published') - ->find('recent'); - -De plus, il est aussi possible d'empiler plusieurs opérations ``mapReduce`` -pour une même requête. Par exemple, si nous souhaitons avoir les mots les -plus couramment utilisés pour les articles, mais ensuite les filtrer pour -retourner uniquement les mots qui étaient mentionnés plus de 20 fois tout au -long des articles:: - - $mapper = function ($count, $word, $mr) { - if ($count > 20) { - $mr->emit($count, $word); - } - }; - - $articles->find('commonWords')->mapReduce($mapper)->all(); - -Retirer Toutes les Opérations Map-reduce Empilées -------------------------------------------------- - -Dans certaines circonstances vous pourriez vouloir modifier un objet ``Query`` -pour que les opérations ``mapReduce`` prévues ne soient pas exécutées du tout. -Vous pouvez le faire en appelant la méthode avec les deux paramètres à null et -le troisième paramètre (overwrite) à ``true``:: - - $query->mapReduce(null, null, true); - diff --git a/fr/orm/saving-data.rst b/fr/orm/saving-data.rst deleted file mode 100644 index d705c49dd0..0000000000 --- a/fr/orm/saving-data.rst +++ /dev/null @@ -1,1357 +0,0 @@ -Sauvegarder les Données -####################### - -.. php:namespace:: Cake\ORM - -.. php:class:: Table - :noindex: - -Après avoir :doc:`chargé vos données` vous -voudrez probablement mettre à jour et sauvegarder les changements. - -Coup d'Oeil sur l'Enregistrement des Données -============================================ - -Les applications ont habituellement deux façons d'enregistrer les données. -La première est évidemment d'utiliser des formulaires web et l'autre consiste à -générer ou modifier directement les données dans le code pour l'envoyer à la -base de données. - -Insérer des Données -------------------- - -Le moyen le plus simple d'insérer des données dans une base de données est de -créer une nouvelle entity et de la passer à la méthode ``save()`` de la classe -``Table``:: - - use Cake\ORM\Locator\LocatorAwareTrait; - - $articlesTable = $this->getTableLocator()->get('Articles'); - $article = $articlesTable->newEmptyEntity(); - - $article->title = 'Un nouvel Article'; - $article->body = 'Ceci est le contenu de cet article'; - - if ($articlesTable->save($article)) { - // L'entity $article contient maintenant l'id - $id = $article->id; - } - -Mettre à jour des Données -------------------------- - -La méthode ``save()`` sert également à la mise à jour des données:: - - use Cake\ORM\Locator\LocatorAwareTrait; - - $articlesTable = $this->getTableLocator()->get('Articles'); - $article = $articlesTable->get(12); // Retourne l'article avec l'id 12 - - $article->title = 'Un nouveau titre pour cet article'; - $articlesTable->save($article); - -CakePHP saura s'il doit faire une insertion ou une mise à jour d'après le -résultat de la méthode ``isNew()``. Les entities qui sont récupérées via -``get()`` ou ``find()`` renverront toujours ``false`` lorsqu'on appelle leur -méthode ``isNew()``. - -Enregistrer avec des Associations ---------------------------------- - -Par défaut, la méthode ``save()`` sauvegardera aussi un seul niveau -d'association:: - - $articlesTable = $this->getTableLocator()->get('Articles'); - $author = $articlesTable->Authors->findByUserName('mark')->first(); - - $article = $articlesTable->newEmptyEntity(); - $article->title = 'Un article par mark'; - $article->author = $author; - - if ($articlesTable->save($article)) { - // La valeur de la clé étrangère a été ajoutée automatiquement. - echo $article->author_id; - } - -La méthode ``save()`` est également capable de créer de nouveaux -enregistrements pour les associations:: - - $firstComment = $articlesTable->Comments->newEmptyEntity(); - $firstComment->body = 'Un super article'; - - $secondComment = $articlesTable->Comments->newEmptyEntity(); - $secondComment->body = 'J aime lire ceci!'; - - $tag1 = $articlesTable->Tags->findByName('cakephp')->first(); - $tag2 = $articlesTable->Tags->newEmptyEntity(); - $tag2->name = 'Génial'; - - $article = $articlesTable->get(12); - $article->comments = [$firstComment, $secondComment]; - $article->tags = [$tag1, $tag2]; - - $articlesTable->save($article); - -Associer des Enregistrements Many to Many ------------------------------------------ - -Dans le code ci-dessus il y a déjà un exemple de liaison d'un article vers -deux tags. Il y a un autre moyen de faire la même chose en utilisant la -méthode ``link()`` dans l'association:: - - $tag1 = $articlesTable->Tags->findByName('cakephp')->first(); - $tag2 = $articlesTable->Tags->newEmptyEntity(); - $tag2->name = 'Génial'; - - $articlesTable->Tags->link($article, [$tag1, $tag2]); - -Dissocier des Enregistrements Many To Many ------------------------------------------- - -Pour dissocier des enregistrements many-to-many, utilisez la méthode -``unlink()``:: - - $tags = $articlesTable - ->Tags - ->find() - ->where(['name IN' => ['cakephp', 'awesome']]) - ->toList(); - - $articlesTable->Tags->unlink($article, $tags); - -Lorsque vous modifiez des enregistrements en définissant ou modifiant -directement leurs propriétés, il n'y aura pas de validation, ce qui est -problématique s'il s'agit d'accepter les données d'un formulaire. La section -suivante explique comment convertir efficacement les données de formulaire -en entities afin qu'elles puissent être validées et sauvegardées. - -.. _converting-request-data: - -Convertir les Données Requêtées en Entities -=========================================== - -Avant de modifier et de réenregistrer les données dans la base de données, -vous devrez convertir les données d'un format de tableau (figurant -dans la requête) en entities utilisées par l'ORM. La classe -Table peut convertir efficacement une ou plusieurs entities à partir des -données de la requête. Vous pouvez convertir une entity unique en utilisant:: - - // Dans un controller. - - $articles = $this->getTableLocator()->get('Articles'); - - // Valide et convertit en un objet Entity - $entity = $articles->newEntity($this->request->getData()); - -.. note:: - - Si vous utilisez newEntity() et qu'il manque tout ou partie des nouvelles - données dans les entities créées, vérifiez que les colonnes que vous voulez - modifier sont listées dans la propriété ``$_accessible`` de votre entity. - Cf. :ref:`entities-mass-assignment`. - -Les données de la requête doivent suivre la structure de vos entities. Par -exemple si vous avez un article, que celui-ci appartient à un utilisateur, -(*belongs to*), et a plusieurs commentaires (*has many*), les données de votre -requête devraient ressembler à:: - - $data = [ - 'title' => 'Mon titre', - 'body' => 'Le texte', - 'user_id' => 1, - 'user' => [ - 'username' => 'mark' - ], - 'comments' => [ - ['body' => 'Premier commentaire'], - ['body' => 'Deuxième commentaire'], - ] - ]; - -Par défaut, la méthode ``newEntity()`` valide les données qui lui sont passées, -comme expliqué dans la section :ref:`validating-request-data`. Si vous voulez -empêcher la validation des données, passez l'option ``'validate' => false``:: - - $entity = $articles->newEntity($data, ['validate' => false]); - -Lors de la construction de formulaires qui sauvegardent des associations -imbriquées, vous devez définir quelles associations doivent être converties:: - - // Dans un controller - - $articles = $this->getTableLocator()->get('Articles'); - - // Nouvelle entity avec des associations imbriquées - $entity = $articles->newEntity($this->request->getData(), [ - 'associated' => [ - 'Tags', 'Comments' => ['associated' => ['Users']] - ] - ]); - -Ceci indique que les Tags, les commentaires et les utilisateurs associés aux -commentaires doivent être convertis. Au choix, vous pouvez aussi utiliser la -notation par points, qui est plus concise:: - - // Dans un controller - - $articles = $this->getTableLocator()->get('Articles'); - - // Nouvelle entity avec des associations imbriquées en utilisant - // la notation par points - $entity = $articles->newEntity($this->request->getData(), [ - 'associated' => ['Tags', 'Comments.Users'] - ]); - -Vous pouvez aussi désactiver la conversion d'associations imbriquées comme -ceci:: - - $entity = $articles->newEntity($data, ['associated' => []]); - // ou... - $entity = $articles->patchEntity($entity, $data, ['associated' => []]); - -Les données associées sont aussi validées par défaut, à moins de spécifier le -contraire. Vous pouvez également changer l'ensemble de validation utilisé pour -chaque association:: - - // Dans un controller - - $articles = $this->getTableLocator()->get('Articles'); - - // Ne fait pas de validation pour l'association Tags et - // appelle l'ensemble de validation 'signup' pour Comments.Users - $entity = $articles->newEntity($this->request->getData(), [ - 'associated' => [ - 'Tags' => ['validate' => false], - 'Comments.Users' => ['validate' => 'signup'] - ] - ]); - -Le chapitre :ref:`using-different-validators-per-association` contient davantage -d'informations sur la façon d'utiliser les différents validateurs pour la -sauvegarde d'associations. - -Le diagramme suivant donne un aperçu de ce qui se passe dans la méthode -``newEntity()`` ou ``patchEntity()``: - -.. figure:: /_static/img/validation-cycle.png - :align: left - :alt: Logigramme montrant le process de conversion/validation. - -La méthode ``newEmptyEntity()`` vous renverra toujours une entity. Si la -validation échoue, votre entité contiendra des erreurs et les champs invalides -ne seront pas remplis dans l'entity créée. - -Convertir des Données BelongsToMany ------------------------------------ - -Si vous sauvegardez des associations belongsToMany, vous pouvez utiliser soit -une liste de données d'entity, soit une liste d'ids. Quand vous utilisez une -liste de données d'entity, les données de votre requête devraient ressembler à -ceci:: - - $data = [ - 'title' => 'Mon titre', - 'body' => 'Le texte', - 'user_id' => 1, - 'tags' => [ - ['name' => 'CakePHP'], - ['name' => 'Internet'], - ], - ]; - -Ce code créera 2 nouveaux tags. Si vous voulez créer un lien entre un article et -des tags existants, vous pouvez utiliser une liste d'ids. Les données de votre -requête doivent ressembler à ceci:: - - $data = [ - 'title' => 'Mon titre', - 'body' => 'Le texte', - 'user_id' => 1, - 'tags' => [ - '_ids' => [1, 2, 3, 4], - ], - ]; - -Si vous souhaitez lier des entrées belongsToMany existantes et en créer de -nouvelles en même temps, vous pouvez utiliser la forme étendue:: - - $data = [ - 'title' => 'Mon titre', - 'body' => 'Le texte', - 'user_id' => 1, - 'tags' => [ - ['name' => 'Un nouveau tag'], - ['name' => 'Un autre nouveau tag'], - ['id' => 5], - ['id' => 21], - ], - ]; - -Quand ces données seront converties en entities, il y aura 4 tags. -Les deux premiers seront de nouveaux objets, et les deux autres seront des -références à des tags existants. - -Quand vous convertissez des données de belongsToMany, vous pouvez désactiver la -création d'une nouvelle entity en utilisant l'option ``onlyIds``:: - - $result = $articles->patchEntity($entity, $data, [ - 'associated' => ['Tags' => ['onlyIds' => true]], - ]); - -Quand elle est activée, cette option restreint la conversion des données de -belongsToMany pour utiliser uniquement la clé ``_ids``. - -Convertir des Données HasMany ------------------------------ - -Si vous souhaitez mettre à jour des associations hasMany existantes et mettre à -jour leurs propriétés, vous devez d'abord vous assurer que votre entity est -chargée avec les données hasMany associées. Vous pouvez ensuite utiliser une -requête avec des données structurées de la façon suivante:: - - $data = [ - 'title' => 'Mon titre', - 'body' => 'Le texte', - 'comments' => [ - ['id' => 1, 'comment' => 'Mettre à jour le premier commentaire'], - ['id' => 2, 'comment' => 'Mettre à jour le deuxième commentaire'], - ['comment' => 'Créer un nouveau commentaire'], - ], - ]; - -Si vous sauvegardez des associations hasMany et que voulez lier des -enregistrements existants à un nouveau parent, vous pouvez utiliser le format -``_ids``:: - - $data = [ - 'title' => 'Mon nouvel article', - 'body' => 'Le texte', - 'user_id' => 1, - 'comments' => [ - '_ids' => [1, 2, 3, 4], - ], - ]; - -Quand vous convertissez des données de hasMany, vous pouvez désactiver la -création d'une nouvelle entity en utilisant l'option ``onlyIds``. Quand elle -est activée, cette option restreint la conversion des données hasMany pour -utiliser uniquement la clé ``_ids`` et ignorer toutes les autres données. - -Convertir des Enregistrements Multiples ---------------------------------------- - -Lorsque vous créez des formulaires de création/mise à jour de plusiseurs -enregistrements en une seule opération, vous pouvez utiliser ``newEntities()``:: - - // Dans un controller. - - $articles = $this->getTableLocator()->get('Articles'); - $entities = $articles->newEntities($this->request->getData()); - -Dans cette situation, les données de la requête pour plusieurs articles doivent -ressembler à ceci:: - - $data = [ - [ - 'title' => 'Premier post', - 'published' => 1, - ], - [ - 'title' => 'Second post', - 'published' => 1, - ], - ]; - -Une fois que vous avez converti les données de la requête en entities, vous -pouvez sauvegarder:: - - // Dans un controller. - foreach ($entities as $entity) { - // Sauvegarder l'entity - $articles->save($entity); - } - -Ce code va lancer séparément une transaction pour chaque entity -sauvegardée. Si vous voulez traiter toutes les entities en transaction unique, -vous pouvez utiliser ``saveMany()`` ou ``saveManyOrFail()``:: - - // Renvoie un booléen pour indiquer si l'opération a réussi - $articles->saveMany($entities); - - // Lève une PersistenceFailedException si l'un des enregistrements échoue - $articles->saveManyOrFail($entities); - -.. _changing-accessible-fields: - -Changer les Champs Accessibles ------------------------------- - -Il est également possible d'autoriser ``newEntity()`` à écrire dans des -champs non accessibles. Par exemple, ``id`` est généralement absent de la -propriété ``_accessible``. Dans un tel cas, vous pouvez utiliser l'option -``accessibleFields``. Il pourrait être utile de conserver les ids des entities -associées:: - - // Dans un controller. - - $articles = $this->getTableLocator()->get('Articles'); - $entity = $articles->newEntity($this->request->getData(), [ - 'associated' => [ - 'Tags', 'Comments' => [ - 'associated' => [ - 'Users' => [ - 'accessibleFields' => ['id' => true], - ], - ], - ], - ], - ]); - -Le code ci-dessus permet de conserver l'association entre Comments et Users pour -l'entity concernée. - -.. note:: - - Si vous utilisez newEntity() et qu'il manque dans l'entity tout ou partie - des données transmises, vérifiez à deux fois que les colonnes - que vous souhaitez définir sont listées dans la propriété ``$_accessible`` - de votre entity. Cf. :ref:`entities-mass-assignment`. - -Fusionner les Données de la Requête dans les Entities ------------------------------------------------------ - -Pour mettre à jour des entities, vous pouvez choisir d'appliquer les données de -la requête directement sur une entity existante. Cela a l'avantage que seuls les -champs réellement modifiés seront sauvegardés, au lieu d'envoyer tous les -champs à la base de données, même ceux qui sont inchangés. Vous pouvez -fusionner un tableau de données brutes dans une entity existante en utilisant la -méthode ``patchEntity()``:: - - // Dans un controller. - - $articles = $this->getTableLocator()->get('Articles'); - $article = $articles->get(1); - $articles->patchEntity($article, $this->request->getData()); - $articles->save($article); - -Validation et patchEntity -~~~~~~~~~~~~~~~~~~~~~~~~~ - -De la même façon que ``newEntity()``, la méthode ``patchEntity`` validera les -données avant de les copier dans l'entity. Ce mécanisme est expliqué -dans la section :ref:`validating-request-data`. Si vous souhaitez désactiver la -validation lors du patch d'une entity, passez l'option ``validate`` comme ceci:: - - // Dans un controller. - - $articles = $this->getTableLocator()->get('Articles'); - $article = $articles->get(1); - $articles->patchEntity($article, $data, ['validate' => false]); - -Vous pouvez également changer l'ensemble de validation utilisé pour l'entity -ou pour n'importe qu'elle association:: - - $articles->patchEntity($article, $this->request->getData(), [ - 'validate' => 'custom', - 'associated' => ['Tags', 'Comments.Users' => ['validate' => 'signup']] - ]); - -Patcher des HasMany et BelongsToMany -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Comme expliqué dans la section précédente, les données de la requête doivent suivre -la structure de votre entity. La méthode ``patchEntity()`` est également capable -de fusionner les associations. Par défaut seul les premiers niveaux -d'associations sont fusionnés mais si vous voulez contrôler la liste des -associations à fusionner, ou fusionner des niveaux de plus en plus profonds, vous -pouvez utiliser le troisième paramètre de la méthode:: - - // Dans un controller. - $associated = ['Tags', 'Comments.Users']; - $article = $articles->get(1, ['contain' => $associated]); - $articles->patchEntity($article, $this->request->getData(), [ - 'associated' => $associated, - ]); - $articles->save($article); - -Les associations sont fusionnées en faisant correspondre la clé primaire des -entities source avec les champs correspondants dans le tableau de données -fourni. Si aucune entity cible n'est trouvée, les associations construiront de -nouvelles entities. - -Pa exemple, prenons une requête contenant les données suivantes:: - - $data = [ - 'title' => 'Mon titre', - 'user' => [ - 'username' => 'mark', - ], - ]; - -Si vous essayez de patcher une entity ne contenant pas d'entity associée dans la -propriété user, une nouvelle entity sera créée pour `user`:: - - // Dans un controller. - $entity = $articles->patchEntity(new Article, $data); - echo $entity->user->username; // Affiche 'mark' - -Cela fonctionne de la même manière pour les associations hasMany et -belongsToMany, avec cependant un point d'attention important: - -.. note:: - - Pour les associations belongsToMany, vérifiez que les entities associées - ont bien une propriété accessible pour l'entité associée. - -Si Product belongsToMany Tag:: - - // Dans l'entity Product - protected array $_accessible = [ - // .. autres propriétés - 'tags' => true, - ]; - -.. note:: - - Pour les associations hasMany et belongsToMany, s'il y avait des entities - dont la clé primaire ne correspondait à aucun enregistrement dans le tableau - de données, alors ces enregistrements seraient écartés de l'entity - résultante. - - Rappelez-vous que l'utilisation de ``patchEntity()`` ou de - ``patchEntities()`` ne fait pas persister les données, elle ne fait que - modifier (ou créer) les entities données. Pour sauvegarder l'entity, vous - devrez appeler la méthode ``save()`` de la table. - -Par exemple, considérons le cas suivant:: - - $data = [ - 'title' => 'Mon titre', - 'body' => 'Le text', - 'comments' => [ - ['body' => 'Premier commentaire', 'id' => 1], - ['body' => 'Second commentaire', 'id' => 2], - ], - ]; - $entity = $articles->newEntity($data); - $articles->save($entity); - - $newData = [ - 'comments' => [ - ['body' => 'Commentaire modifié', 'id' => 1], - ['body' => 'Un nouveau commentaire'], - ], - ]; - $articles->patchEntity($entity, $newData); - $articles->save($entity); - -Au final, si l'entity est à nouveau convertie en tableau, vous obtiendrez le -résultat suivant:: - - [ - 'title' => 'Mon titre', - 'body' => 'Le text', - 'comments' => [ - ['body' => 'Commentaire modifié', 'id' => 1], - ['body' => 'Un nouveau commentaire'], - ] - ]; - -Comme vous pouvez le constater, le commentaire avec l'id 2 a disparu, puisqu'il -ne correspondait à aucun élément du tableau ``$newData``. Cela se passe ainsi -parce CakePHP calque les données de l'entity sur le nouvel état que décrit par -les données de la requête. - -Un autre avantage à cette approche est qu'elle réduit le nombre d'opérations à -exécuter lorsque l'entity est à nouveau persistée. - -Notez bien que cela ne veut pas dire que le commentaire avec l'id 2 a été -supprimé de la base de données. Si vous souhaitez supprimer les commentaires de -cet article qui ne sont pas présents dans l'entity, vous pouvez récupérer -les clés primaires et exécuter une suppression batch pour celles qui ne sont -pas dans la liste:: - - // Dans un controller. - use Cake\Collection\Collection; - - $comments = $this->getTableLocator()->get('Comments'); - $present = (new Collection($entity->comments))->extract('id')->filter()->toList(); - $comments->deleteAll([ - 'article_id' => $article->id, - 'id NOT IN' => $present, - ]); - -Comme vous voyez, cela permet aussi de créer des solutions dans lesquelles une -association a besoin d'être implémentée comme un ensemble unique. - -Vous pouvez aussi faire un patch de plusieurs entities à la fois. Ce que nous -avons vu pour les associations hasMany et belongsToMany s'applique aussi pour -patcher plusieurs entities: les correspondances se font d'après la valeur de la -clé primaire et celles qui sont absentes dans le tableau des entities d'origine -seront retirées et absentes des résultats:: - - // Dans un controller. - - $articles = $this->getTableLocator()->get('Articles'); - $list = $articles->find('popular')->toList(); - $patched = $articles->patchEntities($list, $this->request->getData()); - foreach ($patched as $entity) { - $articles->save($entity); - } - -De la même façon qu'avec ``patchEntity()``, vous pouvez utiliser le troisième -argument pour contrôler les associations qui seront fusionnées dans chacune des -entities du tableau:: - - // Dans un controller. - $patched = $articles->patchEntities( - $list, - $this->request->getData(), - ['associated' => ['Tags', 'Comments.Users']] - ); - -.. _before-marshal: - -Modifier les Données de la Requête Avant de Construire les Entities -------------------------------------------------------------------- - -Si vous devez modifier les données de la requête avant de les convertir en -entities, vous pouvez utiliser l'event ``Model.beforeMarshal``. Cet event vous -permet de manipuler les données de la requête juste avant la création des -entities:: - - // Ajoutez les instructions use au début de votre fichier. - use Cake\Event\EventInterface; - use ArrayObject; - - // Dans une classe de table ou un behavior - public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options) - { - if (isset($data['username'])) { - $data['username'] = mb_strtolower($data['username']); - } - } - -Le paramètre ``$data`` est une instance ``ArrayObject``, donc vous n'avez pas -besoin de la renvoyer pour changer les données qui seront utilisées pour créer -les entities. - -Le but principal de ``beforeMarshal`` est d'aider les utilisateurs à passer -le process de validation lorsque des erreurs simples peuvent être résolues -automatiquement, ou lorsque les données doivent être restructurées pour être -placées dans les bons champs. - -L'event ``Model.beforeMarshal`` est lancé juste au début du process de validation. -Une des raisons à cela est que ``beforeMarshal`` est autorisé à modifier les -règles de validation et les options d'enregistrement, telles que la liste -blanche des champs. La validation est lancée juste après la fin de l'exécution -de cet événement. Un exemple classique de modification des données avant leur -validation est la suppression des espaces superflus dans tous les champs avant -leur enregistrement:: - - // Ajoutez les instructions use au début de votre fichier. - use Cake\Event\EventInterface; - use ArrayObject; - - // Dans une table ou un behavior - public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options) - { - foreach ($data as $key => $value) { - if (is_string($value)) { - $data[$key] = trim($value); - } - } - } - -Du fait du mode de fonctionnement du marshalling, si un champ ne passe pas la -validation il sera automatiquement supprimé du tableau de données et ne sera pas -copié dans l'entity. Cela évite d'avoir des données incohérentes dans l'objet -entity. - -De plus, les données fournies à la méthode ``beforeMarshal`` sont une copie des -données passées. La raison à cela est qu'il est important de préserver les -données saisies à l'origine par l'utilisateur, car elles sont susceptibles de -servir autre part. - -Modifier les Entités Après leur Mise À Jour À Partir de la Requête ------------------------------------------------------------------- - -L'événement ``Model.afterMarshal`` vous permet de modifier les entités après -qu'elles auront été créées ou modifiées à partir des données de la requête. Cela -peut vous servir à appliquer une logique de validation supplémentaire qui ne -peut pas être exprimée de manière simple à travers les méthodes du Validator:: - - // Ajoutez les instructions use au début de votre fichier. - use Cake\Event\EventInterface; - use Cake\ORM\EntityInterface; - use ArrayObject; - - // Dans une classe de table ou de behavior - public function afterMarshal( - EventInterface $event, - EntityInterface $entity, - ArrayObject $data, - ArrayObject $options - ) { - // Ne pas accepter les personnes dont le nom commence par J le 20 de - // chaque mois. - if (mb_substr($entity->name, 1) === 'J' && (int)date('d') === 20) { - $entity->setError('name', 'Pas de noms en J aujourd\'hui. Désolé.'); - } - } - -.. versionadded:: 4.1.0 - -Valider les Données Avant de Construire les Entities ----------------------------------------------------- - -Le chapitre :doc:`/orm/validation` vous fournira plus d'informations sur les -fonctionnalités de validation de CakePHP pour garantir l'exactitude et la -cohérence de vos données. - -Éviter les Attaques d'Assignement en Masse de Propriétés --------------------------------------------------------- - -Lors de la création ou la fusion des entities à partir de données de la requête, -vous devez être attentif aux ajouts ou modifications que vous permettez à vos -utilisateurs dans les entities. Par exemple, en envoyant dans la requpete un -tableau contenant ``user_id``, un pirate pourrait changer le propriétaire d'un -article, ce qui entraînerait des effets indésirables:: - - // Contient ['user_id' => 100, 'title' => 'Piraté !']; - $data = $this->request->getData(); - $entity = $this->patchEntity($entity, $data); - $this->save($entity); - -Il y a deux façons de se protéger contre ce problème. La première est de -définir les colonnes par défaut qui peuvent être définies en toute sécurité à -partir d'une requête en utilisant la fonctionnalité -d':ref:`entities-mass-assignment` dans les entities. - -La deuxième façon est d'utiliser l'option ``fields`` lors de la création ou -la fusion de données dans une entity:: - - // Contient ['user_id' => 100, 'title' => 'Piraté !']; - $data = $this->request->getData(); - - // Permet seulement de changer le title - $entity = $this->patchEntity($entity, $data, [ - 'fields' => ['title'] - ]); - $this->save($entity); - -Vous pouvez aussi contrôler les propriétés qui peuvent être assignées pour les -associations:: - - // Permet seulement de modifier le titre et les tags - // et le nom du tag est la seule colonne qui puisse être définie - $entity = $this->patchEntity($entity, $data, [ - 'fields' => ['title', 'tags'], - 'associated' => ['Tags' => ['fields' => ['name']]] - ]); - $this->save($entity); - -Cette fonctionnalité est pratique quand vos utilisateurs ont accès plusieurs -fonctions et que vous voulez leur permettre de modifier différentes données -en fonction de leurs privilèges. - -.. _saving-entities: - -Sauvegarder les Entities -======================== - -.. php:method:: save(Entity $entity, array $options = []) - -Quand vous sauvegardez les données de la requête dans votre base de données, -vous devez d'abord hydrater une nouvelle entity en utilisant ``newEntity()``, -que vous pourrez ensuite passer à ``save()``. Par exemple:: - - // Dans un controller - - $articles = $this->getTableLocator()->get('Articles'); - $article = $articles->newEntity($this->request->getData()); - if ($articles->save($article)) { - // ... - } - -L'ORM utilise la méthode ``isNew()`` sur une entity pour déterminer s'il faut -réaliser une insertion ou une mise à jour. Si la méthode ``isNew()`` renvoie -``true`` et que l'entity a une clé primaire, l'ORM va d'abord lancer une requête -'exists'. Cette requête 'exists' peut être supprimée en passant -``'checkExisting' => false`` dans l'argument ``$options``:: - - $articles->save($article, ['checkExisting' => false]); - -Une fois que vous aurez chargé quelques entities, vous voudrez probablement les -modifier et mettre à jour la base de données. C'est une manipulation simple dans -CakePHP:: - - $articles = $this->getTableLocator()->get('Articles'); - $article = $articles->find('all')->where(['id' => 2])->first(); - - $article->title = 'Mon nouveau titre'; - $articles->save($article); - -Lors de la sauvegarde, CakePHP va -:ref:`appliquer vos règles de validation `, et inclure -l'opération de sauvegarde dans une transaction de la base de données. -Cela mettra à jour uniquement les propriétés qui ont changé. L'appel à -``save()`` ci-dessus va générer un code SQL de ce type: - -.. code-block:: sql - - UPDATE articles SET title = 'Mon nouveau titre' WHERE id = 2; - -Si vous aviez une nouvelle entity, cela générerait le code SQL suivant: - -.. code-block:: sql - - INSERT INTO articles (title) VALUES ('Mon nouveau titre'); - -Quand une entity est sauvegardée, voici ce qui se passe: - -1. La vérification des règles commencera si elle n'est pas désactivée. -2. La vérification des règles va déclencher l'event - ``Model.beforeRules``. Si l'event est stoppé, l'opération de - sauvegarde échouera et retournera ``false``. -3. Les règles seront vérifiées. Si l'entity est en train d'être créée, les - règles ``create`` seront utilisées. Si l'entity est en train d'être mise à - jour, les règles ``update`` seront utilisées. -4. L'event ``Model.afterRules`` sera déclenché. -5. L'event ``Model.beforeSave`` est dispatché. S'il est stoppé, la - sauvegarde sera annulée, et save() va retourner ``false``. -6. Les associations parentes sont sauvegardées. Par exemple, toute association - belongsTo listée sera sauvegardée. -7. Les champs modifiés sur l'entity seront sauvegardés. -8. Les associations enfants sont sauvegardées. Par exemple, toute association - hasMany, hasOne, ou belongsToMany listée sera sauvegardée. -9. L'event ``Model.afterSave`` sera dispatché. -10. L'event ``Model.afterSaveCommit`` sera dispatché. - -Le diagramme suivant illustre ce procédé: - -.. figure:: /_static/img/save-cycle.png - :align: left - :alt: Logigramme montrant le procédé de sauvegarde. - -Consultez la section :ref:`application-rules` pour plus d'informations sur la -création et l'utilisation des règles. - -.. warning:: - - Si l'entity n'a subi aucun changement au moment de sa sauvegarde, les - callbacks ne vont pas être déclenchés car aucune opération de sauvegarde - n'est effectuée. - -La méthode ``save()`` va retourner l'entity modifiée en cas de succès, et -``false`` en cas d'échec. Vous pouvez désactiver les règles et/ou les -transactions en utilisant l'argument ``$options`` lors de la sauvegarde:: - - // Dans un controller ou une méthode de table. - $articles->save($article, ['checkRules' => false, 'atomic' => false]); - -Sauvegarder les Associations ----------------------------- - -Quand vous sauvegardez une entity, vous pouvez aussi choisir de sauvegarder tout -ou partie des entities associées. Par défaut, toutes les entities de premier -niveau seront sauvegardées. Par exemple, sauvegarder un Article va aussi -mettre à jour automatiquement toute entity modifiée directement liée à la table -des articles. - -Vous pouvez accéder à un réglage plus fin des associations qui sont sauvegardées -en utilisant l'option ``associated``:: - - // Dans un controller. - - // Sauvegarde seulement l'association avec les commentaires - $articles->save($entity, ['associated' => ['Comments']]); - -Vous pouvez définir une sauvegarde d'associations imbriquées sur plusieurs -niveaux en utilisant la notation par point:: - - // Sauvegarde la société, les employés et les adresses liées à chacun d'eux. - $companies->save($entity, ['associated' => ['Employees.Addresses']]); - -De plus, vous pouvez combiner la notation par point pour les associations avec -le tableau d'options:: - - $companies->save($entity, [ - 'associated' => [ - 'Employees', - 'Employees.Addresses' - ] - ]); - -Vos entities doivent être structurées de la même façon qu'elles l'étaient -quand elles ont été chargées à partir de la base de données. -Consultez la documentation du helper Form pour savoir comment -:ref:`associated-form-inputs`. - -Si vous construisez ou modifiez des données associées après avoir construit -vos entities, vous devrez marquer la propriété d'association comme étant -modifiée en utilisant ``setDirty()``:: - - $company->author->name = 'Master Chef'; - $company->setDirty('author', true); - -Sauvegarder les Associations BelongsTo --------------------------------------- - -Lors de la sauvegarde des associations belongsTo, l'ORM attend une entity -imbriquée unique avec le nom de l'association au singulier et -:ref:`des underscores `. -Par exemple:: - - // Dans un controller. - $data = [ - 'title' => 'Premier Post', - 'user' => [ - 'id' => 1, - 'username' => 'mark' - ] - ]; - - $articles = $this->getTableLocator()->get('Articles'); - $article = $articles->newEntity($data, [ - 'associated' => ['Users'] - ]); - - $articles->save($article); - -Sauvegarder les Associations HasOne ------------------------------------ - -Lors de la sauvegarde d'associations hasOne, l'ORM attend une entity -imbriquée unique avec le nom de l'association au singulier et -:ref:`des underscores underscore `. -Par exemple:: - - // Dans un controller. - $data = [ - 'id' => 1, - 'username' => 'cakephp', - 'profile' => [ - 'twitter' => '@cakephp' - ] - ]; - - $users = $this->getTableLocator()->get('Users'); - $user = $users->newEntity($data, [ - 'associated' => ['Profiles'] - ]); - $users->save($user); - -Sauvegarder les Associations HasMany ------------------------------------- - -Lors de la sauvegarde d'associations hasMany, l'ORM attend un tableau d'entities -avec le nom de l'association au pluriel et -:ref:`des underscores `. -Par exemple:: - - // Dans un controller. - $data = [ - 'title' => 'Premier Post', - 'comments' => [ - ['body' => 'Le meilleur post de ma vie'], - ['body' => 'Celui-là, je l\'aime vraiment bien.'] - ] - ]; - - $articles = $this->getTableLocator()->get('Articles'); - $article = $articles->newEntity($data, [ - 'associated' => ['Comments'] - ]); - $articles->save($article); - -Lors de la sauvegarde d'associations hasMany, les enregistrements associés -seront soit mis à jour, soit insérés. Dans les cas où l'enregistrement a déjà -des enregistrements associés dans la base de données, vous avez le choix entre -deux stratégies de sauvegarde: - -append - Les enregistrements associés sont mis à jour dans la base de données - ou, s'ils ne correspondent à aucun enregistrement existant, sont insérés. -replace - Tout enregistrement existant qui ne correspond pas aux enregistrements - fournis sera supprimé de la base de données. Seuls les enregistrements - fournis resteront (ou seront insérés). - -Par défaut, l'ORM utilise la stratégie de sauvegarde ``append``. -Consultez :ref:`has-many-associations` pour plus de détails sur la définition -de ``saveStrategy``. - -Quel que soit le moment où vous ajoutez de nouveaux enregistrements dans une -association existante, vous devez toujours marquer la propriété de l'association -comme 'dirty'. Cela fait savoir à l'ORM que la propriété de cette association -doit être persistée:: - - $article->comments[] = $comment; - $article->setDirty('comments', true); - -Sans l'appel à ``setDirty()``, les commentaires mis à jour ne seront pas -sauvegardés. - -Si vous créez une entité et voulez y attacher des enregistrements existants dans -une association hasMany ou belongsToMany, vous devez d'abord initialiser la -propriété de l'association:: - - $article->comments = []; - -Sans l'initialisation, l'appel à ``$article->comments[] = $comment;`` sera sans -effet. - -Sauvegarder les Associations BelongsToMany ------------------------------------------- - -Lors de la sauvegarde d'associations hasMany, l'ORM attend un tableau d'entities -avec le nom de l'association au pluriel et -:ref:`des underscores `. -Par exemple:: - - // Dans un controller. - $data = [ - 'title' => 'Premier Post', - 'tags' => [ - ['tag' => 'CakePHP'], - ['tag' => 'Framework'] - ] - ]; - - $articles = $this->getTableLocator()->get('Articles'); - $article = $articles->newEntity($data, [ - 'associated' => ['Tags'] - ]); - $articles->save($article); - -Quand vous convertissez les données de la requête en entities, les méthodes -``newEntity()`` et ``newEntities()`` traiteront les deux tableaux de propriétés, -ainsi qu'une liste d'ids sous la clé ``_ids``. L'utilisation de la clé ``_ids`` -permet de construire des contrôles de formulaire basés sur une liste déroulante -ou une liste de cases à cocher pour les associations belongsToMany. Consultez la -section :ref:`converting-request-data` pour plus d'informations. - -Lors de la sauvegarde des associations belongsToMany, vous avez le choix entre -deux stratégies de sauvegarde: - -append - Seuls les nouveaux liens seront créés de chaque côté de cette association. - Cette stratégie détruira pas les liens existants même s'ils sont absents du - tableau d'entities à sauvegarder. -replace - Lors de la sauvegarde, les liens existants seront supprimés et les nouveaux - liens seront créés dans la table de jointure. S'il y a des liens existants - dans la base de données vers certaines des entities que l'on souhaite - sauvegarder, ces liens seront mis à jour, et non supprimés et - re-sauvegardés. - -Consultez :ref:`belongs-to-many-associations` pour plus de détails sur la façon -de définir ``saveStrategy``. - -Par défaut, l'ORM utilise la stratégie ``replace``. Si vous ajoutez à quelque -moment que ce soit de nouveaux enregistrements dans une association existante, -vous devez toujours marquer la propriété de l'association comme 'dirty'. Cela -fait savoir à l'ORM que la propriété de l'association doit être persistée:: - - $article->tags[] = $tag; - $article->setDirty('tags', true); - -Sans appel à ``setDirty()``, les tags modifiés ne seront pas sauvegardés. - -Vous vous retrouverez souvent à vouloir créer une association entre deux -entities existantes, par exemple un utilisateur co-auteur d'un article. Pour -cela, utilisez la méthode ``link()``:: - - $article = $this->Articles->get($articleId); - $user = $this->Users->get($userId); - - $this->Articles->Users->link($article, [$user]); - -Lors de la sauvegarde d'associations belongsToMany, il peut être pertinent de -sauvegarder des données supplémentaires dans la table de jointure. Dans -l'exemple précédent des tags, on pourrait imaginer le type de vote ``vote_type`` -de la personne qui a voté sur cet article. Le ``vote_type`` peut être soit -``upvote``, soit ``downvote``, et il est représenté par une chaîne de -caractères. La relation est entre Users et Articles. - -La sauvegarde de cette association et du ``vote_type`` est réalisée en ajoutant -tout d'abord des données à ``_joinData`` et en sauvegardant ensuite -l'association avec ``link()``, par exemple:: - - $article = $this->Articles->get($articleId); - $user = $this->Users->get($userId); - - $user->_joinData = new Entity(['vote_type' => $voteType], ['markNew' => true]); - $this->Articles->Users->link($article, [$user]); - -Sauvegarder des Données Supplémentaires dans la Table de Jointure ------------------------------------------------------------------ - -Dans certaines situations, vous aurez des colonnes supplémentaires dans la table -de jointure de l'association BelongsToMany. Avec CakePHP, il est facile d'y -sauvegarder des propriétés. Chaque entity d'une association belongsToMany a une -propriété ``_joinData`` qui contient les colonnes supplémentaires de la -table de jointure. Ces données peuvent être soit un tableau, soit une instance -Entity. Par exemple, si les Students BelongsToMany Courses, nous pourrions avoir -une table de jointure qui ressemble à ceci:: - - id | student_id | course_id | days_attended | grade - -Lors de la sauvegarde de données, vous pouvez remplir les colonnes -supplémentaires de la table de jointure en définissant les données dans la -propriété ``_joinData``:: - - $student->courses[0]->_joinData->grade = 80.12; - $student->courses[0]->_joinData->days_attended = 30; - - $studentsTable->save($student); - -La propriété ``_joinData`` peut être soit une entity, soit un tableau de données -si vous sauvegardez des entities construites à partir de données de la requête. -Lorsque vous sauvegardez des données de la table de jointure à partir de données -de la requête, vos données POST doivent ressembler à ceci:: - - $data = [ - 'first_name' => 'Sally', - 'last_name' => 'Parker', - 'courses' => [ - [ - 'id' => 10, - '_joinData' => [ - 'grade' => 80.12, - 'days_attended' => 30 - ] - ], - // d'autres cours (courses). - ] - ]; - $student = $this->Students->newEntity($data, [ - 'associated' => ['Courses._joinData'] - ]); - -Consultez le chapitre sur les :ref:`inputs pour les données associées -` pour savoir comment construire correctement des inputs -avec le ``FormHelper``. - -.. _saving-complex-types: - -Sauvegarder les Types Complexes -------------------------------- - -Les tables peuvent stocker des données représentées dans des types basiques, -comme des chaînes, integers, floats, booleans, etc... Mais elles peuvent -aussi être étendues pour accepter des types plus complexes comme des tableaux ou -des objets et sérialiser ces données en types plus simples qui peuvent être -sauvegardés dans la base de données. - -Pour cela, vous devez utiliser le système de types personnalisés. -Consulter la section :ref:`adding-custom-database-types` pour savoir comment -construire des Types de colonnes personnalisés:: - - use Cake\Database\TypeFactory; - - TypeFactory::map('json', 'Cake\Database\Type\JsonType'); - - // Dans src/Model/Table/UsersTable.php - - class UsersTable extends Table - { - - public function initialize(): void - { - $this->getSchema()->setColumnType('preferences', 'json'); - } - - } - -Le code ci-dessus fait correspondre la colonne ``preferences`` au type -personnalisé ``json``. Cela signifie que lorsque vous récupérez des données de -cette colonne, les chaînes JSON de la base de données seront désérialisées et -insérées dans une entity sous forme de tableau. - -De même, lors de la sauvegarde, le tableau sera à nouveau transformé au format -JSON:: - - $user = new User([ - 'preferences' => [ - 'sports' => ['football', 'baseball'], - 'books' => ['Maîtriser le PHP', 'Hamlet'] - ] - ]); - $usersTable->save($user); - -Quand vous utilisez des types complexes, il est important de vérifier que les -données que vous recevez de l'utilisateur final correspondent au bon type. -Sinon, en ne traitant pas correctement les données complexes, vous vous exposez -à ce que des utilisateurs malveillants puissent stocker des données qu'ils -n'auraient normalement pas le droit de stocker. - -Strict Saving -============= - -.. php:method:: saveOrFail(EntityInterface $entity, array $options = []) - -L'appel à cette méthode lancera une -:php:exc:`Cake\\ORM\\Exception\\PersistenceFailedException` si: - -* les règles de validation ont échoué -* l'entity contient des erreurs -* la sauvegarde a été annulée par un _callback_. - -Cette méthode peut être utile pour effectuer des opérations complexes sur la -base de données sans surveillance humaine, comme lors de l'utilisation de -script via des _tasks_ Shell. - -.. note:: - - Si vous utilisez cette méthode dans un Controller, assurez-vous de - capturer la ``PersistenceFailedException`` qui pourrait être levée. - -Si vous voulez trouver l'entity qui n'a pas pu être sauvegardée, vous pouvez -utiliser la méthode :php:meth:`Cake\\ORM\Exception\\PersistenceFailedException::getEntity()`:: - - try { - $table->saveOrFail($entity); - } catch (\Cake\ORM\Exception\PersistenceFailedException $e) { - echo $e->getEntity(); - } - -Dans la mesure où cette méthode utilise la méthode -:php:meth:`Cake\\ORM\\Table::save()`, tous les événements de ``save`` seront -déclenchés. - -Trouver Ou Créer une Entity -=========================== - -.. php:method:: findOrCreate($search, $callback = null, $options = []) - -Trouve un enregistrement d'après les critères de ``$search`` ou crée un nouvel -enregistrement en utilisant les propriétés de ``$search`` et en appelant la -méthode optionnelle ``$callback``. Cette méthode est idéale dans les scénarios -où vous avez besoin réduire les risque de doublons:: - - $record = $table->findOrCreate( - ['email' => 'bobbi@example.com'], - function ($entity) use ($autresDonnees) { - // Appelé seulement en cas de création d'un nouvel enregistrement - $entity->name = $autresDonnees['name']; - } - ); - -Si vos conditions pour ``find`` nécessitent un tri, des associations ou des -conditions personnalisés, alors le paramètre ``$search`` peut être un _callable_ -ou un objet ``Query``. Si vous utilisez un _callable_, il est censé prendre un -objet ``Query`` comme argument. - -L'entité renvoyée aura été sauvegardée s'il s'agit d'un nouvel enregistrement. -Cette méthode supporte les options suivantes: - -* ``atomic`` Si les opérations ``find`` et ``save`` sont censées être effectuées - à l'intérieur d'une transaction. -* ``defaults`` Défini à ``false`` pour ne pas définir les propriétés ``$search`` - dans l'entity créée. - -Créer Avec une Clé Primaire -=========================== - -Quand vous traitez des clés primaires UUID, vous avez souvent besoin de fournir -une valeur générée ailleurs, au lieu d'un identifiant autogénéré pour vous. Dans -ce cas, assurez-vous de ne pas passer la clé primaire au milieu des autres -données. Au lieu de cela, assignez la clé primaire puis patchez les autres -données dans l'entity:: - - $record = $table->newEmptyEntity(); - $record->id = $existingUuid; - $record = $table->patchEntity($record, $existingData); - $table->saveOrFail($record); - -Sauvegarder Plusieurs Entities -============================== - -.. php:method:: saveMany(iterable $entities, array $options = []) - -Cette méthode vous permet de sauvegarder plusieurs entities de façon atomique. -``$entities`` peut être un tableau d'entities créées avec ``newEntities()`` / -``patchEntities()``. ``$options`` peut avoir les mêmes options que celles -acceptées par ``save()``:: - - $data = [ - [ - 'title' => 'Premier post', - 'published' => 1 - ], - [ - 'title' => 'Second post', - 'published' => 1 - ], - ]; - - $articles = $this->getTableLocator()->get('Articles'); - $entities = $articles->newEntities($data); - $result = $articles->saveMany($entities); - -La méthode renvoie les entities mises à jour en cas de succès, ou ``false`` en -cas d'échec. - -Mises à Jour en Masse -===================== - -.. php:method:: updateAll($fields, $conditions) - -Il y a des cas où la mise à jour de lignes individuelles n'est pas efficace ni -nécessaire. Dans ce cas, il est préférable d'utiliser une mise à jour en masse -pour modifier plusieurs lignes en une fois, en assignant les nouvelles valeurs -des champs et les conditions de mise à jour:: - - // Publie tous les articles non publiés. - function publishAllUnpublished() - { - $this->updateAll( - [ // champs - 'published' => true, - 'publish_date' => FrozenTime::now() - ], - [ // conditions - 'published' => false - ] - ); - } - -Si vous devez faire des mises à jour en masse et utiliser des expressions SQL, -vous devrez utiliser un objet expression puisque ``updateAll()`` utilise des -requêtes préparées sous le capot:: - - use Cake\Database\Expression\QueryExpression; - - ... - - function incrementCounters() - { - $expression = new QueryExpression('view_count = view_count + 1'); - $this->updateAll([$expression], ['published' => true]); - } - -Une mise à jour en masse sera considérée comme réussie si une ou plusieurs -lignes sont mises à jour. - -.. warning:: - - updateAll *ne* va *pas* déclencher d'events beforeSave/afterSave. Si vous - avez besoin de ceux-ci, chargez d'abord une collection d'enregistrements et - mettez les à jour. - -``updateAll()`` est seulement une fonction de commodité. Vous pouvez également -utiliser cette interface plus flexible:: - - // Publication de tous les articles non publiés. - function publishAllUnpublished() - { - $this->query() - ->update() - ->set(['published' => true]) - ->where(['published' => false]) - ->execute(); - } - -Reportez-vous à la section :ref:`query-builder-updating-data`. diff --git a/fr/orm/schema-system.rst b/fr/orm/schema-system.rst deleted file mode 100644 index b2efe0b1e3..0000000000 --- a/fr/orm/schema-system.rst +++ /dev/null @@ -1,243 +0,0 @@ -Système de Schéma -################# - -.. php:namespace:: Cake\Database\Schema - -CakePHP dispose d'un système de schéma qui est capable de montrer et de générer -les informations de schéma des tables dans les stockages de données SQL. Le -système de schéma peut générer/montrer un schéma pour toute plateforme SQL -que CakePHP supporte. - -Les principales parties du système de schéma sont ``Cake\Database\Schema\Collection`` -et ``Cake\Database\Schema\TableSchema``. Ces classes vous donnent accès -respectivement à la base de donnée toute entière et aux fonctionnalités de -l'objet TableSchema. - -L'utilisation première du système de schéma est pour les :ref:`test-fixtures`. -Cependant, il peut aussi être utilisé dans votre application si nécessaire. - -Objets Schema\\TableSchema -========================== - -.. php:class:: TableSchema - -Le sous-système de schéma fournit un objet TableSchema pour récupérer les données d'une -table dans la base de données. Cet objet est retourné par les fonctionnalités -de réflection de schéma:: - - use Cake\Database\Schema\TableSchema; - - // Crée une table colonne par colonne. - $schema = new TableSchema('posts'); - $schema->addColumn('id', [ - 'type' => 'integer', - 'length' => 11, - 'null' => false, - 'default' => null, - ])->addColumn('title', [ - 'type' => 'string', - 'length' => 255, - // Create a fixed length (char field) - 'fixed' => true - ])->addConstraint('primary', [ - 'type' => 'primary', - 'columns' => ['id'] - ]); - - // Les classes Schema\TableSchema peuvent aussi être créées avec des données de tableau - $schema = new TableSchema('posts', $columns); - -Les objets ``Schema\TableSchema`` vous permettent de construire des informations sur -le schéma d'une table. Il aide à normaliser et à valider les données utilisées -pour décrire une table. Par exemple, les deux formulaires suivants sont -équivalents:: - - $schema->addColumn('title', 'string'); - // et - $schema->addColumn('title', [ - 'type' => 'string' - ]); - -Bien qu'équivalent, le 2ème formulaire donne plus de détails et de contrôle. -Ceci émule les fonctionnalités existantes disponibles dans les fichiers de -Schéma + le schéma de fixture dans 2.x. - -Accéder aux Données de Colonne ------------------------------- - -Les colonnes sont soit ajoutées en argument du constructeur, soit via -`addColumn()`. Une fois que les champs sont ajoutés, les informations peuvent -être récupérées en utilisant `column()` ou `columns()`:: - - // Récupère le tableau de données d'une colonne - $c = $schema->column('title'); - - // Récupère la liste de toutes les colonnes. - $cols = $schema->columns(); - -Index et Contraintes --------------------- - -Les index sont ajoutés en utilisant ``addIndex()``. Les contraintes sont -ajoutées en utilisant ``addConstraint()``. Les index et contraintes ne -peuvent pas être ajoutés pour les colonnes qui n'existent pas puisque cela -donnerait un état invalide. Les index sont différents des contraintes et -des exceptions seront levées si vous essayez de mélanger les types entre -les méthodes. Un exemple des deux méthodes est:: - - $schema = new TableSchema('posts'); - $schema->addColumn('id', 'integer') - ->addColumn('author_id', 'integer') - ->addColumn('title', 'string') - ->addColumn('slug', 'string'); - - // Ajoute une clé primaire. - $schema->addConstraint('primary', [ - 'type' => 'primary', - 'columns' => ['id'] - ]); - // Ajoute une clé unique - $schema->addConstraint('slug_idx', [ - 'columns' => ['slug'], - 'type' => 'unique', - ]); - // Ajoute un indice - $schema->addIndex('slug_title', [ - 'columns' => ['slug', 'title'], - 'type' => 'index' - ]); - // Ajoute une clé étrangère - $schema->addConstraint('author_id_idx', [ - 'columns' => ['author_id'], - 'type' => 'foreign', - 'references' => ['authors', 'id'], - 'update' => 'cascade', - 'delete' => 'cascade' - ]); - -Si vous ajoutez une contrainte de clé primaire à une colonne unique integer, -elle va automatiquement être convertie en une colonne auto-incrémentée/série -selon la plateforme de la base de données:: - - $schema = new TableSchema('posts'); - $schema->addColumn('id', 'integer') - ->addConstraint('primary', [ - 'type' => 'primary', - 'columns' => ['id'] - ]); - -Dans l'exemple ci-dessus, la colonne ``id`` générerait le SQL suivant dans -MySQL: - -.. code-block:: mysql - - CREATE TABLE `posts` ( - `id` INTEGER AUTO_INCREMENT, - PRIMARY KEY (`id`) - ) - -Si votre clé primaire contient plus d'une colonne, aucune d'elle ne sera -automatiquement convertie en une valeur auto-incrémentée. A la place, vous -devrez dire à l'objet table quelle colonne dans la clé composite vous voulez -auto-incrémenter:: - - $schema = new TableSchema('posts'); - $schema->addColumn('id', [ - 'type' => 'integer', - 'autoIncrement' => true, - ]) - ->addColumn('account_id', 'integer') - ->addConstraint('primary', [ - 'type' => 'primary', - 'columns' => ['id', 'account_id'] - ]); - -L'option ``autoIncrement`` ne fonctionne qu'avec les colonnes ``integer`` et -``biginteger``. - -Lire les Index et les Contraintes ---------------------------------- - -Les index et les contraintes peuvent être lus d'un objet table en utilisant -les méthodes d'accesseur. En supposant que ``$schema`` est une instance de table -remplie, vous pourriez faire ce qui suit:: - - // Récupère les contraintes. Va retourner les noms de toutes les - // contraintes. - $constraints = $schema->constraints() - - // Récupère les données sur une contrainte unique. - $constraint = $schema->constraint('author_id_idx') - - // Récupère les index. Va retourner les noms de tous les index - $indexes = $schema->indexes() - - // Récupère les données d'un index unique. - $index = $schema->index('author_id_idx') - -Ajouter des Options de Table ----------------------------- - -Certains drivers (principalement MySQL) supportent et nécessitent des -meta données de table supplémentaires. Dans le cas de MySQL, les propriétés -``CHARSET``, ``COLLATE`` et ``ENGINE`` sont nécessaires pour maintenir une -structure de table dans MySQL. Ce qui suit pourra être utilisé pour ajouter -des options de table:: - - $schema->options([ - 'engine' => 'InnoDB', - 'collate' => 'utf8_unicode_ci', - ]); - -Les languages de plateforme ne gèrent que les clés qui les intéressent et -ignorent le reste. Toutes les options ne sont pas supportées sur toutes les -plateformes. - -Convertir les Tables en SQL ---------------------------- - -En utilisant ``createSql()`` ou ``dropSql()`` vous pouvez récupérer du SQL -spécifique à la plateforme pour créer ou supprimer une table spécifique:: - - $db = ConnectionManager::get('default'); - $schema = new TableSchema('posts', $fields, $indexes); - - // Crée une table - $queries = $schema->createSql($db); - foreach ($queries as $sql) { - $db->execute($sql); - } - - // Supprime une table - $sql = $schema->dropSql($db); - $db->execute($sql); - -En utilisant un driver de connection, les données de schéma peuvent être -converties en SQL spécifique à la plateforme. Le retour de ``createSql`` et -``dropSql`` est une liste de requêtes SQL nécessaires pour créer une table et -les index nécessaires. Certaines plateformes peuvent nécessiter plusieurs -lignes pour créer des tables avec des commentaires et/ou index. Un tableau -de requêtes est toujours retourné. - -Collections de Schéma -===================== - -.. php:class:: Collection - -``Collection`` fournit un accès aux différentes tables disponibles pour une -connection. Vous pouvez l'utiliser pour récupérer une liste des tables ou -envoyer les tables dans les objets :php:class:`TableSchema`. Une utilisation -habituelle de la classe ressemble à:: - - $db = ConnectionManager::get('default'); - - // Crée une collection de schéma. - // Prior to 3.4 use $db->schemaCollection() - $collection = $db->getSchemaCollection(); - - // Récupère les noms des tables - $schemaables = $collection->listTables(); - - // Récupère une table unique (instance de Schema\TableSchema) - $schemaable = $collection->describe('posts'); - diff --git a/fr/orm/table-objects.rst b/fr/orm/table-objects.rst deleted file mode 100644 index b9933b109e..0000000000 --- a/fr/orm/table-objects.rst +++ /dev/null @@ -1,593 +0,0 @@ -Les Objets Table -################ - -.. php:namespace:: Cake\ORM - -.. php:class:: Table - :noindex: - -Les objets Table fournissent un accès à la collection des entities stockées -dans une table spécifique. Chaque table dans votre application devra avoir une -classe Table associée qui est utilisée pour interagir avec une table donnée. Si -vous n'avez pas besoin de personnaliser le comportement d'une table donnée, -CakePHP va générer une instance Table à utiliser pour vous. - -Avant d'essayer d'utiliser les objets Table et l'ORM, vous devriez vous assurer -que vous avez configuré votre -:ref:`connexion à la base de données `. - -Utilisation Basique -=================== - -Pour commencer, créez une classe Table. Ces classes se trouvent dans -**src/Model/Table**. Les Tables sont une collection de type model spécifique -aux bases de données relationnelles, et sont l'interface principale pour votre -base de données dans l'ORM de CakePHP. La classe table la plus basique devrait -ressembler à ceci:: - - // src/Model/Table/ArticlesTable.php - namespace App\Model\Table; - - use Cake\ORM\Table; - - class ArticlesTable extends Table - { - } - -Notez que nous ne disons pas à l'ORM quelle table utiliser pour notre classe. -Par convention, les objets Table vont utiliser une table avec la notation en -minuscule et avec des underscores pour le nom de la classe. Dans l'exemple du -dessus, la table ``articles`` va être utilisée. Si notre classe table était -nommée ``BlogPosts``, votre table serait nommée ``blog_posts``. Vous pouvez -spécifier la table en utilisant la méthode ``setTable()``:: - - namespace App\Model\Table; - - use Cake\ORM\Table; - - class ArticlesTable extends Table - { - - public function initialize(array $config): void - { - $this->setTable('my_table'); - } - - } - -Aucune convention d'inflexion ne sera appliquée quand on spécifie une table. -Par convention, l'ORM s'attend aussi à ce que chaque table ait une clé primaire -avec le nom de ``id``. Si vous avez besoin de modifier ceci, vous pouvez -utiliser la méthode ``setPrimaryKey()``:: - - namespace App\Model\Table; - - use Cake\ORM\Table; - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->setPrimaryKey('my_id'); - } - } - -Personnaliser la Classe Entity qu'une Table Utilise ---------------------------------------------------- - -Par défaut, les objets table utilisent une classe entity basée sur les -conventions de nommage. Par exemple, si votre classe de table est appelée -``ArticlesTable`` l'entity sera ``Article``. Si la classe table est -``PurchaseOrdersTable`` l'entity sera ``PurchaseOrder``. Cependant si vous -souhaitez utiliser une entity qui ne suit pas les conventions, vous pouvez -utiliser la méthode ``setEntityClass()`` pour changer les choses:: - - class PurchaseOrdersTable extends Table - { - public function initialize(array $config): void - { - $this->setEntityClass('App\Model\Entity\PO'); - } - } - -Comme vu dans les exemples ci-dessus, les objets Table ont une méthode -``initialize()`` qui est appelée à la fin du constructeur. Il est recommandé -d'utiliser cette méthode pour placer la logique d'initialisation au lieu -de surcharger le constructeur. - -Obtenir les Instances d'une Classe Table ----------------------------------------- - -Avant de pouvoir requêter sur une table, vous aurez besoin d'obtenir une -instance de la table. Vous pouvez faire ceci en utilisant la classe -``TableLocator``:: - - // Dans un controller - $articles = $this->getTableLocator()->get('Articles'); - -Le TableLocator fournit les diverses dépendances pour construire la table, -et maintient un registre de toutes les instances de table construites, -facilitant la construction de relations et la configuration l'ORM. Regardez -:ref:`table-locator-usage` pour plus d'informations. - -Si votre classe table est dans un plugin, assurez-vous d'utiliser le bon nom -pour votre classe table. Ne pas le faire peut entraîner des résultats non voulus -dans les règles de validation, ou que les callbacks ne soient pas récupérés car -une classe par défaut est utilisée à la place de votre classe souhaitée. Pour -charger correctement les classes table de votre plugin, utilisez ce qui suit:: - - // Table de plugin - $articlesTable = $this->getTableLocator()->get('PluginName.Articles'); - - // Table de plugin préfixé par Vendor - $articlesTable = $this->getTableLocator()->get('VendorName/PluginName.Articles'); - -.. _table-callbacks: - -Callbacks du Cycle de Vie -========================= - -Comme vous l'avez vu ci-dessus les objets table déclenchent un certain nombre -d'events. Les events sont utiles si vous souhaitez ajouter de la logique dans -l'ORM sans faire de sous-classe et sans réécrire les méthodes. Les écouteurs -(*listeners*) d'events peuvent être définis dans les classes de table ou de -behavior. Vous pouvez aussi utiliser le gestionnaire d'events d'une table pour y -lier des écouteurs dedans. - -Lors de l'utilisation des méthodes de callback, les behaviors attachés dans la -méthode ``initialize()`` déclencheront leurs écouteurs **avant** que les -méthodes de callback de la table ne soient déclenchées. Ceci suit la même -séquence que les controllers et les components. - -Pour ajouter un écouteur d'events à une classe Table ou à un Behavior, -implémentez simplement les signatures de méthodes comme décrit ci-dessus. -Consultez les :doc:`/core-libraries/events` pour avoir plus de détails sur la -façon d'utiliser le sous-système d'events:: - - // Dans un controller - $articles->save($article, ['variablePerso1' => 'votreValeur1']); - - // Dans ArticlesTable.php - public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options) - { - $variablePerso = $options['variablePerso1']; // 'votreValeur1' - $options['variablePerso2'] = 'votreValeur2'; - } - - public function afterSaveCommit(Event $event, EntityInterface $entity, ArrayObject $options) - { - $variablePerso = $options['variablePerso1']; // 'votreValeur1' - $variablePerso = $options['variablePerso2']; // 'votreValeur2' - } - -Liste des Events ----------------- - -* ``Model.initialize`` -* ``Model.beforeMarshal`` -* ``Model.afterMarshal`` -* ``Model.beforeFind`` -* ``Model.buildValidator`` -* ``Model.buildRules`` -* ``Model.beforeRules`` -* ``Model.afterRules`` -* ``Model.beforeSave`` -* ``Model.afterSave`` -* ``Model.afterSaveCommit`` -* ``Model.beforeDelete`` -* ``Model.afterDelete`` -* ``Model.afterDeleteCommit`` - -initialize ----------- - -.. php:method:: initialize(EventInterface $event, ArrayObject $data, ArrayObject $options) - -L'event ``Model.initialize`` est déclenché après que les méthodes de -constructeur et initialize ont été appelées. Les classes ``Table`` n'écoutent pas -cet event par défaut, et utilisent plutôt la méthode hook ``initialize``. - -Pour répondre à l'event ``Model.initialize``, vous pouvez créer une classe -écouteur qui implémente ``EventListenerInterface``:: - - use Cake\Event\EventListenerInterface; - class ModelInitializeListener implements EventListenerInterface - { - public function implementedEvents() - { - return [ - 'Model.initialize' => 'initializeEvent', - ]; - } - public function initializeEvent($event): void - { - $table = $event->getSubject(); - // faire quelque chose ici - } - } - -et attacher l'écouteur au ``EventManager`` ainsi:: - - use Cake\Event\EventManager; - $listener = new ModelInitializeListener(); - EventManager::instance()->attach($listener); - -Ceci va appeler ``initializeEvent`` quand une classe ``Table`` est construite. - -beforeMarshal -------------- - -.. php:method:: beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options) - -L'event ``Model.beforeMarshal`` est déclenché avant que les données de requête -ne soient converties en entities. Consultez la documentation -:ref:`before-marshal` pour plus d'informations. - -afterMarshal ------------- - -.. php:method:: afterMarshal(EventInterface $event, EntityInterface $entity, ArrayObject $data, ArrayObject $options) - -L'event ``Model.afterMarshal`` est déclenché après que les données de requête -ont été converties en entities.Les gestionnaires d'events obtiendront les -entities converties, les données originales de la requête et les options -fournies à ``patchEntity()`` ou ``newEntity()``. - -beforeFind ----------- - -.. php:method:: beforeFind(EventInterface $event, Query $query, ArrayObject $options, $primary) - -L'event ``Model.beforeFind`` est lancé avant chaque opération find. En -arrêtant l'événement et en alimentant la requête avec un jeu de résultats -personnalisé, vous pouvez ignorer complètement l'opération de recherche:: - - public function beforeFind(EventInterface $event, Query $query, ArrayObject $options, $primary) - { - if (/* ... */) { - $event->stopPropagation(); - $query->setResult(new \Cake\Datasource\ResultSetDecorator([])); - - return; - } - // ... - } - -Dans cet exemple, aucun autre événement ``beforeFind`` ne sera déclenché sur -la table associée ou ses comportements attachés (bien que les événements de -comportement soient généralement appelés plus tôt compte tenu de leurs -priorités par défaut), et la requête renverra le jeu de résultats vide qui a -été transmis via ``Query::setResult()``. - -Tout changement fait à l'instance ``$query`` sera retenu pour le reste du find. -Le paramètre ``$primary`` indique si oui ou non ceci est la requête racine ou -une requête associée. Un event ``Model.beforeFind`` sera déclenché dans toutes -les associations participant à la requête. Pour les associations qui -utilisent des jointures, une requête factice sera fournie. Dans votre écouteur -d'event, vous pouvez définir des champs supplémentaires, des conditions, des -jointures ou des formateurs de résultat. Ces options/fonctionnalités seront -copiées dans la requête racine. - -Dans les versions précédentes de CakePHP, il y avait un callback ``afterFind``, -qui a été remplacé par les fonctionnalités de :ref:`map-reduce` et les -constructeurs d'entity. - -buildValidator --------------- - -.. php:method:: buildValidator(EventInterface $event, Validator $validator, $name) - -L'event ``Model.buildValidator`` est déclenché lorsque le validator ``$name`` -est créé. Les behaviors peuvent utiliser ce hook pour ajouter des méthodes -de validation. - -buildRules ----------- - -.. php:method:: buildRules(EventInterface $event, RulesChecker $rules) - -L'event ``Model.buildRules`` est déclenché après qu'une instance de règles a été -créée et après que la méthode ``buildRules()`` de la table a été appelée. - -beforeRules ------------ - -.. php:method:: beforeRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, $operation) - -L'event ``Model.beforeRules`` est déclenché avant que les règles n'aient été -appliquées à une entity. En stoppant cet event, vous pouvez retourner la valeur -finale de l'opération de vérification des règles. - -afterRules ----------- - -.. php:method:: afterRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, $result, $operation) - -L'event ``Model.afterRules`` est déclenché après que les règles soient -appliquées à une entity. En stoppant cet event, vous pouvez retourner la valeur -finale de l'opération de vérification des règles. - -beforeSave ----------- - -.. php:method:: beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) - -L'event ``Model.beforeSave`` est déclenché avant que chaque entity ne soit -sauvegardée. Stopper cet event va annuler l'opération de sauvegarde. Quand -l'event est stoppé, le résultat de l'event sera retourné. -La manière de stopper un event est documentée :ref:`ici `. - -afterSave ---------- - -.. php:method:: afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) - -L'event ``Model.afterSave`` est déclenché après qu'une entity ne soit -sauvegardée. - -afterSaveCommit ---------------- - -.. php:method:: afterSaveCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options) - -L'event ``Model.afterSaveCommit`` est lancé après que la transaction, dans -laquelle l'opération de sauvegarde est fournie, a été committée. Il est aussi -déclenché pour des sauvegardes non atomic, quand les opérations sur la base de -données sont implicitement committées. L'event est déclenché seulement pour -la table primaire sur laquelle ``save()`` est directement appelée. L'event -n'est pas déclenché si une transaction est démarrée avant l'appel de save. - -beforeDelete ------------- - -.. php:method:: beforeDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options) - -L'event ``Model.beforeDelete`` est déclenché avant qu'une entity ne soit -supprimée. En stoppant cet event, vous allez annuler l'opération de -suppression. Quand l'event est stoppé le résultat de l'event sera retourné. - -afterDelete ------------ - -.. php:method:: afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options) - -L'event ``Model.afterDelete`` est déclenché après qu'une entity a été supprimée. - -afterDeleteCommit ------------------ - -.. php:method:: afterDeleteCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options) - -L'event ``Model.afterDeleteCommit`` est lancé après que la transaction, dans -laquelle l'opération de sauvegarde est fournie, a été committée. Il est aussi -déclenché pour des suppressions non atomic, quand les opérations sur la base de -données sont implicitement committées. L'event est décenché seulement pour -la table primaire sur laquelle ``delete()`` est directement appelée. L'event -n'est pas déclenché si une transaction est démarrée avant l'appel de delete. - -Stopper des Events de Table ---------------------------- -Pour empêcher la sauvegarde de se poursuivre, arrêtez simplement la propagation -de l'event dans votre callback:: - - public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) - { - if (...) { - $event->stopPropagation(); - $event->setResult(false); - - return; - } - ... - } - -Alternativement, vous pouvez aussi renvoyer false depuis votre callback. Cela a -le même effet d'arrêt de la propagation. - -Priorités de Callbacks ----------------------- - -Quand vous utilisez des events sur vos tables et vos behaviors, ayez en tête la -priorité et l'ordre dans lequel les écouteurs sont attachés. Les events des -behaviors sont attachés avant ceux des tables. Avec les priorités par défaut, -cela signifie que les callbacks de behaviors seront déclenchés **avant** l'event -de la table ayant le même nom. - -À titre d'exemple, si votre Table utilise ``TreeBehavior``, la méthode -``TreeBehavior::beforeDelete()`` sera appelée avant la méthode -``beforeDelete()`` de votre table, et ne pourra pas travailler avec les nœuds -enfantsde l'enregistrement qui est en train d'être supprimé dans la méthode de -votre Table. - -Vous avez plusieurs façons de gérer les priorités d'events: - -#. Changez la priorité des écouteurs d'un Behavior en utilisant l'option - ``priority``. Cela modifiera la priorité de **toutes** les méthodes de - callback dans le Behavior:: - - // Dans la méthode initialize() d'une Table - $this->addBehavior('Tree', [ - // La valeur par défaut est 10, et les écouteurs sont déclenchés de - // la plus faible valeur de priorité à la plus haute. - 'priority' => 2, - ]); - -#. Modifiez la priorité dans votre classe ``Table`` en utilisant la méthode - ``Model.implementedEvents()``. Cela vous permet d'assigner une priorité - différente pour chaque fonction de callback:: - - // Dans une classe Table. - public function implementedEvents() - { - $events = parent::implementedEvents(); - $events['Model.beforeDelete'] = [ - 'callable' => 'beforeDelete', - 'priority' => 3 - ]; - -Behaviors -========= - -.. php:method:: addBehavior($name, array $options = []) - -.. start-behaviors - -Les Behaviors fournissent un moyen de créer des parties de logique -réutilisables horizontalement liées aux classes table. Vous vous demandez -peut-être pourquoi les behaviors sont des classes classiques et non des -traits. La raison principale tient aux écouteurs d'event. Alors que les traits -permettent de réutiliser des parties de logique, ils compliqueraient la -liaison des events. - -Pour ajouter un behavior à votre table, vous pouvez appeler la méthode -``addBehavior()``. Généralement, le meilleur endroit pour le faire est dans la -méthode ``initialize()``:: - - namespace App\Model\Table; - - use Cake\ORM\Table; - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->addBehavior('Timestamp'); - } - } - -Comme pour les associations, vous pouvez utiliser la :term:`syntaxe de plugin` -et fournir des options de configuration supplémentaires:: - - namespace App\Model\Table; - - use Cake\ORM\Table; - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->addBehavior('Timestamp', [ - 'events' => [ - 'Model.beforeSave' => [ - 'created_at' => 'new', - 'modified_at' => 'always' - ] - ] - ]); - } - } - -.. end-behaviors - -Vous pouvez en savoir plus sur les behaviors, y compris sur les behaviors -fournis par CakePHP dans le chapitre sur les :doc:`/orm/behaviors`. - -.. _configuring-table-connections: - -Configurer les Connexions -========================= - -Par défaut, toutes les instances de table utilisent la connexion à la base -de données ``default``. Si votre application utilise plusieurs connexions à la -base de données, vous voudrez peut-être configurer quelle table utilise -quelle connexion. C'est le rôle de la méthode ``defaultConnectionName()``:: - - namespace App\Model\Table; - - use Cake\ORM\Table; - - class ArticlesTable extends Table - { - public static function defaultConnectionName(): string { - return 'replica_db'; - } - } - -.. note:: - - La méthode ``defaultConnectionName()`` **doit** être statique. - -.. _table-registry-usage: -.. _table-locator-usage: - -Utiliser le TableLocator -======================== - -.. php:class:: TableLocator - -Comme nous l'avons vu précédemment, la classe TableLocator fournit un -registre/fabrique facile d'utilisation pour accéder aux instances des tables -de vos applications. Elle fournit aussi quelques autres fonctionnalités utiles. - -Configurer les Objets Table ---------------------------- - -.. php:method:: get($alias, $config) - -Lors du chargement des tables à partir du registry, vous pouvez personnaliser -leurs dépendances, ou utiliser les objets factices en fournissant un tableau -``$options``:: - - $articles = FactoryLocator::get('Table')->get('Articles', [ - 'className' => 'App\Custom\ArticlesTable', - 'table' => 'my_articles', - 'connection' => $connectionObject, - 'schema' => $schemaObject, - 'entityClass' => 'Custom\EntityClass', - 'eventManager' => $eventManager, - 'behaviors' => $behaviorRegistry - ]); - -Remarquez les paramètres de configurations de la connexion et du schéma, ils -ne sont pas des valeurs de type string mais des objets. La connexion va -prendre un objet ``Cake\Database\Connection`` et un schéma -``Cake\Database\Schema\Collection``. - -.. note:: - - Si votre table fait aussi une configuration supplémentaire dans sa méthode - ``initialize()``, ces valeurs vont écraser celles fournies au registre. - -Vous pouvez aussi pré-configurer le registre en utilisant la méthode -``setConfig()``. Les données de configuration sont stockées *par alias*, et peuvent -être surchargées par une méthode ``initialize()`` de l'objet:: - - FactoryLocator::get('Table')->setConfig('Users', ['table' => 'my_users']); - -.. note:: - - Vous pouvez configurer une table avant ou pendant la **première** fois - où vous accédez à l'alias. Faire ceci après que le registre est rempli - n'aura aucun effet. - -Vider le Registre ------------------ - -.. php:method:: clear() - -Pendant les cas de test, vous voudrez vider le registre. Faire ceci est souvent -utile quand vous utilisez les objets factices, ou modifiez les dépendances d'une -table:: - - FactoryLocator::get('Table')->clear(); - -Configurer le Namespace pour Localiser les Classes de l'ORM ------------------------------------------------------------ - -Si vous n'avez pas suivi les conventions, il est probable que vos classes -Table ou Entity ne soient pas detectées par CakePHP. Pour régler cela, vous -pouvez définir un namespace avec la méthode ``Cake\Core\Configure::write``. -Par exemple:: - - /src - /App - /My - /Namespace - /Model - /Entity - /Table - -Serait configuré avec:: - - Cake\Core\Configure::write('App.namespace', 'App\My\Namespace'); - diff --git a/fr/orm/validation.rst b/fr/orm/validation.rst deleted file mode 100644 index 081043a13f..0000000000 --- a/fr/orm/validation.rst +++ /dev/null @@ -1,792 +0,0 @@ -Valider des Données -################### - -Avant que vous ne :doc:`sauvegardiez des données` vous voudrez -probablement vous assurer que les données sont correctes et cohérentes. Dans -CakePHP, nous avons deux étapes de validation: - -1. Avant que les données de requête ne soient converties en entities, il est - possible d'appliquer des règles de validation concernant le type de données - et leur format. -2. Avant que les données ne soient sauvegardées, il est possible d'appliquer des - règles du domaine ou de l'application. Ces règles aident à garantir la - cohérence des données de votre application. - -.. _validating-request-data: - -Valider les Données Avant de Construire les Entities ----------------------------------------------------- - -Vous pouvez valider les données lors de leur transformation en entities. Cette -validation vous permet de vérifier le type, la forme et la taille des données. -Par défaut, les données de la requête seront validées avant d'être converties en -entities. -Si une ou plusieurs règles de validation échouent, l'entity retournée contiendra -des erreurs. Les champs comportant des erreurs ne figureront pas dans l'entity -renvoyée:: - - $article = $articles->newEntity($this->request->getData()); - if ($article->getErrors()) { - // La validation de l'entity a échoué. - } - -Voici ce qui se passe quand vous construisez une entity et que la validation est -activée: - -1. L'objet validator est créé. -2. Les fournisseurs de validation (*providers*) ``table`` et ``default`` sont - attachés. -3. La méthode de validation désignée est appelée. Par exemple - ``validationDefault()``. -4. L'événement ``Model.buildValidator`` est déclenché. -5. Les données de la requête sont validées. -6. Les données de la requête sont castées en types correspondant aux types des - colonnes. -7. Les erreurs sont définies dans l'entity. -8. Les données valides sont définies dans l'entity, tandis que les champs qui - ont échoué à la validation sont laissés de côté. - -Si vous voulez désactiver la validation lors de la conversion des données de la -requête, définissez l'option ``validate`` à false:: - - $article = $articles->newEntity( - $this->request->getData(), - ['validate' => false] - ); - -La méthode ``patchEntity()`` fonctionne de façon identique:: - - $article = $articles->patchEntity($article, $newData, [ - 'validate' => false - ]); - -Créer un Ensemble de Validation par Défaut -========================================== - -Les règles de validation sont définies dans la classe Table par commodité. -Cela définit quelles données doivent être validées en conjonction avec l'endroit -où elles seront sauvegardées. - -Pour créer un objet de validation par défaut dans votre table, créez la fonction -``validationDefault()``:: - - use Cake\ORM\Table; - use Cake\Validation\Validator; - - class ArticlesTable extends Table - { - public function validationDefault(Validator $validator): Validator - { - $validator - ->requirePresence('title', 'create') - ->notEmptyString('title'); - - $validator - ->allowEmptyString('link') - ->add('link', 'valid-url', ['rule' => 'url']); - - ... - - return $validator; - } - } - -Les méthodes et règles de validation disponibles proviennent de la classe -``Validator`` et sont documentées dans la section :ref:`creating-validators`. - -.. note:: - - Les objets de validation sont principalement destinés à valider les données - provenant de l'utilisateur, par exemple les formulaires ou d'autres - données postées dans la requête. - -Utiliser un Ensemble de Validation Différent -============================================ - -Outre la possibilité de désactiver la validation, vous pouvez choisir quel -ensemble de règles de validation que vous souhaitez appliquer:: - - $article = $articles->newEntity( - $this->request->getData(), - ['validate' => 'update'] - ); - -Ceci appellerait la méthode ``validationUpdate()`` sur l'instance de table pour -construire les règles requises. Par défaut, c'est la méthode -``validationDefault()`` qui sera utilisée. Voici un exemple de validator pour -notre table ``articles``:: - - class ArticlesTable extends Table - { - public function validationUpdate($validator) - { - $validator - ->notEmptyString('title', __('Vous devez indiquer un titre')) - ->notEmptyString('body', __('Un contenu est nécessaire')); - - return $validator; - } - } - -Vous pouvez avoir autant d'ensembles de validation que vous le souhaitez. -Consultez le :doc:`chapitre sur la validation ` -pour plus d'informations sur la construction des ensembles de règles de -validation. - -.. _using-different-validators-per-association: - -Utiliser un Ensemble de Validation Différent pour les Associations ------------------------------------------------------------------- - -Les ensembles de validation peuvent également être définis pour chaque association. -Lorsque vous utilisez les méthodes ``newEntity()`` ou ``patchEntity()``, vous -pouvez passer des options supplémentaires à chaque association qui doit être -convertie:: - - $data = [ - 'title' => 'Mon titre', - 'body' => 'Le texte', - 'user_id' => 1, - 'user' => [ - 'username' => 'marc' - ], - 'comments' => [ - ['body' => 'Premier commentaire'], - ['body' => 'Second commentaire'], - ] - ]; - - $article = $articles->patchEntity($article, $data, [ - 'validate' => 'update', - 'associated' => [ - 'Users' => ['validate' => 'signup'], - 'Comments' => ['validate' => 'custom'] - ] - ]); - -Combiner les Validators -======================= - -Grâce à la manière dont les objets validator sont construits, vous pouvez -diviser leur process de construction en petites étapes réutilisables:: - - // UsersTable.php - - public function validationDefault(Validator $validator): Validator - { - $validator->notEmptyString('username'); - $validator->notEmptyString('password'); - $validator->add('email', 'valid-email', ['rule' => 'email']); - ... - - return $validator; - } - - public function validationHardened(Validator $validator): Validator - { - $validator = $this->validationDefault($validator); - - $validator->add('password', 'length', ['rule' => ['lengthBetween', 8, 100]]); - - return $validator; - } - -Avec cette configuration, lors de l'utilisation de l'ensemble de validation -``hardened``, vous aurez aussi les règles de l'ensemble ``default``. - -Validation Providers -==================== - -Les règles de validation peuvent utiliser des fonctions définies par n'importe -quel provider connu. Par défaut, CakePHP définit quelques providers: - -1. Les méthodes sur la classe de table, ou ses behaviors, sont disponibles dans - le provider ``table``. -2. La classe du cœur :php:class:`~Cake\\Validation\\Validation` est - configurée en tant que provider ``default``. - -En créant une règle de validation, vous pouvez désigner le provider de cette -règle. Par exemple, si votre table a une méthode ``isValidRole``, vous pouvez -l'utiliser comme une règle de validation:: - - use Cake\ORM\Table; - use Cake\Validation\Validator; - - class UsersTable extends Table - { - public function validationDefault(Validator $validator): Validator - { - $validator - ->add('role', 'validRole', [ - 'rule' => 'isValidRole', - 'message' => __('Vous devez fournir un rôle valide'), - 'provider' => 'table', - ]); - - return $validator; - } - - public function isValidRole($value, array $context): bool - { - return in_array($value, ['admin', 'editor', 'author'], true); - } - - } - -Vous pouvez également utiliser des closures en tant que règles de validation:: - - $validator->add('name', 'myRule', [ - 'rule' => function ($value, array $context) { - if ($value > 1) { - return true; - } - - return 'Valeur incorrecte.'; - } - ]); - -Les méthodes de validation peuvent renvoyer des messages d'erreur lorsqu'elles échouent. -C'est un moyen simple de créer des messages d'erreur dynamiques basés en -fonction de la valeur fournie. - -Récupérer des Validators depuis les Tables -========================================== - -Une fois que vous avez créé quelques ensembles de validation dans votre classe -de table, vous pouvez récupérer l'objet résultant par son nom:: - - $defaultValidator = $usersTable->getValidator('default'); - - $hardenedValidator = $usersTable->getValidator('hardened'); - -Classe Validator par Défaut -============================ - -Comme mentionné ci-dessus, par défaut les méthodes de validation reçoivent -une instance de ``Cake\Validation\Validator``. Si vous souhaitez utiliser -une instance d'un validator personnalisé, vous pouvez utiliser l'attribut -``$_validatorClass`` de la table:: - - // Dans votre classe de table - public function initialize(array $config): void - { - $this->_validatorClass = \FullyNamespaced\Custom\Validator::class; - } - -.. _application-rules: - -Appliquer des Règles d'Application -================================== - -Au-delà de la validation basique des données qui est lancée quand -:ref:`les données de la requête sont converties en entities `, -de nombreuses applications ont des validations plus complexes qui doivent être -appliquées après la validation basique. - -Là où la validation s'assure que la forme ou la syntaxe de vos données -sont correctes, les règles s'attellent à comparer les données avec l'état -existant de votre application et/ou du réseau. - -Ces types de règles sont souvent appelées 'règles de domaine' ou -'règles d'application'. CakePHP utilise ce concept avec les 'RulesCheckers' -qui sont appliquées avant que les entities ne soient sauvegardées. Voici -quelques exemples de règles de domaine: - -* S'assurer qu'un email est unique. -* Transition d'états ou étapes de flux de travail, par exemple pour mettre à - jour le statut d'une facture. -* Eviter la modification d'articles ayant fait l'objet d'une suppression - logique. -* Appliquer des limites d'usage, que ce soit en nombre d'appels total ou en nombre - d'appels sur une période donnée. - -Les règles de domaine sont vérifiées lors de l'appel aux méthodes ``save()`` et -``delete()`` de Table. - -.. _creating-a-rules-checker: - -Créer un Vérificateur de Règles -------------------------------- - -Les classes de vérificateur de règles (*rules checkers*) sont généralement définies par la -méthode ``buildRules()`` dans votre classe de table. Les behaviors et les autres -souscripteurs d'events peuvent utiliser l'événement ``Model.buildRules`` pour -ajouter des règles au vérificateur pour une classe Table donnée:: - - use Cake\ORM\RulesChecker; - - // Dans une classe de table - public function buildRules(RulesChecker $rules): RulesChecker - { - // Ajoute une règle qui est appliquée pour les opérations de création et de mise à jour - $rules->add(function ($entity, $options) { - // Retourne un booléen pour indiquer si succès/échec - }, 'ruleName'); - - // Ajoute une règle pour la création. - $rules->addCreate(function ($entity, $options) { - // Retourne un booléen pour indiquer si succès/échec - }, 'ruleName'); - - // Ajoute une règle pour la mise à jour. - $rules->addUpdate(function ($entity, $options) { - // Retourne un booléen pour indiquer si succès/échec - }, 'ruleName'); - - // Ajoute une règle pour la suppression. - $rules->addDelete(function ($entity, $options) { - // Retourne un booléen pour indiquer si succès/échec - }, 'ruleName'); - - return $rules; - } - -Vos fonctions de règles ont pour paramètres l'Entity à vérifier et un tableau -d'options. Le tableau d'options contiendra ``errorField``, ``message`` et -``repository``. L'option ``repository`` contiendra la classe de table à -laquelle les règles sont attachées. Comme les règles acceptent n'importe quel -``callable``, vous pouvez aussi utiliser des fonctions d'instance:: - - $rules->addCreate([$this, 'uniqueEmail'], 'uniqueEmail'); - -ou des classes callable:: - - $rules->addCreate(new IsUnique(['email']), 'uniqueEmail'); - -Lors de l'ajout de règles, vous pouvez définir en options le champ correspondant -à la règle et le message d'erreur:: - - $rules->add([$this, 'isValidState'], 'validState', [ - 'errorField' => 'status', - 'message' => 'Cette facture ne peut pas être déplacée vers ce statut.' - ]); - -L'erreur sera visible lors de l'appel à la méthode ``getErrors()`` dans -l'entity:: - - $entity->getErrors(); // Contient les messages d'erreur des règles de domaine - -Créer des Règles d'Unicité de Champ ------------------------------------ - -Les règles d'unicité étant couramment utilisées, CakePHP inclut une classe -simple qui vous permet de définir des ensembles de champs uniques:: - - use Cake\ORM\Rule\IsUnique; - - // Un seul champ. - $rules->add($rules->isUnique(['email'])); - - // Une liste de champs - $rules->add($rules->isUnique( - ['username', 'account_id'], - 'Cette combinaison `username` & `account_id` est déjà utilisée.' - )); - -Quand vous définissez des règles sur des champs de clé étrangère, il est -important de se rappeler que seuls les champs listés sont utilisés dans la -règle. L'ensemble des règles d'unicité sera trouvé avec ``find('all')``. Cela -signifie que la règle ci-dessus ne sera pas déclenchée en définissant -``$user->account->id``. - -De nombreux moteurs de bases de données autorisent plusieurs valeurs NULL dans -les index UNIQUE. Pour simuler ce comportement, définissez l'option -``allowMultipleNulls`` à ``true``:: - - $rules->add($rules->isUnique( - ['username', 'account_id'], - ['allowMultipleNulls' => true] - )); - -Règles de Clés Etrangères -------------------------- - -Bien que vous puissiez vous reposer sur les erreurs de la base de données pour -imposer des contraintes, le fait d'utiliser des règles vous permet de fournir une -expérience utilisateur plus sympathique. C'est pour cela que CakePHP inclut -une classe de règle ``ExistsIn``:: - - // Un champ unique. - $rules->add($rules->existsIn('article_id', 'Articles')); - - // Plusieurs clés, utile pour des clés primaires composites. - $rules->add($rules->existsIn(['site_id', 'article_id'], 'Articles')); - -Les champs dont vous demandez à vérifier l'existence dans la table -correspondante doivent faire partie de la clé primaire. - -Vous pouvez forcer ``existsIn`` à accepter qu'une partie de votre clé étrangère -composite soit null:: - - // Exemple: NodesTable contient une clé primaire composite (parent_id, site_id). - // Un "Node" peut faire référence à un Node parent mais ce n'est pas - // obligatoire. Dans ce dernier cas, parent_id est null. - // Nous autorisons la validation de cette règle même si les champs qui sont - // nullables, comme parent_id, sont null: - $rules->add($rules->existsIn( - ['parent_id', 'site_id'], // Schema: parent_id NULL, site_id NOT NULL - 'ParentNodes', - ['allowNullableNulls' => true] - )); - - // Un Node doit cependant toujours avoir une référence à un Site. - $rules->add($rules->existsIn(['site_id'], 'Sites')); - -Dans la majorité des bases de données SQL, les index ``UNIQUE`` sur plusieurs -colonnes autorisent plusieurs valeurs null car ``NULL`` n'est -pas égal à lui même. Même si CakePHP autorise par défaut plusieurs valeurs null, -vous pouvez inclure les nulls dans vos -vérifications d'unicité en utilisant ``allowMultipleNulls``:: - - // Une seule valeur null peut exister dans `parent_id` et `site_id` - $rules->add($rules->existsIn( - ['parent_id', 'site_id'], - 'ParentNodes', - ['allowMultipleNulls' => false] - )); - -Règles sur le Nombre de Valeurs d'une Association -------------------------------------------------- - -Si vous avez besoin de valider qu'une propriété ou une association contient un -certain nombre de valeurs, vous pouvez utiliser la règle ``validCount()``:: - - // Dans le fichier ArticlesTable.php - // Pas plus de 5 tags sur un article. - $rules->add($rules->validCount('tags', 5, '<=', 'Vous ne pouvez avoir que 5 tags')); - -Quand vous définissez des règles qui basées sur un nombre de valeurs, le troisième -paramètre vous permet de définir l'opérateur de comparaison à utiliser. Les -opérateurs acceptés sont ``==``, ``>=``, ``<=``, ``>``, ``<``, et ``!=``. Pour -vérifier que le décompte d'une propriété est entre certaines valeurs, utilisez -deux règles:: - - // Dans le fichier ArticlesTable.php - // Entre 3 et 5 tags - $rules->add($rules->validCount('tags', 3, '>=', 'Vous devez avoir au moins 3 tags')); - $rules->add($rules->validCount('tags', 5, '<=', 'Vous devez avoir au plus 5 tags')); - -Notez que ``validCount`` retourne ``false`` si la propriété ne peut pas être comptée -ou n'existe pas:: - - // La sauvegarde échouera si tags est null - $rules->add($rules->validCount('tags', 0, '<=', 'Vous ne devez pas avoir de tags')); - -Règles de Contraintes d'Association ------------------------------------ - -La règle ``LinkConstraint`` vous permet d'émuler des contraintes SQL dans les -bases de données qui ne les supportent pas, ou quand vous voulez fournir des -messages d'erreur plus sympathiques quand la contrainte échoue. Cette règle vous -permet de vérifier si une association a ou non des enregistrements liés en -fonction du mode utilisé:: - - // S'assure que chaque commentaire est lié à un Article lors de sa mise à jour. - $rules->addUpdate($rules->isLinkedTo( - 'Articles', - 'article', - 'Spécifiez un article' - )); - - // S'assure qu'un article n'a pas de commentaire au moment de sa suppression. - $rules->addDelete($rules->isNotLinkedTo( - 'Comments', - 'comments', - 'Impossible de supprimer un article qui contient des commentaires.' - )); - -.. versionadded:: 4.0.0 - -Utiliser les Méthodes d'Entity en tant que Règles -------------------------------------------------- - -Vous pouvez utiliser les méthodes d'une entity en tant que règles de domaine:: - - $rules->add(function ($entity, $options) { - return $entity->isOkLooking(); - }, 'ruleName'); - -Utiliser des Règles Conditionnelles ------------------------------------ - -Vous pouvez appliquer des règles conditionnelles en fonction des données de -l'entity:: - - $rules->add(function ($entity, $options) use($rules) { - if ($entity->role == 'admin') { - $rule = $rules->existsIn('user_id', 'Admins'); - - return $rule($entity, $options); - } - if ($entity->role == 'user') { - $rule = $rules->existsIn('user_id', 'Users'); - - return $rule($entity, $options); - } - - return false; - }, 'userExists'); - -Messages d'Erreur Dynamiques/Conditionnels ------------------------------------------- - -Les règles, qu'elles soient :ref:`des callables personnalisés ` -ou :ref:`des objets Rule `, peuvent soit retourner -un booléen indiquant si elles ont réussi, soit retourner une chaîne qui -signifie que la règle a échoué et que la chaîne doit être utilisée comme message -d'erreur. - -Les messages d'erreur possibles définis par l'option ``message`` seront écrasés -par ceux retournés par la règle:: - - $rules->add( - function ($entity, $options) { - if (!$entity->length) { - return false; - } - - if ($entity->length < 10) { - return "Message d'erreur quand la valeur est inférieure à 10"; - } - - if ($entity->length > 20) { - return "Message d'erreur quand la valeur est supérieure à 20"; - } - - return true; - }, - 'ruleName', - [ - 'errorField' => 'length', - 'message' => "Message d'erreur générique utilisé quand la règle retourne `false`." - ] - ); - -.. note:: - - Notez que pour que le message retourné puisse être utilisé, vous *devez* - aussi définir l'option ``errorField``, sinon la règle va se contenter - d'échouer silencieusement, c'est-à-dire sans insérer le message d'erreur - dans l'entity ! - -Créer des Règles Personnalisées Réutilisables ---------------------------------------------- - -Vous pouvez vouloir réutiliser des règles de domaine personnalisées. Vous pouvez -le faire en créant votre propre règle invokable:: - - use App\ORM\Rule\IsUniqueWithNulls; - // ... - public function buildRules(RulesChecker $rules): RulesChecker - { - $rules->add(new IsUniqueWithNulls(['parent_id', 'instance_id', 'name']), 'uniqueNamePerParent', [ - 'errorField' => 'name', - 'message' => 'Doit être unique pour chaque parent.' - ]); - - return $rules; - } - -Regardez les règles du cœur pour avoir des exemples sur la façon de créer de -telles règles. - -.. _creating-custom-rule-objects: - -Créer des Objets de Règles Personnalisées ------------------------------------------ - -Si votre application a des règles qui sont souvent réutilisées, il peut être -utile de packager ces règles dans des classes réutilisables:: - - // Dans src/Model/Rule/CustomRule.php - namespace App\Model\Rule; - - use Cake\Datasource\EntityInterface; - - class CustomRule - { - public function __invoke(EntityInterface $entity, array $options) - { - // Faire le boulot ici - return false; - } - } - - // Ajouter la règle personnalisée - use App\Model\Rule\CustomRule; - - $rules->add(new CustomRule(/* ... */), 'ruleName'); - -En ajoutant des classes de règles personnalisées, vous pouvez garder votre code -DRY et tester vos règles de domaine isolément. - -Désactiver les Règles ---------------------- - -Quand vous sauvegardez une entity, vous pouvez désactiver les règles si c'est -nécessaire:: - - $articles->save($article, ['checkRules' => false]); - -Validation vs. Règles d'Application -=================================== - -L'ORM de CakePHP est unique dans le sens où il utilise une approche à deux -couches pour la validation. - -La première couche est la validation. Les règles de validation ont pour objet -d'opérer sans état (*stateless*). Elles servent à s'assurer que la forme, les -types de données et le format des données sont corrects. - -La seconde couche est celle des règles d'application. Les règles d'application -sont plus appropriés pour vérifier l'état des propriétés de vos entities. Par exemple, -les règles de validation peuvent permettre de s'assurer qu'une adresse email est -valide, tandis qu'une règle d'application permet de s'assurer que l'adresse -email est unique. - -Comme vous avez pu le voir, la première couche est réalisée par l'objet -``Validator`` lors de l'appel à ``newEntity()`` ou ``patchEntity()``:: - - $validatedEntity = $articlesTable->newEntity( - $donneesDouteuses, - ['validate' => 'maRegle'] - ); - $validatedEntity = $articlesTable->patchEntity( - $entity, - $donneesDouteuses, - ['validate' => 'maRegle'] - ); - -Dans l'exemple ci-dessus, nous allons utiliser un validateur 'maRegle', qui est -défini en utilisant la méthode ``validationMaRegle()``:: - - public function validationMaRegle($validator) - { - $validator->add( - // ... - ); - - return $validator; - } - -La validation présuppose que les arguments passés sont des chaînes de caractères -ou des tableaux, puisque c'est ce qui est passé dans n'importe quelle requête:: - - // Dans src/Model/Table/UsersTable.php - public function validatePasswords($validator) - { - $validator->add('confirm_password', 'no-misspelling', [ - 'rule' => ['compareWith', 'password'], - 'message' => 'Les mots de passe ne sont pas identiques', - ]); - - // ... - - return $validator; - } - -La validation **n'est pas** déclenchée lorsque vous définissez directement une -propriété sur vos entities:: - - $userEntity->email = 'pas un email!!'; - $usersTable->save($userEntity); - -Dans l'exemple ci-dessus, l'entity sera sauvegardée car la validation n'est -déclenchée que par les méthodes ``newEntity()`` et ``patchEntity()``. Le second -niveau de validation est conçu pour gérer cette situation. - -Les règles d'application, comme expliqué précédement, seront vérifiées à chaque -appel de ``save()`` ou ``delete()``:: - - // Dans src/Model/Table/UsersTable.php - public function buildRules(RulesChecker $rules): RulesChecker - { - $rules->add($rules->isUnique(['email'])); - - return $rules; - } - - // Autre part dans le code de votre application - $userEntity->email = 'a@email.en.doublon'; - $usersTable->save($userEntity); // Retourne false - -La validation est conçue pour les données provenant directement des -utilisateurs, tandis que les règles d'application sont conçues spécifiquement -pour les transitions de données générées à l'intérieur de l'application:: - - // Dans src/Model/Table/CommandesTable.php - public function buildRules(RulesChecker $rules): RulesChecker - { - $check = function($commande) { - if ($commande->livraison !== 'gratuit') { - return true; - } - - return $commande->prix >= 100; - }; - $rules->add($check, [ - 'errorField' => 'livraison', - 'message' => 'Pas de frais de port gratuits pour une commande de moins de 100!' - ]); - - return $rules; - } - - // Autre part dans le code de l'application - $commande->prix = 50; - $commande->livraison = 'gratuit'; - $commandesTable->save($commande); // Retourne false - -Utiliser la Validation en tant que Règle d'Application ------------------------------------------------------- - -Dans certaines situations, vous voudrez peut-être lancer les mêmes routines -à la fois pour des données générées par un utilisateur et pour l'intérieur de -votre application. Cela peut se produire lorsque vous exécutez un script CLI -qui définit des propriétés directement dans des entities:: - - // Dans src/Model/Table/UsersTable.php - public function validationDefault(Validator $validator): Validator - { - $validator->add('email', 'email_valide', [ - 'rule' => 'email', - 'message' => 'Email invalide' - ]); - - // ... - - return $validator; - } - - public function buildRules(RulesChecker $rules): RulesChecker - { - // Ajoute des règles de validation - $rules->add(function($entity) { - $data = $entity->extract($this->getSchema()->columns(), true); - if (!$entity->isNew() && !empty($data)) { - $data += $entity->extract((array)$this->getPrimaryKey()); - } - $validator = $this->getValidator('default'); - $errors = $validator->validate($data, $entity->isNew()); - $entity->setErrors($errors); - - return empty($errors); - }); - - // ... - - return $rules; - } - -Lors de l'exécution du code suivant, la sauvegarde échouera grâce à la nouvelle -règle d'application qui a été ajoutée:: - - $userEntity->email = 'Pas un email!!!'; - $usersTable->save($userEntity); - $userEntity->getErrors('email'); // Email invalide - -Le même résultat est attendu lors de l'utilisation de ``newEntity()`` ou -``patchEntity()``:: - - $userEntity = $usersTable->newEntity(['email' => 'Pas un email!!']); - $userEntity->getErrors('email'); // Email invalide diff --git a/fr/pdf-contents.rst b/fr/pdf-contents.rst deleted file mode 100644 index abcfa76a95..0000000000 --- a/fr/pdf-contents.rst +++ /dev/null @@ -1,60 +0,0 @@ -:orphan: - -Contenu -####### - -.. toctree:: - :maxdepth: 2 - - intro - quickstart - appendices/migration-guides - tutorials-and-examples - contributing - - installation - development/configuration - development/routing - controllers/request-response - controllers - views - orm - - controllers/components/authentication - core-libraries/caching - bake - console-commands - development/debugging - deployment - core-libraries/email - development/errors - core-libraries/events - core-libraries/internationalization-and-localization - core-libraries/logging - core-libraries/form - controllers/components/pagination - plugins - development/rest - security - development/sessions - development/testing - core-libraries/validation - - core-libraries/app - core-libraries/collections - core-libraries/file-folder - core-libraries/hash - core-libraries/httpclient - core-libraries/inflector - core-libraries/number - core-libraries/registry-objects - core-libraries/text - core-libraries/time - core-libraries/xml - - core-libraries/global-constants-and-functions - chronos - debug-kit - migrations - elasticsearch - appendices diff --git a/fr/phinx.rst b/fr/phinx.rst deleted file mode 100644 index 8e61dc58f3..0000000000 --- a/fr/phinx.rst +++ /dev/null @@ -1,4 +0,0 @@ -Phinx Migrations -################ - -Cette page a été `déplacée `__. diff --git a/fr/plugins.rst b/fr/plugins.rst deleted file mode 100644 index 6d186947a2..0000000000 --- a/fr/plugins.rst +++ /dev/null @@ -1,777 +0,0 @@ -Plugins -####### - -CakePHP vous permet de mettre en place une combinaison de controllers, models -et vues et de les distribuer comme un plugin d'application pré-packagé que d'autres -peuvent utiliser dans leurs applications CakePHP. Vous avez développé un module de -gestion des utilisateurs sympa, un simple blog, ou un module de service web -dans une de vos applications ? Pourquoi ne pas en faire un plugin CakePHP ? -De cette manière, vous pourrez le réutiliser dans d'autres applications et le -partager avec la communauté. - -Un plugin CakePHP est séparé de l'application qui l'héberge et fournit généralement -des fonctionnalités précises qui sont packagées de manière à être réutilisées très -facilement dans d'autres applications. L'application et le plugin fonctionnent dans -leurs espaces dédiés mais partage des propriétés spécifiques à l'application (comme -les paramètres de connexion à la base de données par exemple) qui sont définies et -partagées au travers de la configuration de l'application. - -Chaque plugin est censé définir son namespace de top-niveau. Par exemple -``DebugKit``. Par convention, les plugins utilisent leur nom de package pour -leur namespace. Si vous souhaitez utiliser un namespace différent, vous pouvez -configurer le namespace du plugin, quand les plugins sont chargés. - -Installer un Plugin Avec Composer -================================= - -Plusieurs plugins sont disponibles sur `Packagist `_ -et peuvent être installés avec ``Composer``. Pour installer DebugKit, vous -feriez ce qui suit:: - - php composer.phar require cakephp/debug_kit - -Ceci installe la dernière version de DebugKit et met à jour vos fichiers -**composer.json**, **composer.lock**, met à jour **vendor/cakephp-plugins.php** -et met à jour votre autoloader. - -Installer un Plugin Manuellement -================================ - -Si le plugin que vous voulez installer n'est pas disponible sur packagist.org. -Vous pouvez cloner ou copier le code du plugin dans votre répertoire -**plugins**. Si vous voulez installer un plugin appelé ``ContactManager``, vous -créez un sous-répertoire nommé ``ContactManager`` dans **plugins**. C'est dans -ce sous-répertoire que vous mettrez les répertoires src, tests, et tous les -autres répertoires du plugin. - -.. _autoloading-plugin-classes: - -Autoload Manuel des Classes de Plugin -------------------------------------- - -Si vous installez vos plugins par ``composer`` ou ``bake``, vous ne devriez pas -avoir besoin de configurer l'autoload de classes pour vos plugins. - -Si vous installez manuellemen un plugin appelé par exemple ``MyPlugin``, vous -devrez modifier le fichier **composer.json** de votre application pour qu'il -contienne les informations suivantes: - -.. code-block:: json - - { - "autoload": { - "psr-4": { - "MyPlugin\\": "plugins/MyPlugin/src/" - } - }, - "autoload-dev": { - "psr-4": { - "MyPlugin\\Test\\": "plugins/MyPlugin/tests/" - } - } - } - -Si vous utilisez un namespace pour vos plugins, le mappage du namespace vers le -chemin devra ressembler à: - -.. code-block:: json - - { - "autoload": { - "psr-4": { - "AcmeCorp\\Users\\": "plugins/AcmeCorp/Users/src/", - "AcmeCorp\\Users\\Test\\": "plugins/AcmeCorp/Users/tests/" - } - } - } - -De plus, vous devrez dire à Composer de rafraîchir son cache d'autoload: - -.. code-block:: console - - php composer.phar dumpautoload - -Charger un Plugin -================= - -Si vous voulez utiliser des routes du plugin, des commandes de console, des -middlewares, des écouteurs d'événements, des templates ou des ressources du -webroot, il faudra d'abord charger le plugin. -C'est la function ``bootstrap()`` de votre application qui devra s'en charger:: - - // Dans src/Application.php - use Cake\Http\BaseApplication; - use ContactManager\ContactManagerPlugin; - - class Application extends BaseApplication { - public function bootstrap() - { - parent::bootstrap(); - // Charge la plugin ContactManager d'après son nom - $this->addPlugin(ContactManagerPlugin::class); - - // Charger un plugin avec un namespace d'après son "nom court" - $this->addPlugin('AcmeCorp/ContactManager'); - - // Charger ne dépendance de développement qui n'existera pas en environnement de production. - $this->addOptionalPlugin('AcmeCorp/ContactManager'); - } - } - -Si vous voulez juste utiliser des helpers, behaviors ou components d'un plugin, -vous n'avez pas besoin de le charger explicitement, bien que nous recommandions -de toujours le faire. - -Il existe aussi une commande de shell bien pratique pour activer un plugin. -Exécutez cette instruction: - -.. code-block:: console - - bin/cake plugin load ContactManager - -Cela va mettre à jour la méthode bootstrap de votre application, ou insérer le -code ``$this->addPlugin('ContactManager');`` dans le bootstrap à votre place. - -.. versionadded:: 4.1.0 - Ajout de la méthode ``addOptionalPlugin()``. - -.. _plugin-configuration: - -Configuration du Plugin -======================= - -Les plugins proposent plusieurs *hooks* permettant à un plugin de s'injecter -lui-même aux endroits appropriés de votre application. Les *hooks* sont: - -* ``bootstrap`` Utilisé pour charger les fichiers de configuration par défaut - d'un plugin, définir des constantes et d'autres fonctions globales. -* ``routes`` Utilisé pour charger les routes pour un plugin. Il est déclenché - après le chargement des routes de l'application. -* ``middleware`` Utilisé pour ajouter un middleware de plugin à la file de - middlewares de l'application. -* ``console`` Utilisé pour ajouter des commandes de console à la collection des - commandes d'une application. - -En chargeant les plugins, vous pouvez configurer quels *hooks* doivent être -activés. Par défaut, tous les *hooks* sont désactivés dans les plugins qui n'ont -pas de :ref:`plugin-objects`. Les plugins du nouveau style autorisent les -auteurs de plugins à définir des valeurs par défaut, que vous pouvez configurer -dans votre application:: - - // Dans Application::bootstrap() - use ContactManager\ContactManagerPlugin; - - // Désactiver les routes pour le plugin ContactManager - $this->addPlugin(ContactManagerPlugin::class, ['routes' => false]); - -Vous pouvez configurer les *hooks* avec un tableau d'options, ou par les -méthodes fournies par les classes de plugin:: - - // Dans Application::bootstrap() - use ContactManager\ContactManagerPlugin; - - // Utiliser disable/enable pour configurer les hooks. - $plugin = new ContactManagerPlugin(); - - $plugin->disable('bootstrap'); - $plugin->enable('routes'); - $this->addPlugin($plugin); - -Les objets plugins connaissent aussi leurs noms et leurs informations de -chemin:: - - $plugin = new ContactManagerPlugin(); - - // Obtenir le nom du plugin. - $name = $plugin->getName(); - - // Chemin vers la racine du plugin, et autres chemins. - $path = $plugin->getPath(); - $path = $plugin->getConfigPath(); - $path = $plugin->getClassPath(); - -Utiliser un Plugin -================== - -Vous pouvez référencer les controllers, models, components, behaviors et -helpers du plugin en préfixant le nom du plugin. - -Par exemple, Supposons que vous veuillez utiliser le ContactInfoHelper du plugin -ContactManager pour afficher des informations de contact formatées dans une de -vos vues. Dans votre controller, vous pouvez utiliser ``addHelper()`` de la -façon suivante:: - - $this->viewBuilder()->addHelper('ContactManager.ContactInfo'); - -.. note:: - Ce nom de classe séparé par un point se réfère à la :term:`syntaxe de - plugin`. - -Vous serez ensuite capable d'accéder à ``ContactInfoHelper`` comme tout autre -helper dans votre vue, comme ceci:: - - echo $this->ContactInfo->address($contact); - -Le splugins peuvent utiliser des models, components, behaviors et helper fournis -par l'application, ou par d'autres plugins si nécessaire:: - - // Utiliser un component d'application - $this->loadComponent('AppFlash'); - - // Utiliser un behavior d'un autre plugin - $this->addBehavior('AutrePlugin.AuditLog'); - -.. _plugin-create-your-own: - -Créer Vos Propres Plugins -========================= - -En exemple de travail, commençons par créer le plugin ContactManager -référencé ci-dessus. Pour commencer, nous allons configurer votre structure -de répertoire basique. Cela devrait ressembler à ceci:: - - /src - /plugins - /ContactManager - /config - /src - /ContactManagerPlugin.php - /Controller - /Component - /Model - /Table - /Entity - /Behavior - /View - /Helper - /templates - /layout - /tests - /TestCase - /Fixture - /webroot - -Notez le nom du dossier du plugin, '**ContactManager**'. Il est important -que ce dossier ait le même nom que le plugin. - -Dans le dossier plugin, vous remarquerez qu'il ressemble beaucoup à une -application CakePHP, et c'est au fond ce que c'est. Vous n'avez à inclure -aucun de vos dossiers si vous ne les utilisez pas. Certains plugins peuvent -ne contenir qu'un Component ou un Behavior, et dans ce cas ils peuvent -carrément ne pas avoir de répertoire 'templates'. - -Un plugin peut aussi avoir n'importe quels autres répertoires similaires à ceux -d'une application comme Config, Console, webroot, etc... - -Créer un Plugin en utilisant Bake ---------------------------------- - -Le processus de création des plugins peut être grandement simplifié en utilisant -le shell bake. - -Pour "cuisiner" (*bake*) un plugin, utilisez la commande suivante: - -.. code-block:: console - - bin/cake bake plugin ContactManager - -Vous pouvez utiliser bake pour créer des classes dans votre plugin. Par exemple, -pour générer un contrôleur de plugin, vous pouvez lancer: - -.. code-block:: console - - bin/cake bake controller --plugin ContactManager Contacts - -Rendez-vous au chapitre -:doc:`/bake/usage` si vous avez le moindre -problème avec l'utilisation de la ligne de commande. Assurez-vous de -re-générer votre autoloader après avoir créé votre plugin: - -.. code-block:: console - - php composer.phar dumpautoload - -.. _plugin-objects: - -Plugin Classes -============== - -Les Objets Plugin permettent à un auteur de plugin de spécifier une logique de -démarrage, de définire des *hooks* par défaut, de charger des routes, un -middleware ou des commandes de console. Les objets Plugin se trouvent dans -**src/Plugin.php**. Pour notre plugin ContactManager, notre classe de plugin -pourrait ressembler à:: - - namespace ContactManager; - - use Cake\Core\BasePlugin; - use Cake\Core\ContainerInterface; - use Cake\Core\PluginApplicationInterface; - use Cake\Console\CommandCollection; - use Cake\Http\MiddlewareQueue; - - class ContactManagerPlugin extends BasePlugin - { - public function middleware(MiddlewareQueue $middleware): MiddlewareQueue - { - // Ajouter le middleware ici. - $middleware = parent::middleware($middleware); - - return $middleware; - } - - public function console(CommandCollection $commands): CommandCollection - { - // Ajouter les commandes de console ici. - $commands = parent::console($commands); - - return $commands; - } - - public function bootstrap(PluginApplicationInterface $app): void - { - // Ajouter des constantes, charger une configuration par défaut. - // Par défaut, cela chargera `config/bootstrap.php` dans le plugin. - parent::bootstrap($app); - } - - public function routes($routes): void - { - // Ajouter des routes. - // Par défaut, cela chargera `config/routes.php` dans le plugin. - parent::routes($routes); - } - - /** - * Enregistrer des services de container d'application. - * - * @param \Cake\Core\ContainerInterface $container Le Container à mettre à jour. - * @return void - * @link https://book.cakephp.org/4/fr/development/dependency-injection.html#dependency-injection - */ - public function services(ContainerInterface $container): void - { - // Ajoutez vos services ici - } - } - -.. _plugin-routes: - -Routes de Plugins -================= - -Les plugins peuvent mettre à disposition des fichiers de routes contenant leurs -propres routes. Chaque plugin peut contenir un fichier **config/routes.php**. Ce -fichier de routes peut être chargé quand le plugin est ajouté, ou dans le -fichier de routes de l'application. -Pour créer les routes du plugin ContractManager, ajoutez le code suivant dans -**plugins/ContactManager/config/routes.php**:: - - plugin( - 'ContactManager', - ['path' => '/contact-manager'], - function ($routes) { - $routes->setRouteClass(DashedRoute::class); - - $routes->get('/contacts', ['controller' => 'Contacts']); - $routes->get('/contacts/{id}', ['controller' => 'Contacts', 'action' => 'view']); - $routes->put('/contacts/{id}', ['controller' => 'Contacts', 'action' => 'update']); - } - ); - -Le code ci-dessus connectera les routes par défaut de votre plugin. Vous pourrez -personnaliser ce fichier plus tard avec des routes plus spécifiques. - -Avant de pouvoir accéder à vos controllers, assuez-vous que le plugin est bien -chargé et que les routes du plugin le sont également. Dans votre fichier -**src/Application.php**, ajoutez la ligne suivante:: - - $this->addPlugin('ContactManager', ['routes' => true]); - -Vous pouvez également charger les routes du plugin dans la liste des routes de votre -application. De cette manière, vous avez plus de contrôle sur le chargement des -routes de plugin et cela vous permet d'englober les routes du plugin -dans des préfixes et des 'scopes' supplémentaires:: - - $routes->scope('/', function ($routes) { - // Connect other routes. - $routes->scope('/backend', function ($routes) { - $routes->loadPlugin('ContactManager'); - }); - }); - -Le code ci-dessus vous permettrait d'avoir des URLs de la forme ``/backend/contact-manager/contacts``. - -Controllers du Plugin -===================== - -Les controllers pour notre plugin ContactManager seront stockés dans -**plugins/ContactManager/src/Controller/**. Puisque notre activité principale -est la gestion des contacts, nous aurons besoin d'un ContactsController pour ce -plugin. - -Ainsi, nous mettons notre nouveau ContactsController dans -**plugins/ContactManager/src/Controller** et il ressemblerait à cela:: - - // plugins/ContactManager/src/Controller/ContactsController.php - namespace ContactManager\Controller; - - use ContactManager\Controller\AppController; - - class ContactsController extends AppController - { - - public function index() - { - //... - } - } - -Créez également le ``AppController`` si vous n'en avez pas déjà un:: - - // plugins/ContactManager/src/Controller/AppController.php - namespace ContactManager\Controller; - - use App\Controller\AppController as BaseController; - - class AppController extends BaseController - { - } - -Un ``AppController`` dédié à votre plugin peut contenir la logique commune à -tous les controllers de votre plugin, et n'est pas obligatoire si vous ne -souhaitez pas en utiliser. - -Si vous souhaitez accéder à ce que nous avons fait jusqu'ici, visitez l'URL -``/contact-manager/contacts``. Vous aurez une erreur "Missing Model" -parce que nous n'avons pas encore défini de model Contact. - -Si votre application inclut le routage par défaut fourni par CakePHP, vous -serez en mesure d'accéder aux controllers de votre plugin en utilisant des URLs -comme:: - - // Accéder à la route index d'un controller de plugin. - /contact-manager/contacts - - // Toute action sur un controller de plugin. - /contact-manager/contacts/view/1 - -Si votre application définit des préfixes de routage, le routage par défaut de -CakePHP connectera aussi les routes qui utilisent le modèle suivant:: - - /{prefix}/{plugin}/{controller} - /{prefix}/{plugin}/{controller}/{action} - -Consultez la section sur :ref:`plugin-configuration` pour plus d'informations -sur la façon de charger les fichiers de routes spécifiques à un plugin. - -Pour les plugins que vous n'avez pas créés avec bake, vous devrez aussi modifier -le fichier ``composer.json`` pour ajouter votre plugin aux classes d'autoload. -Vous pouvez le faire en suivant la documentation -:ref:`autoloading-plugin-classes`. - -.. _plugin-models: - -Models du Plugin -================ - -Les Models pour le plugin sont stockés dans **plugins/ContactManager/src/Model**. -Nous avons déjà défini un ContactsController pour ce plugin, donc créons la -table et l'entity pour ce controller:: - - // plugins/ContactManager/src/Model/Entity/Contact.php: - namespace ContactManager\Model\Entity; - - use Cake\ORM\Entity; - - class Contact extends Entity - { - } - - // plugins/ContactManager/src/Model/Table/ContactsTable.php: - namespace ContactManager\Model\Table; - - use Cake\ORM\Table; - - class ContactsTable extends Table - { - } - -Si vous avez besoin de faire référence à un model dans votre plugin lors de la -construction des associations ou la définition de classes d'entity, vous devrez -inclure le nom du plugin avec le nom de la classe, séparés par un point. Par -exemple:: - - // plugins/ContactManager/src/Model/Table/ContactsTable.php: - namespace ContactManager\Model\Table; - - use Cake\ORM\Table; - - class ContactsTable extends Table - { - public function initialize(array $config): void - { - $this->hasMany('ContactManager.AltName'); - } - } - -Si vous préférez que les clés du tableau pour l'association n'aient pas le -préfixe du plugin, utilisez la syntaxe alternative:: - - // plugins/ContactManager/src/Model/Table/ContactsTable.php: - namespace ContactManager\Model\Table; - - use Cake\ORM\Table; - - class ContactsTable extends Table - { - public function initialize(array $config): void - { - $this->hasMany('AltName', [ - 'className' => 'ContactManager.AltName', - ]); - } - } - -Vous pouvez utiliser ``Cake\ORM\Locator\LocatorAwareTrait``` pour charger les -tables de votre plugin en utilisant l'habituelle :term:`syntaxe de plugin`:: - - // Les controllers utilisent déjà LocatorAwareTrait, donc vous n'avez pas besoin d'ajouter ceci. - use Cake\ORM\Locator\LocatorAwareTrait; - - $contacts = $this->fetchTable('ContactManager.Contacts'); - -Vues du Plugin -============== - -Les Vues se comportent exactement comme elles le font dans les applications -normales. Placez-les juste dans le bon dossier à l'intérieur du dossier -``plugins/[PluginName]/templates/``. Pour notre plugin ContactManager, nous -aurons besoin d'une vue pour notre action ``ContactsController::index()``, donc -ajoutons-y ceci:: - - // plugins/ContactManager/templates/Contacts/index.php: -

      Contacts

      -

      Ce qui suit est une liste triable de vos contacts

      - - -Les Plugins peuvent fournir leurs propres layouts. Pour ajouter des layouts de -plugin, placez vos fichiers de template dans -``plugins/[PluginName]/templates/layout``. Pour utiliser le layout d'un plugin -dans votre controller, vous pouvez faire comme ceci:: - - $this->viewBuilder()->setLayout('ContactManager.admin'); - -Si le préfix de plugin n'est pas précisé, le fichier de vue/layout sera localisé -normalement. - -.. note:: - - Pour des informations sur la façon d'utiliser les elements à partir d'un - plugin, consultez :ref:`view-elements`. - -Redéfinir des Templates de Plugin depuis l'Intérieur de votre Application -------------------------------------------------------------------------- - -Vous pouvez redéfinir toutes les vues du plugin à partir de l'intérieur de -votre app en utilisant des chemins spéciaux. Si vous avez un plugin appelé -'ContactManager', vous pouvez redéfinir les fichiers de template du plugin avec -une logique de vue spécifique à l'application, en créant des fichiers sur le -modèle de **templates/plugin/[Plugin]/[Controller]/[view].php**. Pour le -controller Contacts, vous pourriez écrire le fichier suivant:: - - templates/plugin/ContactManager/Contacts/index.php - -La création de ce fichier vous permettra de redéfinir -**plugins/ContactManager/templates/Contacts/index.php**. - -Si votre plugin fait partie d'une dépendence de Composer (ex: -'LeVendor/LePlugin'), le chemin vers la vue 'index' du controller Contacts -sera:: - - templates/plugin/LeVendor/LePlugin/Custom/index.php - -La création de ce fichier vous permettra de redéfinir -**vendor/levendor/leplugin/templates/Custom/index.php**. - -Si le plugin implémente un préfixe de routing, vous devez inclure ce préfixe -dans le template réécrit par votre application. Par exemple, si le plugin -'ContactManager' implémente un préfixe 'Admin', le chemin du template réécrit -sera:: - - templates/plugin/ContactManager/Admin/ContactManager/index.php - -.. _plugin-assets: - -Ressources de Plugin -==================== - -Les ressources web du plugin (mais pas les fichiers PHP) peuvent être servies -à travers le répertoire ``webroot`` du plugin, exactement comme les ressources -de l'application principale:: - - /plugins/ContactManager/webroot/ - css/ - js/ - img/ - flash/ - pdf/ - -Vous pouvez mettre n'importe quel type de fichier dans tout répertoire, -exactement comme un webroot habituel. - -.. warning:: - - La gestion des ressources statiques comme les fichiers images, Javascript et - CSS à travers le Dispatcher est très inefficace. Consultez - :ref:`symlink-assets` pour plus d'informations. - -Liens vers les Ressources dans des Plugins ------------------------------------------- - -Vous pouvez utiliser la :term:`syntaxe de plugin` pour faire un lien vers les -ressources d'un plugin en utilisant les méthodes script, image ou css de -:php:class:`~Cake\\View\\Helper\\HtmlHelper`:: - - // Génère une URL de /contact_manager/css/styles.css - echo $this->Html->css('ContactManager.styles'); - - // Génère une URL de /contact_manager/js/widget.js - echo $this->Html->script('ContactManager.widget'); - - // Génère une URL de /contact_manager/img/logo.jpg - echo $this->Html->image('ContactManager.logo'); - -Les ressources de plugins sont servies par défaut en utilisant le midlleware -``AssetMiddleware``. Ce n'est recommandé que pour le développement. -En production vous devriez :ref:`symlinker vos assets ` pour -améliorer la performance. - -Si vous n'utilisez pas les helpers, vous pouvez préfixer l'URL par /plugin-name/ -pour servir une ressource du plugin . Un lien vers -'/contact_manager/js/some_file.js' renverrait la ressource -**plugins/ContactManager/webroot/js/some_file.js**. - -Components, Helpers et Behaviors -================================ - -Un plugin peut avoir des Components, Helpers et Behaviors tout comme une -application CakePHP classique. Vous pouvez soit créer des plugins qui sont -composés seulement de Components, Helpers ou Behaviors, ce qui peut être une -bonne façon de construire des Components réutilisables qui peuvent être -facilement déplacés dans n'importe quel projet. - -On construit ces components est exactement de la même manière qu'à l'intérieur -d'une application habituelle, sans aucune convention spéciale de nommage. - -Pour faire référence à votre component, que ce soit depuis l'intérieur ou -l'extérieur de votre plugin, vous devez seulement préfixer le nom du component -par le nom du plugin. Par exemple:: - - // Component défini dans le plugin 'ContactManager' - namespace ContactManager\Controller\Component; - - use Cake\Controller\Component; - - class ExampleComponent extends Component - { - } - - // dans vos controllers: - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('ContactManager.Example'); - } - -La même technique s'applique aux Helpers et aux Behaviors. - -.. _plugin-commands: - -Commands -======== - -Les plugins peuvent enregistrer leurs commandes dans le *hook* ``console()``. -Par défaut, tous les shells et commandes du plugin sont découverts -automatiquement et ajoutés à la liste des commandes de l'application. Les -commandes de plugin sont préfixées par le nom du plugin. Par exemple, la -commande ``UserCommand`` fournie par le plugin ``ContactManager`` serait -enregistrée à la fois comme ``contact_manager.user`` et ``user``. Le nom non -préfixé sera retenu par un plugin seulement s'il n'est pas déjà utilisé par -l'application ou un autre plugin. - -Vous pouvez personnaliser les noms de commandes au moment de définir chaque -commande dans votre plugin:: - - public function console($commands) - { - // Créez des commandes imbriquées - $commands->add('bake model', ModelCommand::class); - $commands->add('bake controller', ControllerCommand::class); - - return $commands; - } - -Tester votre Plugin -=================== - -Si vous testez des controllers ou si vous générez des URLs, assurez-vous que -votre plugin connecte les routes ``tests/bootstrap.php``. - -Pour plus d'informations, consultez la page -:doc:`testing plugins `. - -Publier votre Plugin -==================== - -Les plugins CakePHP devraient être publiés dans `le packagist -`__. De cette façon, d'autres personnes pourraient les -utiliser comme dépendances composer. Vous pouvez aussi proposer votre plugin -dans la `liste des outils formidables pour CakePHP -`_. - -Choisissez un nom qui ait du sens pour votre nom de package. Idéalement, il -faudrait le préfixer du nom de la dépendance, au cas présent "cakephp" comme le -nom du framework. Le nom de vendor sera généralement votre nom d'utilisateur -GitHub. **n'utilisez pas** le namespace de CakePHP (cakephp) car il est réservé -aux plugins appartenant à CakePHP. Par convention, on utilise des lettres en -minuscules et des traits d'union comme séparateurs. - -Donc si vous avez créé un plugin "Logging" avec votre compte GitHub "FooBar", -`foo-bar/cakephp-logging` serait un nom judicieux. Et respectivement, le plugin -"Localized" appartenant à CakePHP peut se trouver sous `cakephp/localized`. - --.. index:: vendor/cakephp-plugins.php - -Fichier de Mappage de Plugin -============================ - -Quand vous installez des plugins par Composer, vous noterez la création de -**vendor/cakephp-plugins.php**. Ce fichier de configuration contient un mappage -des noms de plugins et de leurs chemins sur le système de fichiers. Cela rend -possible l'installation des plugins dans le répertoire standard vendor, qui est -en-dehors de l'arborescence de recherche normale. La classe ``Plugin`` utilisera -ce fichier pour localiser les plugins lorsqu'ils sont chargés avec -``addPlugin()``. Vous n'aurez généralement pas besoin d'éditer ce fichier -manuellement, dans la mesure où Composer et le package ``plugin-installer`` s'en -chargeront pour vous. - -Gérer Vos Plugins avec Mixer -============================ - -`Mixer `_ est un autre moyen de découvrir et -gérer les plugins dans votre application CakePHP. C'est un plugin CakePHP qui -aide à installer des plugins depuis Packagist. Il vous aide aussi à gérer les -plugins existants. - -.. note:: - - IMPORTANT: Ne l'utilisez pas en environnement de production. - - -.. meta:: - :title lang=fr: Plugins - :keywords lang=fr: dossier plugin,configuration de la base de données,bootstrap,module de gestion,peu d'espace,connexion base de données,webroot,gestion d'utilisateur,contactmanager,tableau,config,cakephp,models,php,répertoires,blog,plugins,applications diff --git a/fr/quickstart.rst b/fr/quickstart.rst deleted file mode 100644 index 101568ed61..0000000000 --- a/fr/quickstart.rst +++ /dev/null @@ -1,13 +0,0 @@ -Guide de Démarrage Rapide -************************* - -Le meilleur moyen de tester et d'apprendre CakePHP est de s'assoir et de -construire une application simple de gestion de Contenu (CMS). - -.. include:: /tutorials-and-examples/cms/installation.rst -.. include:: /tutorials-and-examples/cms/database.rst -.. include:: /tutorials-and-examples/cms/articles-controller.rst - -.. meta:: - :title lang=fr: Pour Commencer - :keywords lang=fr: structure de dossier,noms de table,requête initiale,table base de données,structure organisationnel,rst,noms de fichier,conventions,mvc,page web,sit diff --git a/fr/release-policy.rst b/fr/release-policy.rst deleted file mode 100644 index ed90030ade..0000000000 --- a/fr/release-policy.rst +++ /dev/null @@ -1,83 +0,0 @@ -Mises à jour -############ - -Toutes les versions de CakePHP suivent la convention de numérotation -**major.minor.patch**. - -L'équipe de développement s'assure autant que possible que chaque version suive -les restrictions et garanties suivantes. - -Mises à jour principales ------------------------- - -Les mises à jour principales ne sont généralement pas rétro-compatibles. Bien -que CakePHP essaye de ne pas changer trop de grandes fonctionnalités dans les -mises à jour principales, il y a des changements dans l'API. - -Les changements dans une mise à jour principale peuvent porter sur pratiquement -n'importe quel élément mais sont toujours utilisées pour supprimer des -fonctionnalités dépréciées et mettre à jour des interfaces. - -Les changements non rétro-compatibles se font toujours dans une mise à jour -principale. - -Habituellement, chaque mise à jour principale est accompagnée d'un guide de -migration et de plusieurs mises à niveau du code avec l'outil rector. - -Mise à jour mineures --------------------- - -Les versions mineures sont généralement rétro-compatibles avec la précédente -version mineure et ses patchs. - -Certaines fonctionnalités peuvent être dépréciées, mais elles ne sont jamais -supprimées dans une version mineure. - -Les interfaces ne sont pas modifiées, mais des annotations peuvent être ajoutées -pour de nouvelles méthodes présentes dans les implémentations fournies par -CakePHP. - -Les nouvelles fonctionnalités sont habituellement ajoutées uniquement dans les -versions mineures, de façon à ce que les utilisateurs puissent suivre les notes -de migration. Les nouvelles fonctionnalités peuvent aussi inclure la levée de -nouvelles exceptions lorsque le comportement est modifié ou que des bugs sont -signalés. - -Les modifications de comportement qui nécessitent une documentation sont faites -dans des versions mineures, mais elles restent en principe rétro-compatibles. Il -peut être dérogé à cette règle en cas de problème grave. - -.. note: - Les versions mineures sont aussi appelées "versions point". - -Patchs de mise à jour ---------------------- - -Les patchs de mise à jour sont toujours rétro-compatibles. Les modifications -ne portent que sur la correction de dysfonctionnements. - -Habituellement, les utilisateurs peuvent compter sur le fait que les patchs de -mise à jour ne modifient pas le comportement, si ce n'est pour corriger un bug. - -Les corrections qui modifient le comportement à long terme de l'application ne -sont en principe pas diffusées dans des patchs. Elles sont considérées comme des -modifications du comportement et entreront soit dans une mise à jour mineure, -soit dans une mise à jour majeure, de façon à ce que les utilisateurs puissent -mettre en œuvre la migration. - -.. note: - Les patchs de mise à jour sont aussi appelés "versions correctives". - -Fonctionnalités expérimentales ------------------------------- - -Lorsqu'une nouvelle fonctionnalité est ajoutée et que son API n'est pas -définitive, elle peut être étiquetée **expérimental**. - -Les fonctionnalités expérimentales suivent en principe les conventions ci-dessus -dans les versions mineures et les patchs. Cependant, des changements d'API -peuvent être introduits dans des versions mineures et modifier significativement -le comportement. - -Les utilisateurs devraient toujours s'attendre à ce que l'API puisse changer -tant que les fonctionnalités expérimentales ne sont pas totalement validées. diff --git a/fr/security.rst b/fr/security.rst deleted file mode 100644 index 3eb6507fcb..0000000000 --- a/fr/security.rst +++ /dev/null @@ -1,19 +0,0 @@ -Sécurité -######## - -CakePHP fournit quelques outils pour sécuriser votre application. -Les sections suivantes traitent de ces outils: - -.. toctree:: - :maxdepth: 1 - - core-libraries/security - Middleware de Protection des Formulaires - Protection CSRF - Content Security Policy - Headers de Sécurité - HTTPS Enforcer - -.. meta:: - :title lang=fr: Sécurité - :keywords lang=fr: sécurité, csrf, cross site request forgery component diff --git a/fr/security/content-security-policy.rst b/fr/security/content-security-policy.rst deleted file mode 100644 index e25cc206ee..0000000000 --- a/fr/security/content-security-policy.rst +++ /dev/null @@ -1,55 +0,0 @@ -Middleware Content Security Policy -================================== - -Le ``CspMiddleware`` rend les choses plus simples pour ajouter des en-têtes -Content-Security-Policy dans votre application. Avant de l'utiliser, vous devez -installer ``paragonie/csp-builder``: - -.. code-block:: bash - - composer require paragonie/csp-builder - -Vous pouvez configurer le middleware en utilisant un tableau, ou en lui passant -un objet ``CSPBuilder`` déjà construit:: - - use Cake\Http\Middleware\CspMiddleware; - - $csp = new CspMiddleware([ - 'script-src' => [ - 'allow' => [ - 'https://www.google-analytics.com', - ], - 'self' => true, - 'unsafe-inline' => false, - 'unsafe-eval' => false, - ], - ]); - - $middlewareQueue->add($csp); - -Si vous voulez utiliser une configuration CSP plus stricte, vous pouvez activer -des règles CSP basées sur le nonce avec les options ``scriptNonce`` et -``styleNonce``. Lorsqu'elles sont activées, ces options vont modifier votre -politique CSP et définir les attributs ``cspScriptNonce`` et ``cspStyleNonce`` -dans la requête. Ces attributs sont appliqués -à l'attribut ``nonce`` de tous les éléments scripts et liens CSS créés par -``HtmlHelper``. Cela simplifie l'adoption de stratégies utilisant un `nonce-base64 -`__ -et ``strict-dynamic`` pour un surcroît de sécurité et une maintenance plus -facile:: - - $policy = [ - // Doivent exister, même vides, pour définir le nonce pour script-src - 'script-src' => [], - 'style-src' => [], - ]; - // Active l'ajout automatique du nonce aux tags script & liens CSS. - $csp = new CspMiddleware($policy, [ - 'scriptNonce' => true, - 'styleNonce' => true, - ]); - $middlewareQueue->add($csp); - -.. meta:: - :title lang=fr: Middleware Content Security Policy - :keywords lang=fr: security, content security policy, csp, middleware, cross-site scripting diff --git a/fr/security/csrf.rst b/fr/security/csrf.rst deleted file mode 100644 index bc3ff26802..0000000000 --- a/fr/security/csrf.rst +++ /dev/null @@ -1,200 +0,0 @@ -Protection CSRF -############### - -Les Cross-Site Request Forgeries (CSRF) sont un type de vulnérabilité dans -lequel des commandes non autorisées sont exécutées au nom d'un utilisateur -authentifié à son insu ou sans son consentement. - -CakePHP offre deux formes de protection CSRF: - -* ``SessionCsrfProtectionMiddleware`` stocke les jetons CSRF en session. Cela - nécessite que votre application ouvre la session à chaque requête, avec des - effets collatéraux. L'avantage des jetons CSRF basés sur la session est qu'ils - sont propres à un utilisateur, et valides seulement pendant la durée de la - session. -* ``CsrfProtectionMiddleware`` stocke les jetons CSRF dans un cookie. - L'utilisation d'un cookie permet de faire les vérifications CSRF - indépendamment de l'état du serveur. Les valeurs des cookies sont vérifiées - par un test HMAC. Toutefois, de par leur nature *stateless*, les jetons CSRF - sont réutilisables d'un utilisateur à l'autre et d'une session à l'autre. - -.. note:: - - Vous ne pouvez pas utiliser ces deux approches simultanément, vous devez en - choisir une. Si vous utilisez les deux ensemble, une erreur de jeton CSRF - invalide se produira à chaque requête `PUT` et `POST`. - -.. _csrf-middleware: - -Middleware Cross Site Request Forgery (CSRF) -============================================ - -La protection CSRF peut être appliqué à votre application complète ou à des -'scopes' spécifiques. En ajoutant le middleware CSRF à la file des middlewares -de votre Application, vous protégez toutes les actions de l'application:: - - // dans src/Application.php - // Pour les jetons CSRF basés sur un Cookie. - use Cake\Http\Middleware\CsrfProtectionMiddleware; - - // Pour les jetons CSRF basés sur la session. - use Cake\Http\Middleware\SessionCsrfProtectionMiddleware; - - public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue - { - $options = [ - // ... - ]; - $csrf = new CsrfProtectionMiddleware($options); - // ou - $csrf = new SessionCsrfProtectionMiddleware($options); - - $middlewareQueue->add($csrf); - - return $middlewareQueue; - } - -En ajoutant la protection CSRF à des scopes de routing, vous pouvez conditionner -l'utilisation de CSRF à certains groupes de routes:: - - // dans src/Application.php - use Cake\Http\Middleware\CsrfProtectionMiddleware; - use Cake\Routing\RouteBuilder; - - public function routes(RouteBuilder $routes) : void - { - $options = [ - // ... - ]; - $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware($options)); - parent::routes($routes); - } - - // dans config/routes.php - $routes->scope('/', function (RouteBuilder $routes) { - $routes->applyMiddleware('csrf'); - }); - - -Options du middleware CSRF basés sur un Cookie ----------------------------------------------- - -Les options de configuration disponibles sont: - -- ``cookieName`` Le nom du cookie à envoyer. Par défaut ``csrfToken``. -- ``expiry`` La durée de vie du jeton CSRF. Par défaut, le temps de la session. -- ``secure`` Selon que le cookie doit être défini avec le drapeau Secure ou pas. - C'est-à-dire que le cookie sera défini seulement dans une connexion HTTPS et - toute tentative à travers un HTTP normal échouera. Par défaut à ``false``. -- ``httponly`` Selon que le cookie sera défini avec le drapeau HttpOnly ou pas. - Par défaut à ``false``. Avant 4.1.0, utilisez l'option ``httpOnly``. -- ``samesite`` Vous permet de déclarer si le cookie doit être restreint à un - contexte first-party ou same-site. Les valeurs possibles sont ``Lax``, - ``Strict`` et ``None``. Par défaut à ``null``. -- ``field`` Le champ de formulaire à vérifier. Par défaut ``_csrfToken``. - Changer ceci obligera à changer également la configuration de FormHelper. - -Options du middleware CSRF basé sur la Session ----------------------------------------------- - -Les options de configuration disponibles sont: - -- ``key`` La clé de session à utiliser. Par défaut `csrfToken`. -- ``field`` Le champ de formulaire à vérifier. Par défaut ``_csrfToken``. - Changer ceci obligera à changer également la configuration de FormHelper. - - -Lorsqu'il est activé, vous pouvez accéder au jeton CSRF en cours sur l'objet -requête:: - - $token = $this->request->getAttribute('csrfToken'); - -Ignorer les vérifications CSRF pour certaines actions ------------------------------------------------------ - -Les deux implémentations du middleware CSRF vous autorisent à ignorer les -callbacks de vérification pour un contrôle plus fin selon l'URL pour laquelle la -vérification était censée avoir lieu:: - - // dans src/Application.php - use Cake\Http\Middleware\CsrfProtectionMiddleware; - - public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue - { - $csrf = new CsrfProtectionMiddleware(); - - // La vérification du jeton sera ignorée lorsque le callback renvoie `true`. - $csrf->skipCheckCallback(function ($request) { - // Ignore la vérification du jeton pour les URLs API. - if ($request->getParam('prefix') === 'Api') { - return true; - } - }); - - // S'assure que le middleware de routing est ajouté à la file avant le middleware de protection CSRF. - $middlewareQueue->add($csrf); - - return $middlewareQueue; - } - -.. note:: - - Vous devez appliquer le middleware de protection CSRF seulement pour les - routes qui gèrent des requêtes stateful en utilisant des cookies/sessions. - Par exemple, en développant une API, les requêtes stateless ne sont pas - affectées par CSRF, donc le middleware n'a pas besoin d'être appliqué à ces - routes. - -Intégration avec le FormHelper ------------------------------- - -Le ``CsrfProtectionMiddleware`` s'intègre parfaitement avec le ``FormHelper``. -Chaque fois que vous créez un formulaire avec le ``FormHelper``, cela créera un -champ caché contenant le token CSRF. - -.. note:: - - Lorsque vous utilisez la protection CSRF, vous devriez toujours commencer - vos formulaires avec le ``FormHelper``. Si vous ne le faites pas, vous allez - devoir créer manuellement les champs cachés dans chaque formulaire. - -Protection CSRF et Requêtes AJAX --------------------------------- - -En plus des données de la requête, les tokens CSRF peuvent être soumis *via* le -header spécial ``X-CSRF-Token``. Utiliser un header facilite généralement -l'intégration du token CSRF dans les applications qui utilisent Javascript de -manière intensive ou avec les applications API JSON / XML. - -Le token CSRF peut être récupéré via le Cookie ``csrfToken``, ou en PHP *via* -l'attribut nommé ``csrfToken`` dans l'objet requête. Il est peut-être plus -facile d'utiliser le cookie si votre code Javascript se trouve dans des fichiers -séparés des templates de vue de CakePHP, ou si vous avez déjà une fonctionnalité -qui vous permet de parser des cookies avec Javascript. - -Si vous avez des fichiers Javascript séparés mais que vous ne voulez pas avoir à -gérer des cookies, vous pouvez par exemple définir un token dans une variable -Javascript globale dans votre layout, en définissant un bloc script comme ceci:: - - echo $this->Html->scriptBlock(sprintf( - 'var csrfToken = %s;', - json_encode($this->request->getAttribute('csrfToken')) - )); - -Vous pouvez accéder au token par l'expression ``csrfToken`` ou -``window.csrfToken`` dans n'importe quel fichier de script qui sera chargé après -ce bloc de script. - -Une autre alternative serait de placer le token dans une balise meta -personnalisée comme ceci:: - - echo $this->Html->meta('csrfToken', $this->request->getAttribute('csrfToken')); - -ce qui le rendrait accessible dans vos scripts en recherchant l'élément ``meta`` -nommé ``csrfToken``. Avec jQuery, cela pourrait être aussi simple que ça:: - - var csrfToken = $('meta[name="csrfToken"]').attr('content'); - -.. meta:: - :title lang=fr: Protection CSRF - :keywords lang=fr: security, csrf, cross site request forgery, middleware, session diff --git a/fr/security/https-enforcer.rst b/fr/security/https-enforcer.rst deleted file mode 100644 index 8ba3868682..0000000000 --- a/fr/security/https-enforcer.rst +++ /dev/null @@ -1,56 +0,0 @@ -.. _https-enforcer-middleware: - -Middleware HTTPS Enforcer -========================= - -Si vous voulez que votre application soit accessible uniquement par des -connexions HTTPS, vous pouvez utiliser le ``HttpsEnforcerMiddleware``:: - - use Cake\Http\Middleware\HttpsEnforcerMiddleware; - - // Toujours soulever une exception et ne jamais rediriger. - $https = new HttpsEnforcerMiddleware([ - 'redirect' => false, - ]); - - // Envoyer un code de statut 302 en cas de redirection - $https = new HttpsEnforcerMiddleware([ - 'redirect' => true, - 'statusCode' => 302, - ]); - - // Envoyer des headers supplémentaires dans la réponse de redirection. - $https = new HttpsEnforcerMiddleware([ - 'headers' => ['X-Https-Upgrade' => 1], - ]); - - // Désactiver le HTTPs forcé quand ``debug`` est activé. - $https = new HttpsEnforcerMiddleware([ - 'disableOnDebug' => true, - ]); - -À la réception d'une requête non-HTTP qui n'utilise pas GET, un -``BadRequestException`` sera soulevée. - -Ajouter Strict-Transport-Security -================================= - -Si votre application nécessite du SSL, une bonne idée serait de définir le -header ``Strict-Transport-Security``. La valeur de ce header est mise en cache -par le navigateur, et informe les navigateurs qu'ils devraient toujours se -connecter en HTTPS. Vous pouvez configurer ce header avec l'option ``hsts``:: - - $https = new HttpsEnforcerMiddleware([ - 'hsts' => [ - // La durée pendant laquelle la valeur du header devrait être mise en cache - 'maxAge' => 60 * 60 * 24 * 365, - // cette politique s'applique-t-elle aux sous-domaines ? - 'includeSubdomains' => true, - // La valeur de ce header devrait-elle être mise en cache dans le service - // HSTS preload de Google ? Bien que ne faisant pas partie de la spécification, il est souvent implémenté. - 'preload' => true, - ], - ]); - - .. meta:: - :title lang=fr: Middleware HTTPS Enforcer diff --git a/fr/security/security-headers.rst b/fr/security/security-headers.rst deleted file mode 100644 index edd450ce18..0000000000 --- a/fr/security/security-headers.rst +++ /dev/null @@ -1,32 +0,0 @@ -Middleware des Headers de Sécurité -================================== - -La couche ``SecurityHeaderMiddleware`` vous permet d'ajouter à votre application -des headers liés à la sécurité. Une fois configuré, le middleware peut ajouter -les headers suivants aux réponses: - -* ``X-Content-Type-Options`` -* ``X-Download-Options`` -* ``X-Frame-Options`` -* ``X-Permitted-Cross-Domain-Policies`` -* ``Referrer-Policy`` - -Ce middleware peut être configuré en utilisant l'interface fluide avant d'être -appliqué au stack de middlewares:: - - use Cake\Http\Middleware\SecurityHeadersMiddleware; - - $securityHeaders = new SecurityHeadersMiddleware(); - $securityHeaders - ->setCrossDomainPolicy() - ->setReferrerPolicy() - ->setXFrameOptions() - ->setXssProtection() - ->noOpen() - ->noSniff(); - - $middlewareQueue->add($securityHeaders); - -.. meta:: - :title lang=fr: Middleware des Headers de Sécurité - :keywords lang=fr: x-frame-options, cross-domain, referrer-policy, download-options, middleware, content-type-options diff --git a/fr/standalone-packages.rst b/fr/standalone-packages.rst deleted file mode 100644 index 6853cce0a9..0000000000 --- a/fr/standalone-packages.rst +++ /dev/null @@ -1,82 +0,0 @@ -Paquets autonomes -################# - -Le cœur de CakePHP est divisé en plusieurs paquets autonomes qui peuvent être -utilisés indépendamment les uns des autres. - -`ORM `_ ---------------------------------------- - -Un mapping objet-relationnel pour PHP, flexible, léger et puissant, implémenté -en utilisant le pattern DataMapper. - -`Database `_ -------------------------------------------------- - -Une bibliothèque d'abstraction de base de données flexible et puissante avec une -API proche du bien connu PDO. - -`Datasource `_ ------------------------------------------------------ - -Fournit la gestion de la connexion et les traits pour les Entities et les -Queries, peut être réutilisée avec différents datastores. - -`HTTP `_ ------------------------------------------ - -Client HTTP conforme à PSR-18 et PSR-15 et bibliothèques de serveur. - -`Console `_ ------------------------------------------------ - -Une bibliothèque pour construire des applications de ligne de commande à partir -d'un ensemble de commandes. - -`Collection `_ ------------------------------------------------------ - -Une bibliothèque qui fournit un ensemble d'outils pour manipuler des tableaux et -des objets parcourables. - -`I18n `_ ------------------------------------------ - -Fournit un support pour la traduction des messages et la localisation des dates -et nombres. - -`Cache `_ -------------------------------------------- - -Bibliothèque de cache conforme à PSR-16, supportant les -caches multiples en backend. - -`Log `_ ---------------------------------------- - -Bibliothèque de logs conforme à PSR-3 supportant plusieurs flux différents. - -`Event `_ -------------------------------------------- - -La bibliothèque de distribution des événements. - -`Utility `_ ------------------------------------------------ - -Des classes utilitaires telles que Inflector, Text, Hash, Security et Xml. - -`Validation `_ ------------------------------------------------------ - -Bibliothèque de validation dans CakePHP. - -`Form `_ ------------------------------------------ - -Abstraction de formulaire utilisée pour créer des formulaires non liés aux -modèles ORM, ni à d'autres datastores permanents. - -.. meta:: - :title lang=fr: Paquets autonomes - :keywords lang=fr: packages, cakephp, orm, database, http client, http server, utility, events, log, cache diff --git a/fr/topics.rst b/fr/topics.rst deleted file mode 100644 index 35e2a1a3cc..0000000000 --- a/fr/topics.rst +++ /dev/null @@ -1,37 +0,0 @@ -Utiliser CakePHP -################ - -Introduction à toutes les parties clés de CakePHP: - -* :doc:`/installation` -* :doc:`/development/configuration` -* :doc:`/development/routing` -* :doc:`/controllers/request-response` -* :doc:`/controllers` -* :doc:`/controllers/components` -* :doc:`/controllers/pages-controller` -* :doc:`/views` -* :doc:`/views/cells` -* :doc:`/views/helpers` -* :doc:`/views/json-and-xml-views` -* :doc:`/orm` -* :doc:`/orm/table-objects` -* :doc:`/orm/query-builder` -* :doc:`/orm/entities` -* :doc:`/development/errors` -* :doc:`/core-libraries/caching` -* :doc:`/core-libraries/logging` -* :doc:`/core-libraries/form` -* :doc:`/development/sessions` -* :doc:`/development/rest` -* :doc:`/controllers/components/authentication` -* :doc:`/controllers/components/pagination` -* :doc:`/core-libraries/email` -* :doc:`/views/helpers/form` -* :doc:`/views/helpers/html` -* :doc:`/core-libraries/validation` -* :doc:`/development/testing` -* :doc:`/deployment` -* :doc:`/console-commands` -* :doc:`/contributing` -* :doc:`/tutorials-and-examples` diff --git a/fr/tutorials-and-examples.rst b/fr/tutorials-and-examples.rst deleted file mode 100644 index f272767b59..0000000000 --- a/fr/tutorials-and-examples.rst +++ /dev/null @@ -1,34 +0,0 @@ -Tutoriels et exemples -##################### - -Dans cette section, vous pourrez découvrir des applications CakePHP -typiques afin de voir comment toutes les pièces s'assemblent. - -Sinon, vous pouvez vous référer au dépôt de plugins non-officiels de CakePHP -`CakePackages `_ ainsi que la -`Boulangerie `_ (Bakery) pour des applications -et components existants. - -.. toctree:: - :maxdepth: 1 - - tutorials-and-examples/cms/installation - tutorials-and-examples/cms/database - tutorials-and-examples/cms/articles-controller - tutorials-and-examples/cms/tags-and-users - tutorials-and-examples/cms/authentication - tutorials-and-examples/cms/authorization - -.. toctree:: - :hidden: - - tutorials-and-examples/bookmarks/intro - tutorials-and-examples/bookmarks/part-two - tutorials-and-examples/blog/blog - tutorials-and-examples/blog/part-two - tutorials-and-examples/blog/part-three - tutorials-and-examples/blog-auth-example/auth - -.. meta:: - :title lang=fr: Tutoriels & Exemples - :keywords lang=fr: tutoriels application,glob,bakery,boulangerie,repository,applications,blog,acl diff --git a/fr/tutorials-and-examples/blog-auth-example/auth.rst b/fr/tutorials-and-examples/blog-auth-example/auth.rst deleted file mode 100755 index f31ca57ca4..0000000000 --- a/fr/tutorials-and-examples/blog-auth-example/auth.rst +++ /dev/null @@ -1,402 +0,0 @@ -Tutoriel d'un Blog - Authentification -##################################### - -Poursuivant notre exemple :doc:`/tutorials-and-examples/blog/blog`, imaginons -que nous souhaitions interdire aux utilisateurs non connectés de créer des -articles. - -Créer la Table et le Controller pour Users -========================================== - -Premièrement, créons une nouvelle table dans notre base de données blog pour -enregistrer les données de nos utilisateurs: - -.. code-block:: mysql - - CREATE TABLE users ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - email VARCHAR(255), - password VARCHAR(255), - role VARCHAR(20), - created DATETIME DEFAULT NULL, - modified DATETIME DEFAULT NULL - ); - -Si vous utilisez PostgreSQL, connectez-vous à la base de données cake_blog et -exécutez plutôt la commande SQL suivante: - -.. code-block:: SQL - - CREATE TABLE users ( - id SERIAL PRIMARY KEY, - email VARCHAR(255), - password VARCHAR(255), - role VARCHAR(20), - created TIMESTAMP DEFAULT NULL, - modified TIMESTAMP DEFAULT NULL - ); - -Nous avons respecté les conventions de CakePHP pour le nommage des tables, mais -nous profitons d'une autre convention: en utilisant les colonnes ``email`` et -``password`` dans une table ``users``, CakePHP sera capable de -configurer automatiquement la plupart des choses pour nous quand nous -réaliserons la connexion de l'utilisateur. - -La prochaine étape est de créer notre classe ``UsersTable``, qui a la -responsabilité de trouver, sauvegarder et valider toute donnée d'utilisateur:: - - // src/Model/Table/UsersTable.php - namespace App\Model\Table; - - use Cake\ORM\Table; - use Cake\Validation\Validator; - - class UsersTable extends Table - { - - public function validationDefault(Validator $validator): Validator - { - return $validator - ->notEmpty('email', "Un email est nécessaire") - ->email('email') - ->notEmpty('password', 'Un mot de passe est nécessaire') - ->notEmpty('role', 'Un rôle est nécessaire') - ->add('role', 'inList', [ - 'rule' => ['inList', ['admin', 'author']], - 'message' => 'Merci d\'entrer un rôle valide' - ]); - } - - } - -Créons aussi notre UsersController. Le contenu suivant correspond à la -classe obtenue grâce à l'utilitaire de génération de code fourni par CakePHP:: - - // src/Controller/UsersController.php - - namespace App\Controller; - - use App\Controller\AppController; - use Cake\Event\EventInterface; - - class UsersController extends AppController - { - - public function index() - { - $this->set('users', $this->Users->find()->all()); - } - - public function view($id) - { - $user = $this->Users->get($id); - $this->set(compact('user')); - } - - public function add() - { - $user = $this->Users->newEmptyEntity(); - if ($this->request->is('post')) { - $user = $this->Users->patchEntity($user, $this->request->getData()); - if ($this->Users->save($user)) { - $this->Flash->success(__("L'utilisateur a été sauvegardé.")); - - return $this->redirect(['action' => 'add']); - } - $this->Flash->error(__("Impossible d'ajouter l\'utilisateur.")); - } - $this->set('user', $user); - } - - } - -De la même façon que nous avons créé les vues pour nos articles en utilisant -l'outil de génération de code, nous pouvons implémenter les vues des -utilisateurs. Dans le cadre de ce tutoriel, nous allons juste montrer le -**add.php**: - -.. code-block:: php - - - -
      - Form->create($user) ?> -
      - - Form->control('email') ?> - Form->control('password') ?> - Form->control('role', [ - 'options' => ['admin' => 'Admin', 'author' => 'Author'] - ]) ?> -
      - Form->button(__('Ajouter')); ?> - Form->end() ?> -
      - -Authentification (Connexion et Déconnexion) -=========================================== - -Nous sommes maintenant prêts à ajouter notre couche d'authentification. Dans -CakePHP, cette couche est gérée par le plugin ``authentication``. Commençons par -l'installer. Utilisez composer pour l'installation du plugin: - -.. code-block:: console - - composer require "cakephp/authentication:^2.0" - -Puis ajoutez le code suivant à la méthode ``bootstrap()`` de votre application:: - - // dans la méthode bootstrap() de src/Application.php - $this->addPlugin('Authentication'); - -Hachage des Mots de Passe -========================= - -Ensuite, nous allons créer l'entité ``User`` et ajouter un hachage de mots de -passe. Créez le fichier d'entité **src/Model/Entity/User.php** et ajoutez ce qui -suit:: - - // src/Model/Entity/User.php - namespace App\Model\Entity; - - use Cake\Auth\DefaultPasswordHasher; - use Cake\ORM\Entity; - - class User extends Entity - { - - // Rend les champs assignables en masse sauf pour la clé primaire "id". - protected array $_accessible = [ - '*' => true, - 'id' => false - ]; - - // ... - - protected function _setPassword($password) - { - if (strlen($password) > 0) { - return (new DefaultPasswordHasher)->hash($password); - } - } - - // ... - } - -Maintenant, à chaque fois qu'un mot de passe est assigné à l'entité utilisateur, -il est haché en utilisant la classe ``DefaultPasswordHasher``. - -Configurer l'Authentification -============================= - -Il est maintenant temps de configurer le Plugin Authentication. -Le Plugin va gérer le processus d'identification en utilisant 3 classes -différentes: - -* ``Application`` utilisera le Middleware Authentication et fournira un - AuthenticationService. Il comportera toute la configuration que nous voulons - pour définir comment nous allons vérifier les identifiants fournis, et où nous - allons trouver les informations avec lesquelles les comparer. -* ``AuthenticationService`` sera une classe utilitaire pour vous permettre de - configurer le processus d'authentification. -* ``AuthenticationMiddleware`` sera exécuté comme une étape de la middleware - queue. Il s'exécute avant que vos contrôleurs soient appelés par le framework, - et va chercher les identifiants ou preuves de connexion pour vérifier si - l'utilisateur est connecté. - -La logique d'authentification est divisée en classes spécifiques et le processus -d'authentification se met en route avant la couche de vos contrôleurs. En tout -premier, l'authentification cherche à authentifier l'utilisateur (selon la -configuration que vous aurez définie) puis injecte l'utilisateur et les -résultats d'authentification dans la requête, pour qu'ils soient consultables -par la suite. - -Dans **src/Application.php**, ajoutez les imports suivants:: - - // Dans src/Application.php ajoutez les imports suivants - use Authentication\AuthenticationService; - use Authentication\AuthenticationServiceInterface; - use Authentication\AuthenticationServiceProviderInterface; - use Authentication\Middleware\AuthenticationMiddleware; - use Psr\Http\Message\ServerRequestInterface; - -Puis implémentez l'interface d'authentification dans votre classe Application:: - - // dans src/Application.php - class Application extends BaseApplication - implements AuthenticationServiceProviderInterface - { - -Et ajoutez ce qui suit:: - - // src/Application.php - public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue - $middlewareQueue - // ... autres middlewares ajoutés auparavant - ->add(new RoutingMiddleware($this)) - // ajoutez Authentication après RoutingMiddleware - ->add(new AuthenticationMiddleware($this)); - - return $middlewareQueue; - - public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface - { - $authenticationService = new AuthenticationService([ - 'unauthenticatedRedirect' => '/users/login', - 'queryParam' => 'redirect', - ]); - - // Charger les identificateurs. S'assurer que nous vérifions les champs email et password - $authenticationService->loadIdentifier('Authentication.Password', [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password', - ] - ]); - - // Charger les authentificateurs. En général vous voudrez mettre Session en premier. - $authenticationService->loadAuthenticator('Authentication.Session'); - // Configurer la connexion par formulaire pour qu'elle aille chercher - // les champs email et password. - $authenticationService->loadAuthenticator('Authentication.Form', [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password', - ], - 'loginUrl' => '/users/login', - ]); - - return $authenticationService; - } - -Dans votre classe ``AppController``, ajoutez ce code:: - - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('RequestHandler'); - $this->loadComponent('Flash'); - - // AJoutez cette ligne pour vérifier le résultat de l'authentification - // et donc verrouiller l'accès à votre site. - $this->loadComponent('Authentication.Authentication'); - -Maintenant, à chaque requête, l'\ ``AuthenticationMiddleware`` va examiner la -session de la requête pour y rechercher un utilisateur authentifié. Si nous -sommes en train de charger la page ``/users/login``, il va aussi inspecter les -données envoyées par formulaire (s'il y en a) pour en extraire les identifiants -utilisateur. Par défaut, les identifiants seront extraits des champs ``email`` -et ``password`` dans les données de la requête. Le résultat de -l'authentification sera injecté dans un attribut de la requête nommé -``authentication``. Vous pouvez consulter le résultat à n'importe quel moment en -utilisant ``$this->request->getAttribute('authentication')`` depuis les actions -de vos contrôleurs. Toutes vos pages auront un accès restreint puisque -l'\ ``AuthenticationComponent`` vérifie le résultat à chaque requête. Lorsqu'il -échouera à trouver un utilisateur authentifié, il redirigera l'utilisateur vers -la page ``/users/login``. Veuillez noter qu'à ce stade, le site ne fonctionnera -pas puisque nous n'avons pas encore de page de connexion. Si vous visitez le -site, vous obtiendrez une "boucle infinie de redirections". Alors, corrigeons -ça ! - -Dans votre ``UsersController``, ajoutez ce code:: - - public function beforeFilter(\Cake\Event\EventInterface $event) - { - parent::beforeFilter($event); - // Configurer l'action login pour ne pas exiger d'authentification, et - // ainsi empêcher un problème de boucle infinie de redirections - $this->Authentication->addUnauthenticatedActions(['login']); - } - - public function login() - { - $this->request->allowMethod(['get', 'post']); - $result = $this->Authentication->getResult(); - // Qu'on soit en POST ou en GET, rediriger l'utilisateur s'il est déjà connecté - if ($result->isValid()) { - // rediriger vers /articles après une connexion réussie - $redirect = $this->request->getQuery('redirect', [ - 'controller' => 'Articles', - 'action' => 'index', - ]); - - return $this->redirect($redirect); - } - // afficher une erreur si l'utilisateur a validé le formulaire mais que - // l'authentification a échoué - if ($this->request->is('post') && !$result->isValid()) { - $this->Flash->error(__('Invalid email or password')); - } - } - -Ajoutez la logique du template pour votre action login:: - - -
      - Flash->render() ?> -

      Login

      - Form->create() ?> -
      - - Form->control('email', ['required' => true]) ?> - Form->control('password', ['required' => true]) ?> -
      - Form->submit(__('Se Connecter')); ?> - Form->end() ?> - - Html->link("Ajouter un utilisateur", ['action' => 'add']) ?> -
      - -À présent, la page de connexion va nous permettre de nous connecter correctement -dans notre application. -Testez-le en essayant d'accéder à une page quelconque de votre site. Après avoir -été redirigé vers la page ``/users/login``, entrez l'e-mail et le mot de passe -que vous aviez choisis précédemment quand vous avez créé l'utilisateur. Vous -devriez être connecté sans problème et redirigé vers la bonne page. - -Nous avons encore besoin de quelques détails pour configurer notre application. -Nous voulons que toutes les pages ``view`` et ``index`` soient accessibles sans -avoir à se connecter, donc nous allons ajouter cette configuration spécifique -dans ``AppController``:: - - // dans src/Controller/AppController.php - public function beforeFilter(\Cake\Event\EventInterface $event) - { - parent::beforeFilter($event); - // pour tous les contrôleurs de notre application, rendre les actions - // index et viex publiques en sautant l'étape d'authentification. - $this->Authentication->addUnauthenticatedActions(['index', 'view']); - -Déconnexion -=========== - -Ajoutez l'action logout à votre classe ``UsersController``:: - - // dans src/Controller/UsersController.php - public function logout() - { - $result = $this->Authentication->getResult(); - // Qu'on soit en POST ou en GET, rediriger l'utilisateur s'il est déjà connecté - if ($result->isValid()) { - $this->Authentication->logout(); - - return $this->redirect(['controller' => 'Users', 'action' => 'login']); - } - } - -À présent vous pouvez visiter l'URL ``/users/logout`` pour vous déconnecter. -Vous devriez alors être renvoyé vers la page de connexion. Si vous êtes arrivés -à ce point, félicitations, vous avez maintenant un blog simple qui: - -* Autorise les utilisateurs connectés à créer et éditer des articles. -* Autorise les utilisateurs non connectés à consulter des articles et des tags. - -Lectures suivantes suggérées ----------------------------- - -#. :doc:`/bake/usage` Génération basique CRUD de code -#. Documentation de `Authentication Plugin `__. - -.. meta:: - :title lang=fr: Authentification Simple - :keywords lang=fr: incrémentation auto,autorisation application,modèle user,tableau,conventions,authentification,urls,cakephp,suppression,doc,colonnes diff --git a/fr/tutorials-and-examples/blog/blog.rst b/fr/tutorials-and-examples/blog/blog.rst deleted file mode 100644 index 58906f6a0c..0000000000 --- a/fr/tutorials-and-examples/blog/blog.rst +++ /dev/null @@ -1,247 +0,0 @@ -Tutoriel d'un Blog -################## - -Ce tutoriel vous accompagnera à travers la création d'une simple application -de blog. Nous récupérerons et installerons CakePHP, créerons et configurerons -une base de données et ajouterons suffisamment de logique applicative pour -lister, ajouter, éditer et supprimer des articles. - -Voici ce dont vous aurez besoin: - -#. Un serveur web fonctionnel. Nous supposerons que vous utilisez Apache, - bien que les instructions pour utiliser d'autres serveurs doivent - être assez semblables. Nous aurons peut-être besoin de jouer un peu sur la - configuration du serveur, mais la plupart des personnes peuvent faire - fonctionner CakePHP sans aucune configuration préalable. Assurez-vous - d'avoir PHP |minphpversion| ou supérieur et que les extensions ``mbstring`` et - ``intl`` sont activées dans PHP. -#. Un serveur de base de données. Dans ce tutoriel, nous utiliserons MySQL. - Vous aurez besoin d'un minimum de connaissance en SQL afin de créer une - base de données : CakePHP prendra les rênes à partir de là. Puisque nous - utilisons MySQL, assurez-vous aussi que vous avez ``pdo_mysql`` activé - dans PHP. -#. Des connaissances de base en PHP. - -Maintenant, lançons-nous ! - -Obtenir CakePHP -=============== - -Le manière la plus simple pour l'installer est d'utiliser Composer. -Composer est une manière simple d'installer CakePHP à partir de votre -terminal ou de l'invite de ligne de commande. Pour commencer, vous devrez -télécharger et installer Composer si vous ne l'avez pas déjà. Si vous avez cURL, -c'est aussi simple que de lancer la commande suivante:: - - curl -s https://getcomposer.org/installer | php - -Ou vous pouvez télécharger ``composer.phar`` depuis le -`site de Composer `_. - -Ensuite tapez simplement la ligne suivante dans votre terminal depuis votre -répertoire d'installation pour installer le squelette d'application de CakePHP -dans le répertoire où vous souhaitez l'utiliser. Pour l'exemple nous utiliserons -"blog", mais vous pouvez utiliser le nom que vous souhaitez:: - - php composer.phar create-project --prefer-dist cakephp/app:4.* blog - -Dans le cas où vous avez déjà composer installé globalement, vous devrez plutôt -taper:: - - composer self-update && composer create-project --prefer-dist cakephp/app:4.* blog - -L'avantage d'utiliser Composer est qu'il va automatiquement réaliser certaines -tâches de configurations importantes, comme configurer les bonnes permissions -de fichiers et créer votre fichier config/app.php à votre place. - -Il y a d'autres moyens d'installer CakePHP. Si vous ne pouvez pas ou ne voulez pas -utiliser ``Composer``, regardez la section :doc:`/installation`. - -Quelle que soit la façon dont vous avez téléchargé et installé CakePHP, une fois -la configuration terminée, votre répertoire d'installation devrait ressembler à -quelque chose comme cela:: - - /cake_install - /bin - /config - /logs - /plugins - /src - /tests - /tmp - /vendor - /webroot - .editorconfig - .gitignore - .htaccess - .travis.yml - composer.json - index.php - phpunit.xml.dist - README.md - -A présent, il est peut-être temps de voir un peu comment fonctionne la -structure de fichiers de CakePHP : lisez le chapitre -:doc:`/intro/cakephp-folder-structure`. - -Les Permissions des Répertoires tmp et logs -=========================================== - -Les répertoires ``tmp`` and ``logs`` doivent être accessibles en écriture pour -le serveur web. Si vous avez utilisé Composer pour l'installation, ceci a du -être fait pour vous et confirmé par un message -"Permissions set on ". Si vous avez un message d'erreur à la place, -ou si vous voulez le faire manuellement, la meilleure façon est de trouver sous -quel utilisateur votre serveur web tourne en faisant (````) et -en attribuant la propriété du répertoire **src/tmp** à cet utilisateur. La -commande finale que vous pouvez lancer (dans \*nix) pourrait ressembler à ceci:: - - chown -R www-data tmp - chown -R www-data logs - -Si pour une raison ou une autre, CakePHP ne peut écrire dans ce répertoire, vous -en serez informé par un avertissement quand vous n'êtes pas en mode production. - -Bien que non recommandé, si vous ne pouvez pas attribuer la propriété de ces -répertoires à votre serveur web, vous pouvez simplement définir les -permissions sur le dossier en lançant une commande comme celle-ci:: - - chmod -R 777 tmp - chmod -R 777 logs - -Créer la Base de Données du Blog -================================ - -Maintenant, mettons en place la base de données MySQL pour notre blog. Si vous -ne l'avez pas déjà fait, créez une base de données vide avec le nom de votre -choix pour l'utiliser dans ce tutoriel, par exemple ``cake_blog``. Pour le -moment, nous allons juste créer une seule table pour stocker nos articles. - -.. code-block:: mysql - - # D'abord, créons la table des articles - CREATE TABLE articles ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - title VARCHAR(50), - body TEXT, - created DATETIME DEFAULT NULL, - modified DATETIME DEFAULT NULL - ); - -Si vous utilisez PostgreSQL, connectez-vous à la base de données cake_blog et -exécutez plutôt cette commande SQL: - -.. code-block:: SQL - - -- D'abord, créons la table des articles - CREATE TABLE articles ( - id SERIAL PRIMARY KEY, - title VARCHAR(50), - body TEXT, - created TIMESTAMP DEFAULT NULL, - modified TIMESTAMP DEFAULT NULL - ); - -Nous allons aussi y placer quelques articles qui pourront être utilisés pour les -tests. Exécutez les instructions SQL suivantes dans votre base de données (cela -marche aussi bien pour MySQL que pour PostgreSQL): - -.. code-block:: mysql - - # Puis insérons quelques articles pour les tests - INSERT INTO articles (title,body,created) - VALUES ('Le titre', 'Ceci est un contenu d\'article.', NOW()); - INSERT INTO articles (title,body,created) - VALUES ('Encore un titre', 'Et un autre contenu d\'article.', NOW()); - INSERT INTO articles (title,body,created) - VALUES ('Le retour du titre', 'C\'est vraiment excitant! Non.', NOW()); - -Le choix des noms de tables et de colonnes n'est pas arbitraire. -Si vous respectez les conventions de nommage de CakePHP pour les bases de -données et les classes (toutes deux expliquées au chapitre -:doc:`/intro/conventions`), vous tirerez profit d'un -grand nombre de fonctionnalités automatiques et vous éviterez des étapes -de configurations. CakePHP est suffisamment souple pour implémenter les pires -schémas de bases de données, mais respecter les conventions vous fera gagner -du temps. - -Consultez le chapitre :doc:`/intro/conventions` pour plus -d'informations, mais il suffit de comprendre que nommer notre table 'articles' -permet de la relier automatiquement à notre model Articles, et qu'avoir des -champs 'modified' et 'created' fait qu'ils seront gérés *automagiquement* par -CakePHP. - -Configurer la base de données -============================= - -Ensuite, indiquons à CakePHP où se trouve notre base de données et comment s'y -connecter. Pour la plupart d'entre vous, c'est la première et dernière fois que -vous configurerez quelque chose. - -Le fichier de configuration devrait être assez simple : remplacez simplement -les valeurs du tableau ``Datatsources.default`` dans le fichier -**config/app.php** avec ceux de votre config. Un exemple de tableau de -configuration complet pourrait ressembler à ce qui suit:: - - return [ - // Plus de configuration au-dessus. - 'Datasources' => [ - 'default' => [ - 'className' => 'Cake\Database\Connection', - 'driver' => 'Cake\Database\Driver\Mysql', - 'persistent' => false, - 'host' => 'localhost', - 'username' => 'cake_blog', - 'password' => 'AngelF00dC4k3~', - 'database' => 'cake_blog', - 'encoding' => 'utf8', - 'timezone' => 'UTC' - ], - ], - // Plus de configuration ci-dessous. - ]; - -Une fois votre fichier **config/app.php** sauvegardé, vous devriez -être en mesure d'ouvrir votre navigateur internet et de voir la page d'accueil -de CakePHP. Elle devrait également vous indiquer que votre fichier de connexion -a été trouvé, et que CakePHP peut s'y connecter avec succès. - -.. note:: - - Une copie du fichier de configuration par défaut de - CakePHP se trouve dans **config/app.default.php**. - -Configuration facultative -========================= - -Il y a quelques autres éléments qui peuvent être configurés. La plupart des -développeurs configurent les éléments de cette petite liste, mais ils ne -sont pas obligatoires pour ce tutoriel. Le premier consiste à définir une -chaîne de caractères personnalisée (ou "grain de sel") afin de sécuriser les -hashs. - -Le "grain de sel" est utilisé pour générer des hashes. Si vous avez utilisé -Composer, cela aussi a été pris en charge pendant l'installation. Sinon, changez -sa valeur par défaut en modifiant **config/app.php**. -La nouvelle valeur n'a pas beaucoup d'importance du moment qu'elle est -difficile à deviner:: - - 'Security' => [ - 'salt' => 'quelque chose de long et qui contienne plein de valeurs différentes.', - ], - -Une note sur mod\_rewrite -========================= - -Occasionnellement, les nouveaux utilisateurs peuvent avoir des problèmes de -mod\_rewrite. Par exemple si la page d'accueil de CakePHP a l'air bizarre -(pas d'images ou de styles CSS), cela signifie probablement que -mod\_rewrite ne fonctionne pas sur votre système. Merci de consulter la section -:ref:`url-rewriting` pour résoudre le problème. - -Maintenant continuez vers :doc:`/tutorials-and-examples/blog/part-two` pour -commencer à construire votre première application CakePHP. - -.. meta:: - :title lang=fr: Tutoriel d'un Blog - :keywords lang=fr: modèle vue contrôleur,model view controller,object oriented programming,application logic,directory setup,basic knowledge,database server,server configuration,reins,documentroot,readme,repository,web server,productivity,lib,sql,aim,cakephp,servers,apache,downloads diff --git a/fr/tutorials-and-examples/blog/part-three.rst b/fr/tutorials-and-examples/blog/part-three.rst deleted file mode 100644 index 8bde440b32..0000000000 --- a/fr/tutorials-and-examples/blog/part-three.rst +++ /dev/null @@ -1,407 +0,0 @@ -Tutoriel d'un Blog - Partie 3 -############################# - -Créer une Catégorie en Arbre (Tree) -=================================== - -Continuons notre application de blog et imaginons que nous souhaitions -catégoriser nos articles. Nous souhaitons que les catégories soit triées, et -pour cela, nous allons utiliser le :doc:`behavior Tree ` -pour nous aider à organiser les catégories. - -Mais d'abord, nous devons modifier nos tables. - -Plugin Migrations -================= - -Nous allons utiliser le -`plugin migrations `_ pour créer une -table dans notre base de données. Si vous avez déjà une table articles dans -votre base de données, supprimez-la. - -Maintenant ouvrez le fichier **composer.json** de votre application. Normalement -vous devriez voir que le plugin migrations est déjà dans ``require``. Si ce -n'est pas le cas, ajoutez-le en utilisant:: - - composer require cakephp/migrations:~1.0 - -Le plugin migrations va maintenant être dans le dossier **plugins** de votre -application. Ajoutez aussi ``Plugin::load('Migrations');`` à la méthode -``bootstrap`` de votre application. - -Une fois que le plugin est chargé, lancez la commande suivante pour créer un -fichier de migration:: - - bin/cake bake migration CreateArticles title:string body:text category_id:integer created modified - -Un fichier de migration sera généré dans le dossier **config/Migrations** avec -ce qui suit:: - - table('articles'); - $table->addColumn('title', 'string', [ - 'default' => null, - 'limit' => 255, - 'null' => false, - ]); - $table->addColumn('body', 'text', [ - 'default' => null, - 'null' => false, - ]); - $table->addColumn('category_id', 'integer', [ - 'default' => null, - 'limit' => 11, - 'null' => false, - ]); - $table->addColumn('created', 'datetime', [ - 'default' => null, - 'null' => false, - ]); - $table->addColumn('modified', 'datetime', [ - 'default' => null, - 'null' => false, - ]); - $table->create(); - } - } - -Exécutez une autre commande pour créer une table ``categories``. Si vous voulez -spécifier une longueur de champ, vous pouvez le faire entre crochets dans le -type du champ, par exemple:: - - bin/cake bake migration CreateCategories parent_id:integer lft:integer[10] rght:integer[10] name:string[100] description:string created modified - -Ceci va générer le fichier suivant dans **config/Migrations**:: - - table('categories'); - $table->addColumn('parent_id', 'integer', [ - 'default' => null, - 'limit' => 11, - 'null' => false, - ]); - $table->addColumn('lft', 'integer', [ - 'default' => null, - 'limit' => 10, - 'null' => false, - ]); - $table->addColumn('rght', 'integer', [ - 'default' => null, - 'limit' => 10, - 'null' => false, - ]); - $table->addColumn('name', 'string', [ - 'default' => null, - 'limit' => 100, - 'null' => false, - ]); - $table->addColumn('description', 'string', [ - 'default' => null, - 'limit' => 255, - 'null' => false, - ]); - $table->addColumn('created', 'datetime', [ - 'default' => null, - 'null' => false, - ]); - $table->addColumn('modified', 'datetime', [ - 'default' => null, - 'null' => false, - ]); - $table->create(); - } - } - -Maintenant que les fichiers de migration sont créés, vous pouvez les modifier -avant de créer vos tables. Nous devons changer ``'null' => false`` pour -le champ ``parent_id`` par ``'null' => true`` car une catégorie de premier -niveau a un ``parent_id`` null. - -Exécutez la commande suivante pour créer vos tables:: - - bin/cake migrations migrate - -Modifier les Tables -=================== - -Avec nos tables définies, nous pouvons maintenant nous focaliser sur la -catégorisation de nos articles. - -Nous supposons que vous avez déjà les fichiers (Tables, Controllers et -Templates des Articles) de la partie 2. Donc nous allons juste ajouter les -références aux catégories. - -Nous devons associer ensemble les tables Articles et Categories. Ouvrez le -fichier **src/Model/Table/ArticlesTable.php** et ajoutez ce qui suit:: - - // src/Model/Table/ArticlesTable.php - - namespace App\Model\Table; - - use Cake\ORM\Table; - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->addBehavior('Timestamp'); - // Ajoute juste la relation belongsTo avec CategoriesTable - $this->belongsTo('Categories', [ - 'foreignKey' => 'category_id', - ]); - } - } - -Générer les Squelettes de Code des Catégories -============================================= - -Créez tous les fichiers en lançant les commandes **bake** suivantes:: - - bin/cake bake model Categories - bin/cake bake controller Categories - bin/cake bake template Categories - -Au choix, vous pouvez aussi tous les créer en une seule ligne:: - - bin/cake bake all Categories - -L'outil bake a créé tous les fichiers en un clin d'œil. Vous pouvez les -lire rapidement si vous voulez vous re-familiariser avec le fonctionnement de -CakePHP. - -.. note:: - Si vous utilisez Windows, pensez à utiliser \\ à la place de /. - -Vous devrez modifier ce qui suit dans **templates/Categories/add.php** -et **templates/Categories/edit.php**:: - - echo $this->Form->control('parent_id', [ - 'options' => $parentCategories, - 'empty' => 'Pas de catégorie parente' - ]); - -Attacher TreeBehavior à CategoriesTable -======================================= - -Le :doc:`TreeBehavior ` vous aide à gérer des structures -hiérarchiques en arbre dans une table de base de données. Il utilise la -`logique MPTT `_ pour -gérer les données. Les structures en arbre MPTT sont optimisées pour lire des -données, ce qui les rend souvent pratiques pour lire des applications lourdes -comme les blogs. - -Si vous ouvrez le fichier **src/Model/Table/CategoriesTable.php**, vous verrez -que le TreeBehavior a été attaché à votre CategoriesTable dans la méthode -``initialize()``. Bake ajoute automatiquement ce behavior à toutes les Tables -qui contiennent les colonnes ``lft`` et ``rght``:: - - $this->addBehavior('Tree'); - -Avec le TreeBehavior attaché, vous serez capable d'accéder à certaines -fonctionnalités comme la réorganisation de l'ordre des categories. Nous verrons -cela dans un moment. - -Mais pour l'instant, vous devez retirer les lignes suivantes dans vos templates -**add** et **edit** des catégories:: - - echo $this->Form->control('lft'); - echo $this->Form->control('rght'); - -De plus, vous devez désactiver ou retirer les requirePresence du validateur -pour les colonnes ``lft`` et ``rght`` dans votre model CategoriesTable:: - - public function validationDefault(Validator $validator): Validator - { - $validator - ->add('id', 'valid', ['rule' => 'numeric']) - ->allowEmptyString('id', 'create'); - - $validator - ->add('lft', 'valid', ['rule' => 'numeric']) - // ->requirePresence('lft', 'create') - ->notEmpty('lft'); - - $validator - ->add('rght', 'valid', ['rule' => 'numeric']) - // ->requirePresence('rght', 'create') - ->notEmpty('rght'); - } - -Ces champs sont gérés automatiquement par le TreeBehavior quand -une catégorie est sauvegardée. - -En utilisant votre navigateur, ajoutez quelques nouvelles catégories en -utilisant l'action ``/yoursite/categories/add``. - -Réorganiser l'Ordre des Catégories avec le TreeBehavior -======================================================= - -Dans votre fichier de template **index** des catégories, vous pouvez lister les -catégories et les réordonner. - -Modifions la méthode index dans votre **CategoriesController.php** et ajoutons -les méthodes ``moveUp()`` et ``moveDown()`` pour pouvoir réorganiser l'ordre des -catégories dans l'arbre:: - - class CategoriesController extends AppController - { - public function index() - { - $categories = $this->Categories->find() - ->order(['lft' => 'ASC']) - ->all(); - $this->set(compact('categories')); - $this->viewBuilder()->setOption('serialize', ['categories']); - } - - public function moveUp($id = null) - { - $this->request->allowMethod(['post', 'put']); - $category = $this->Categories->get($id); - if ($this->Categories->moveUp($category)) { - $this->Flash->success('La catégorie a été remontée.'); - } else { - $this->Flash->error("La catégorie n'a pas pu être remontée. Veuillez réessayer."); - } - - return $this->redirect($this->referer(['action' => 'index'])); - } - - public function moveDown($id = null) - { - $this->request->allowMethod(['post', 'put']); - $category = $this->Categories->get($id); - if ($this->Categories->moveDown($category)) { - $this->Flash->success('La catégorie a été descendue.'); - } else { - $this->Flash->error("La catégorie n'a pas pu être descendue. Veuillez réessayer."); - } - - return $this->redirect($this->referer(['action' => 'index'])); - } - } - -Remplacez le contenu existant dans **templates/Categories/index.php** par -ceci:: - -
      -

      -
        -
      • Html->link(__('Nouvelle Categorie'), ['action' => 'add']) ?>
      • -
      -
      -
      - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      IdId parentGaucheDroiteNomDescriptionCréée le
      id ?>parent_id ?>lft ?>rght ?>name) ?>description) ?>created) ?> - Html->link(__('Voir'), ['action' => 'view', $category->id]) ?> - Html->link(__('Editer'), ['action' => 'edit', $category->id]) ?> - Form->postLink(__('Supprimer'), ['action' => 'delete', $category->id], ['confirm' => __('Etes vous sur de vouloir supprimer # {0}?', $category->id)]) ?> - Form->postLink(__('Descendre'), ['action' => 'moveDown', $category->id], ['confirm' => __('Etes vous sur de vouloir descendre # {0}?', $category->id)]) ?> - Form->postLink(__('Monter'), ['action' => 'moveUp', $category->id], ['confirm' => __('Etes vous sur de vouloir monter # {0}?', $category->id)]) ?> -
      -
      - -Modifier ArticlesController -=========================== - -Dans notre ``ArticlesController``, nous allons récupérer la liste de toutes les -catégories. Ceci va nous permettre de choisir une catégorie pour un Article -lorsqu'on va le créer ou le modifier:: - - // src/Controller/ArticlesController.php - - namespace App\Controller; - - use Cake\Http\Exception\NotFoundException; - - class ArticlesController extends AppController - { - - // ... - - public function add() - { - $article = $this->Articles->newEmptyEntity(); - if ($this->request->is('post')) { - $article = $this->Articles->patchEntity($article, $this->request->getData()); - if ($this->Articles->save($article)) { - $this->Flash->success(__('Votre article a été enregistré.')); - - return $this->redirect(['action' => 'index']); - } - $this->Flash->error(__("Impossible d\'ajouter votre article.")); - } - $this->set('article', $article); - - // Ajout de la liste des catégories pour pouvoir choisir - // une catégorie pour un article - $categories = $this->Articles->Categories->find('treeList'); - $this->set(compact('categories')); - } - } - -Modifier les Templates des Articles -=================================== - -Le fichier **add** des articles devrait ressembler à ceci: - -.. code-block:: php - - - -

      Ajouter un Article

      - Form->create($article); - // Ajout des input liés aux catégories (via la méthode "control") - echo $this->Form->control('category_id'); - echo $this->Form->control('title'); - echo $this->Form->control('body', ['rows' => '3']); - echo $this->Form->button(__("Enregistrer l'article")); - echo $this->Form->end(); - -Quand vous allez à l'adresse ``/yoursite/categories/add``, vous devriez voir une -liste de choix des catégories. - -.. meta:: - :title lang=fr: Tutoriel d'un Blog, Migrations et Tree - :keywords lang=fr: doc models,migrations,tree,controller actions,model article,php class,model class,model object,business logic,database table,naming convention,bread and butter,callbacks,prefixes,nutshell,interaction,array,cakephp,interface,applications,delete diff --git a/fr/tutorials-and-examples/blog/part-two.rst b/fr/tutorials-and-examples/blog/part-two.rst deleted file mode 100755 index e0ce294da7..0000000000 --- a/fr/tutorials-and-examples/blog/part-two.rst +++ /dev/null @@ -1,738 +0,0 @@ -Tutoriel d'un Blog - Partie 2 -############################# - -Créer un Model Article -====================== - -Les Models sont le pain quotidien des applications CakePHP. En -créant un model CakePHP qui interagira avec notre base de données, -nous aurons mis en place les fondations nécessaires pour faire plus -tard nos opérations de lecture, d'insertion, d'édition et de suppression. - -Les fichiers des classes de model de CakePHP sont séparés entre des objets -``Table`` et ``Entity``. Les objets ``Table`` fournissent un accès à la -collection des entities stockées dans une table spécifique et vont dans -**src/Model/Table**. Le fichier que nous allons créer sera sauvegardé dans -**src/Model/Table/ArticlesTable.php**. Le fichier complété devrait ressembler -à ceci:: - - // src/Model/Table/ArticlesTable.php - - namespace App\Model\Table; - - use Cake\ORM\Table; - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->addBehavior('Timestamp'); - } - } - -La convention de nommage est vraiment très importante dans CakePHP. En nommant -notre objet Table ``ArticlesTable``, CakePHP va automatiquement supposer que -cet objet Table sera utilisé dans le ``ArticlesController``, et sera lié à une -table de la base de données appelée ``articles``. - -.. note:: - - CakePHP créera dynamiquement un objet model pour vous, s'il ne trouve - pas le fichier correspondant dans **src/Model/Table**. Cela veut aussi dire - que si vous n'avez pas nommé correctement votre fichier (par ex. - articlestable.php ou ArticleTable.php). CakePHP ne reconnaîtra pas votre - configuration et utilisera les objets par défaut. - -Pour plus d'informations sur les models, comme les callbacks et la validation, -consultez le chapitre :doc:`/orm` du manuel. - -.. note:: - - Si vous avez terminé la :doc:`Partie 1 du Tutoriel du blog - ` et créé la table ``articles`` dans - notre base de données Blog, vous pouvez utiliser la console bake de CakePHP - et sa fonctionnalité de génération de code pour créer le model - ``ArticlesTable``:: - - bin/cake bake model Articles - -Pour plus d'informations sur bake et les fonctionnalités de génération de code, -vous pouvez allez voir :doc:`/bake/usage`. - -Créer le controller Articles -============================ - -Nous allons maintenant créer un controller pour nos articles. Le controller est -l'endroit où vont se faire toutes les interactions avec les articles. En un mot, c'est -l'endroit où vous jouerez avec les models et où vous ferez les tâches liées aux -articles. Nous placerons ce nouveau controller dans un fichier appelé -**ArticlesController.php** à l'intérieur du dossier **src/Controller**. Voici -à quoi devrait ressembler le controller de base:: - - // src/Controller/ArticlesController.php - - namespace App\Controller; - - class ArticlesController extends AppController - { - } - -Maintenant, ajoutons une action à notre controller. Les actions représentent -souvent une simple fonction ou une interface dans une application. Par exemple, -quand les utilisateurs requêtent www.exemple.com/articles/index (qui est -également la même chose que www.exemple.com/articles/), ils pourraient -s'attendre à voir une liste d'articles. Le code pour cette action devrait -ressembler à quelque chose comme ça:: - - // src/Controller/ArticlesController.php - - namespace App\Controller; - - class ArticlesController extends AppController - { - - public function index() - { - $articles = $this->Articles->find()->all(); - $this->set(compact('articles')); - } - } - -En définissant la fonction ``index()`` dans notre ``ArticlesController``, les -utilisateurs peuvent maintenant accéder à cette logique en demandant -www.exemple.com/articles/index. De la même façon, si nous devions définir une -fonction appelée ``foobar()``, les utilisateurs pourrait y accéder en demandant -www.exemple.com/articles/foobar. - -.. warning:: - - Vous pourriez être tenté de nommer vos controllers et vos actions d'une - certaine manière pour obtenir une certaine URL. Résistez à cette tentation. - Suivez les :doc:`/intro/conventions` de CakePHP (le nom des controllers au - pluriel, etc.) et nommez vos actions de façon lisible et compréhensible. - Vous pouvez lier les URLs à votre code en utilisant ce qu'on appelle le - :doc:`/development/routing`, on le verra plus tard. - -Dans cet action, la seule instruction utilise ``set()`` pour transmettre les -données du controller à la vue (que nous créerons à la prochaine étape). La -méthode ``find()`` de l'objet ``ArticlesTable`` renvoie une instance de -``Cake\\ORM\\Query`` et appelle sa méthode ``all()``, qui renvoie une instance -de ``Cake\\Collection\\CollectionInterface``, qui est affecté dans une variable -de la vue appelée 'articles'. - -.. note:: - - Si vous avez terminé la :doc:`Partie 1 du Tutoriel du blog - ` et créé la table ``articles`` dans - notre base de données Blog, vous pouvez utiliser la console bake de CakePHP - et sa fonctionnalité de génération de code pour créer la classe - ArticlesController:: - - bin/cake bake controller Articles - -Pour plus d'informations sur bake et les fonctionnalités de génération de code, -vous pouvez allez voir :doc:`/bake/usage`. - -Pour en apprendre plus sur les controllers de CakePHP, consultez le chapitre -:doc:`/controllers`. - -Créer les Vues des Articles -=========================== - -Maintenant que nous avons nos données en provenance du model, ainsi que la -logique applicative définie par notre controller, nous allons créer -une vue pour l'action ``index`` que nous avons créée ci-dessus. - -Les vues de CakePHP sont juste des fragments de présentation, "assaisonnés", -qui s'intègrent au sein du layout de l'application. Pour la plupart des -applications, elles sont un mélange de HTML et PHP, mais les vues peuvent aussi -être constituées de XML, CSV ou même de données binaires. - -Un Layout est un code de présentation qui entoure une vue. Vous pouvez en -définir plusieurs et passer de l'un à l'autre, mais pour le moment, utilisons -juste celui par défaut. - -Vous souvenez-vous, dans la dernière section, comment nous avions assigné -la variable 'articles' à la vue en utilisant la méthode ``set()`` ? -Cela transmettrait l'objet query à la vue, pour qu'elle puisse ensuite le -parcourir avec ``foreach``. - -Les fichiers de template de CakePHP sont stockés dans **templates**, à -l'intérieur d'un dossier dont le nom correspond à celui du controller (nous -aurons à créer un dossier appelé 'Articles' dans ce cas). Pour mettre en forme -les données de ces articles dans un joli tableau, le code de notre vue devrait -ressembler à quelque chose comme cela: - -.. code-block:: php - - - -

      Tous les articles du Blog

      - - - - - - - - - - - - - - - - -
      IdTitreCréé le
      id ?> - Html->link($article->title, ['action' => 'view', $article->id]) ?> - - created->format(DATE_RFC850) ?> -
      - -Espérons que cela vous semble simple. - -Vous avez sans doute remarqué l'utilisation d'un objet appelé ``$this->Html``. -C'est une instance de la classe CakePHP -:php:class:`Cake\\View\\Helper\\HtmlHelper`. CakePHP est livré avec un ensemble -d'assistants (*helpers*) pour les vues, qui réalisent en un clin d'œil -des choses comme le "linking" (mettre les liens dans un texte), l'affichage des -formulaires, du JavaScript et de l'AJAX. Vous pouvez en apprendre plus sur la -manière de les utiliser dans le chapitre :doc:`/views/helpers`, mais ce qu'il -est important de noter ici, c'est que la méthode ``link()`` générera un -lien HTML à partir d'un titre (le premier paramètre) et d'une URL (le second -paramètre). - -Lorsque vous indiquez des URLs dans CakePHP, il est recommandé d'utiliser les -tableaux. La raison est expliquée en détail dans le chapitre des Routes. -L'utilisation de tableaux -dans les URLs vous permet de tirer profit des capacités de CakePHP à -ré-inverser les routes. Vous pouvez aussi utiliser des URLs relatives à -la base de l'application sous la forme ``/controller/action/param1/param2`` ou -utiliser les :ref:`routes nommées `. - -À ce stade, vous devriez être en mesure de pointer votre navigateur sur la -page http://www.exemple.com/articles/index. Vous devriez voir votre vue, -correctement formatée avec le titre et le tableau listant les articles. - -Si vous avez essayé de cliquer sur l'un des liens que nous avons créés dans -cette vue (qui lient le titre d'un article à l'URL -``/articles/view/un_id_quelconque``), vous avez sûrement été informé par CakePHP -que l'action n'a pas encore été définie. S'il ne vous en a pas informé, soit -quelque chose s'est mal passé, soit en fait vous aviez déjà défini l'action, -auquel cas vous êtes vraiment sournois ! Sinon, nous allons la créer sans plus -tarder dans le Controller Articles:: - - // src/Controller/ArticlesController.php - - namespace App\Controller; - - class ArticlesController extends AppController - { - - public function index() - { - $this->set('articles', $this->Articles->find()->all()); - } - - public function view($id = null) - { - $article = $this->Articles->get($id); - $this->set(compact('article')); - } - } - -L'appel de ``set()`` devrait vous être familier. Notez que nous utilisons -``get()`` plutôt que ``find()`` parce que nous voulons -récupérer les informations d'un seul article. - -Notez que notre action "view" prend un paramètre : l'ID de l'article que nous -souhaitons consulter. Ce paramètre est transmis à l'action grâce à l'URL. -Si un utilisateur demande ``/articles/view/3``, alors la valeur '3' est -transmise à la variable ``$id``. - -Nous faisons aussi une petite vérification d'erreurs pour nous assurer qu'un -utilisateur accède bien à l'enregistrement. En utilisant -la fonction ``get()`` dans la table Articles, nous nous assurons que -l'utilisateur a accès à un enregistrement qui existe effectivement. Dans le cas -où l'article requêté n'est pas présent dans la base de données, ou si l'id est -incorrect, la fonction ``get()`` va lancer une ``NotFoundException``. - -Maintenant, créons la vue pour notre nouvelle action 'view' et plaçons-la -dans **templates/Articles/view.php**. - -.. code-block:: php - - - -

      title) ?>

      -

      body) ?>

      -

      Créé: created->format(DATE_RFC850) ?>

      - -Vérifiez que cela fonctionne en testant les liens de la page ``/articles/index`` -ou en affichant manuellement un article via ``/articles/view/{id}``, en -remplaçant {id} par un 'id' d'article. - -Ajouter des Articles -==================== - -Lire depuis la base de données et nous afficher les articles est un bon début, -mais lançons-nous dans l'ajout de nouveaux articles. - -Premièrement, commençons par créer une action ``add()`` dans le -``ArticlesController``:: - - // src/Controller/ArticlesController.php - - namespace App\Controller; - - use App\Controller\AppController; - - class ArticlesController extends AppController - { - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('Flash'); // Charge le FlashComponent - } - - public function index() - { - $this->set('articles', $this->Articles->find()->all()); - } - - public function view($id) - { - $article = $this->Articles->get($id); - $this->set(compact('article')); - } - - public function add() - { - $article = $this->Articles->newEmptyEntity(); - if ($this->request->is('post')) { - $article = $this->Articles->patchEntity($article, $this->request->getData()); - if ($this->Articles->save($article)) { - $this->Flash->success(__('Votre article a été sauvegardé.')); - - return $this->redirect(['action' => 'index']); - } - $this->Flash->error(__('Impossible d\'ajouter votre article.')); - } - $this->set('article', $article); - } - } - -.. note:: - - Vous avez besoin de charger le component :doc:`/controllers/components/flash` - dans chaque controller où vous voudrez l'utiliser. Si nécessaire, - chargez-le dans le controller principal (``AppController``). - -Voici ce que fait l'action ``add()`` : si la requête HTTP est de type POST, -elle essaye de sauvegarder les données en utilisant le model "Articles". Si pour -une raison quelconque la sauvegarde a échoué, elle affiche simplement la vue. -Cela nous donne la possibilité de montrer à l'utilisateur les erreurs de -validation ou d'autres avertissements. - -Chaque requête de CakePHP contient un objet ``ServerRequest`` qui est accessible -en utilisant ``$this->request``. Cet objet contient des informations utiles -sur la requête qui vient d'être reçue, et permet de contrôler le flux de votre -application. Dans ce cas, nous utilisons la méthode -:php:meth:`Cake\\Http\\ServerRequest::is()` pour vérifier que la requête est de -type POST. - -Lorsqu'un utilisateur utilise un formulaire pour poster des données dans votre -application, ces informations sont disponibles dans ``$this->request->getData()``. -Vous pouvez utiliser les fonctions :php:func:`pr()` ou :php:func:`debug()` pour -les afficher si vous voulez voir à quoi cela ressemble. - -Nous utilisons les méthodes ``success()`` et ``error()`` de FlashComponent pour -définir un message dans une variable de session. Ces méthodes sont fournies via -la `méthode magique _call() -`_ -de PHP. Les messages Flash seront affichés dans la page juste après la -redirection. Dans le layout, nous avons ``Flash->render() ?>`` qui -permet d'afficher le message et d'effacer la variable correspondante. La méthode -:php:meth:`Cake\\Controller\\Controller::redirect` du controller permet de -rediriger vers une autre URL. Le paramètre ``['action' => 'index']`` sera -traduit vers l'URL /articles, c'est à dire l'action "index" du controller -Articles (ArticlesController). Vous pouvez vous référer à l' -`API `_ de la fonction -:php:func:`Cake\\Routing\\Router::url()` pour voir les différents formats -d'URL acceptés dans les différentes fonctions de CakePHP. - -L'appel de la méthode ``save()`` vérifiera les règles de validation et -interrompra l'enregistrement si une erreur survient. Nous verrons -la façon dont les erreurs sont traitées dans les sections suivantes. - -Valider les Données -=================== - -Cake place la barre très haute pour briser la monotonie de la validation des -champs de formulaires. Tout le monde déteste le développement de formulaires -interminables et leurs routines de validations. Cake rend tout cela plus facile -et plus rapide. - -Pour tirer profit des fonctionnalités de validation, vous devez utiliser -le helper :doc:`/views/helpers/form` (FormHelper) dans vos vues. La classe -:php:class:`Cake\\View\\Helper\\FormHelper` est disponible par défaut dans -toutes les vues avec la variable ``$this->Form``. - -Voici le code de notre vue **add**: - -.. code-block:: php - - - -

      Ajouter un article

      - Form->create($article); - echo $this->Form->control('title'); - echo $this->Form->control('body', ['rows' => '3']); - echo $this->Form->button(__("Sauvegarder l\'article")); - echo $this->Form->end(); - ?>; - -Nous utilisons le FormHelper pour générer la balise -d'ouverture d'un formulaire HTML. Voici le code HTML généré par -``$this->Form->create()``: - -.. code-block:: html - - - -Si ``create()`` est appelée sans aucun paramètre, CakePHP suppose que vous -construisez un formulaire qui envoie les données en POST à l'action ``add()`` -(ou ``edit()`` quand ``id`` est dans les données du formulaire) du controller -actuel. - -La méthode ``$this->Form->control()`` est utilisée pour créer des éléments de -formulaire du même nom. Le premier paramètre dit à CakePHP à quel champ ils -correspondent et le second paramètre vous permet de spécifier un large éventail -d'options - dans notre cas, le nombre de lignes du textarea. Il y a un peu -d'introspection et "d'automagie" ici : ``control()`` affichera différents -éléments de formulaire selon le champ spécifié du model. - -L'appel de la méthode ``$this->Form->end()`` clôture le formulaire. Elle crée -les champs cachés si la protection de falsification de formulaire et/ou CSRF est -activée. - -À présent, revenons en arrière et modifions notre vue -**templates/Articles/index.php** pour ajouter un lien "Ajouter un article". -Ajoutez la ligne suivante avant ````:: - - Html->link('Ajouter un article', ['action' => 'add']) ?> - -Vous vous demandez peut-être : comment je fais pour indiquer à CakePHP mes -exigences de validation ? Les règles de validation sont définies dans le -model. Retournons donc à notre model Articles et procédons à quelques -ajustements:: - - // src/Model/Table/ArticlesTable.php - - namespace App\Model\Table; - - use Cake\ORM\Table; - use Cake\Validation\Validator; - - class ArticlesTable extends Table - { - public function initialize(array $config): void - { - $this->addBehavior('Timestamp'); - } - - public function validationDefault(Validator $validator): Validator - { - $validator - ->notEmptyString('title') - ->requirePresence('title', 'create') - ->notEmptyString('body') - ->requirePresence('body', 'create'); - - return $validator; - } - } - -Le méthode ``validationDefault()`` indique à CakePHP comment valider vos données -lorsque la méthode ``save()`` est appelée. Ici, nous avons spécifié que les deux -champs "body" et "title" ne doivent pas être vides et que ces champs sont requis -à la fois pour les opérations de création et de mise à jour. Le moteur de -validation de CakePHP est puissant, il dispose d'un certain nombre de règles -intégrées (code de carte bancaire, adresse emails, etc.) et d'une souplesse pour -ajouter vos propres règles de validation. Pour plus d'informations sur cette -configuration, consultez le chapitre :doc:`/core-libraries/validation`. - -Maintenant que vos règles de validation sont en place, utilisez l'application -pour essayer d'ajouter un article avec un titre et un contenu vides afin de voir -si cela fonctionne. Puisque que nous avons utilisé la méthode -:php:meth:`Cake\\View\\Helper\\FormHelper::control()` du helper "Form" pour -créer nos éléments de formulaire, nos messages d'erreurs de validation seront -affichés automatiquement. - -Éditer des Articles -=================== - -L'édition d'articles : nous y voilà ! Vous êtes un pro de CakePHP maintenant, -vous devriez donc avoir adopté le principe. Créez d'abord l'action, puis la vue. -Voici à quoi l'action ``edit()`` du controller Articles (``ArticlesController``) -devrait ressembler:: - - // src/Controller/ArticlesController.php - - public function edit($id = null) - { - $article = $this->Articles->get($id); - if ($this->request->is(['post', 'put'])) { - $this->Articles->patchEntity($article, $this->request->getData()); - if ($this->Articles->save($article)) { - $this->Flash->success(__('Votre article a été mis à jour.')); - - return $this->redirect(['action' => 'index']); - } - $this->Flash->error(__('Impossible de mettre à jour votre article.')); - } - - $this->set('article', $article); - } - -Cette action s'assure d'abord que l'utilisateur a essayé d'accéder à un -enregistrement existant. S'il n'y a pas de paramètre ``$id`` passé, ou si le -article n'existe pas, nous lançons une ``NotFoundException`` pour que le -gestionnaire d'Erreurs ErrorHandler de CakePHP s'en occupe. - -Ensuite l'action vérifie si la requête est une requête POST ou PUT. Si elle -l'est, alors nous utilisons les données POST pour mettre à jour notre -entity article en utilisant la méthode ``patchEntity()``. Finalement nous -utilisons l'objet table pour sauvegarder l'entity en retour, ou sinon rejeter -les données et montrer les erreurs de validation de l'utilisateur. - -La vue **edit** devrait ressembler à quelque chose comme cela: - -.. code-block:: php - - - -

      Modifier un article

      - Form->create($article); - echo $this->Form->control('title'); - echo $this->Form->control('body', ['rows' => '3']); - echo $this->Form->button(__('Sauvegarder l\'article')); - echo $this->Form->end(); - ?> - -Cette vue affiche le formulaire d'édition (avec les données pré-remplies) avec -les messages d'erreur de validation nécessaires. - -CakePHP déterminera si un ``save()`` doit générer une insertion d'un article ou -la mise à jour d'un article existant en fonction de l'état de l'entity. - -Vous pouvez maintenant mettre à jour votre vue **index** avec des liens pour -éditer chaque article: - -.. code-block:: php - - - -

      Blog articles

      -

      Html->link("Ajouter un Article", ['action' => 'add']) ?>

      -
      - - - - - - - - - - - - - - - - - - - -
      IdTitleCreatedAction
      id ?> - Html->link($article->title, ['action' => 'view', $article->id]) ?> - - created->format(DATE_RFC850) ?> - - Html->link('Modifier', ['action' => 'edit', $article->id]) ?> -
      - -Supprimer des Articles -====================== - -À présent, mettons en place un moyen pour les utilisateurs de supprimer les -articles. Commencez par une action ``delete()`` dans le controller -Articles (``ArticlesController``):: - - // src/Controller/ArticlesController.php - - public function delete($id) - { - $this->request->allowMethod(['post', 'delete']); - - $article = $this->Articles->get($id); - if ($this->Articles->delete($article)) { - $this->Flash->success(__("L'article avec l'id: {0} a été supprimé.", h($id))); - - return $this->redirect(['action' => 'index']); - } - } - -Cette logique supprime l'article spécifié par ``$id``, et utilise -``$this->Flash->success()`` pour afficher à l'utilisateur un message de -confirmation après l'avoir redirigé sur ``/articles``. Si l'utilisateur tente -une suppression en utilisant une requête GET, la méthode ``allowMethod()`` lève -une exception. -Les exceptions manquées sont capturées par le gestionnaire d'exceptions de -CakePHP et un joli message d'erreur est affiché. Il y a plusieurs -:doc:`Exceptions ` intégrées qui peuvent être utilisées -pour indiquer les différentes erreurs HTTP que votre application pourrait -rencontrer. - -Étant donné que nous exécutons juste un peu de logique et de redirection, -cette action n'a pas de vue. Vous voudrez peut-être néanmoins mettre à jour -votre vue **index** avec des liens pour permettre aux utilisateurs de supprimer -des articles: - -.. code-block:: php - - - -

      Blog articles

      -

      Html->link('Ajouter un Article', ['action' => 'add']) ?>

      - - - - - - - - - - - - - - - - - - - - -
      IdTitleCreatedActions
      id ?> - Html->link($article->title, ['action' => 'view', $article->id]) ?> - - created->format(DATE_RFC850) ?> - - Form->postLink( - 'Supprimer', - ['action' => 'delete', $article->id], - ['confirm' => 'Êtes-vous sûr ?']) - ?> - Html->link('Modifier', ['action' => 'edit', $article->id]) ?> -
      - -Utiliser :php:meth:`~Cake\\View\\Helper\\FormHelper::postLink()` permet de -créer un lien qui utilise du JavaScript pour supprimer notre article en faisant -une requête POST. - -.. warning:: - - Autoriser la suppression par une requête GET est dangereux à cause des - robots d'indexation qui peuvent supprimer accidentellement toutes vos - données. - -.. note:: - - Ce code de vue utilise aussi le helper ``FormHelper`` pour demander à - l'utilisateur une confirmation en JavaScript avant de supprimer un article. - -Routes -====== - -Pour certains, le routage par défaut de CakePHP fonctionne suffisamment bien. -Les développeurs qui sont sensibles à la lisibilité pour l'utilisateur et à la -compatibilité avec les moteurs de recherches apprécieront la manière dont -CakePHP lie des URLs à des actions spécifiques. Nous allons donc faire une -petite modification des routes dans ce tutoriel. - -Pour plus d'informations sur les techniques de routage avancées, consultez le -chapitre :ref:`routes-configuration`. - -Par défaut, CakePHP effectue une redirection d'une personne visitant la racine -de votre site (par ex: http://www.exemple.com) vers le controller Pages -(``PagesController``) et affiche le rendu de la vue appelée **home**. Au lieu de -cela, nous voudrions la remplacer avec notre controller Articles -(``ArticlesController``) en créant une règle de routage. - -Le routage de CakePHP se trouve dans **config/routes.php**. Vous devrez -commenter ou supprimer la ligne qui définit la route par défaut. Elle -ressemble à cela: - -.. code-block:: php - - $builder->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']); - -Cette ligne connecte l'URL '/' à la page d'accueil par défaut de CakePHP. Nous -voulons que cette URL soit connectée à notre propre controller, remplacez donc -la ligne par celle-ci: - -.. code-block:: php - - $builder->connect('/', ['controller' => 'Articles', 'action' => 'index']); - -Cela devrait connecter les utilisateurs demandant '/' à l'action ``index()`` de -notre controller Articles (``ArticlesController``). - -.. note:: - - CakePHP peut aussi faire du routage inversé (*reverse routing*). - Si, avec la route définie plus haut, vous passez - ``['controller' => 'Articles', 'action' => 'index']`` à une fonction qui - attend un tableau, l'URL générée sera '/'. Il est d'ailleurs bien - avisé de toujours utiliser un tableau pour les URLs car cela signifie que - vos routes définissent où vont les URLs, et ainsi cela vous assure qu'elles - pointent toujours le même endroit. - -Conclusion -========== - -Gardez à l'esprit que ce tutoriel était très basique. CakePHP a *beaucoup* plus -de fonctionnalités à offrir et il est aussi souple dans d'autres domaines que -nous n'avons pas souhaité couvrir ici pour simplifier les choses. Utilisez -le reste de ce manuel comme un guide pour développer des applications plus -riches en fonctionnalités. - -Maintenant que vous avez créé une application CakePHP basique, vous pouvez soit -continuer vers :doc:`/tutorials-and-examples/blog/part-three`, ou commencer -votre propre projet. Vous pouvez aussi lire attentivement les -:doc:`/topics` ou l'`API `_ pour en -apprendre plus sur CakePHP. - -Si vous avez besoin d'aide, il y a plusieurs façons d'obtenir de l'aide - -merci de regarder la page :doc:`/intro/where-to-get-help` -Bienvenue sur CakePHP ! - -Prochaines lectures suggérées ------------------------------ - -Voici les différents chapitres que les gens veulent souvent lire après: - -1. :ref:`view-layouts`: Personnaliser les layouts de votre application. -2. :ref:`view-elements`: Inclure et réutiliser des portions de vues. -3. :doc:`/bake/usage` Générer un code CRUD basique. -4. :doc:`/tutorials-and-examples/blog-auth-example/auth`: Tutoriel sur l'enregistrement et la connexion d'utilisateurs. - -.. meta:: - :title lang=fr: Blog Tutoriel Ajouter la logique - :keywords lang=fr: doc models,vérification validation,controller actions,model article,php class,classe model,objet model,business logic,table base de données,convention de nommage,bread et butter,callbacks,prefixes,nutshell,intéraction,array,cakephp,interface,applications,suppression diff --git a/fr/tutorials-and-examples/bookmarks/intro.rst b/fr/tutorials-and-examples/bookmarks/intro.rst deleted file mode 100644 index d9d2506690..0000000000 --- a/fr/tutorials-and-examples/bookmarks/intro.rst +++ /dev/null @@ -1,468 +0,0 @@ -Tutoriel de Bookmarker -###################### - -Ce tutoriel va vous montrer la création d'une application simple -de bookmarking (bookmarker). Pour commencer, nous allons installer CakePHP, -créer notre base de données et utiliser les outils que CakePHP nous fournit pour -créer une application rapidement. - -Voici ce dont vous allez avoir besoin: - -#. Un serveur de base de données. Nous allons utiliser un serveur MySQL dans - ce tutoriel. Vous devrez en savoir assez sur SQL pour créer une base de - données: CakePHP prendra les rênes à partir de là. Puisque nous utilisons - MySQL, assurez-vous aussi d'avoir ``pdo_mysql`` activé dans PHP. -#. Des connaissances de base en PHP. - -Avant de commencer, vous devez vous assurer que votre version de PHP est à jour: - -.. code-block:: console - - php -v - -Vous devez avoir installé au moins la version |minphpversion| (CLI) de PHP ou supérieure. -La version de PHP de votre serveur web doit aussi être |minphpversion| ou supérieure, et -doit être préférablement la même version que celle de votre interface en ligne -de commande (CLI). -Si vous souhaitez voir ce que donne l'application au final, regardez -`cakephp/bookmarker `__. C'est -parti ! - -Récupérer CakePHP -================= - -La façon la plus simple pour installer CakePHP est d'utiliser Composer. Composer -est un moyen simple d'installer CakePHP depuis votre terminal ou votre -prompteur de ligne de commandes. D'abord, vous aurez besoin de télécharger et -d'installer Composer si vous ne l'avez pas déjà fait. Si vous avez cURL -installé, exécutez ce qui suit:: - - curl -s https://getcomposer.org/installer | php - -Ou alors vous pouvez télécharger ``composer.phar`` depuis le -`site de Composer `_. - -Ensuite tapez simplement la ligne suivante dans votre terminal à partir -du répertoire d'installation pour installer le squelette d'application -CakePHP dans le répertoire **bookmarker**:: - - php composer.phar create-project --prefer-dist cakephp/app:4.* bookmarker - -Si vous avez téléchargé et exécuté l'`installeur Windows de Composer -`_, tapez la ligne suivante dans -votre terminal à partir de votre répertoire d'installation. (par exemple -C:\\wamp\\www\\dev\\cakephp3):: - - composer self-update && composer create-project --prefer-dist cakephp/app:4.* bookmarker - -L'avantage d'utiliser Composer est qu'il va automatiquement faire des tâches -de configuration importantes, comme de définir les bonnes permissions de -fichier et créer votre fichier **config/app.php** pour vous. - -Il y a d'autres façons d'installer CakePHP. Si vous ne pouvez ou ne voulez pas -utiliser Composer, consultez la section :doc:`/installation`. - -Peu importe la façon dont vous avez téléchargé et installé CakePHP, une fois -que votre configuration est faite, votre répertoire devrait ressembler à -ce qui suit:: - - /bookmarker - /bin - /config - /logs - /plugins - /src - /tests - /tmp - /vendor - /webroot - .editorconfig - .gitignore - .htaccess - .travis.yml - composer.json - index.php - phpunit.xml.dist - README.md - -C'est le bon moment pour en apprendre un peu plus sur la façon dont la -structure du répertoire de CakePHP fonctionne. Consultez la section -:doc:`/intro/cakephp-folder-structure`. - -Vérifions notre Installation -============================ - -Nous pouvons rapidement vérifier que notre installation fonctionne, en -vérifiant la page d'accueil par défaut. Avant de faire ceci, vous devrez -démarrer le serveur de développement:: - - bin/cake server - -.. note:: - - Sur Windows, cette commande doit être ``bin\cake server`` (notez l'antislash). - -Ceci va lancer le serveur web intégré de PHP sur le port 8765. Ouvrez -**http://localhost:8765** dans votre navigateur web pour voir la page d'accueil. -Tous les points devront être cochés sauf pour CakePHP qui n'est pas encore -capable de se connecter à votre base de données. Si ce n'est pas le cas, vous -devrez installer des extensions PHP supplémentaires ou définir des permissions -de répertoire. - -Créer la Base de Données -======================== - -Ensuite, configurons la base de données pour notre application de bookmarking. -Si vous ne l'avez pas déjà fait, créez une base de données vide que nous -allons utiliser dans ce tutoriel, avec un nom de votre choix, par exemple -``cake_bookmarks``. Vous pouvez exécuter le SQL suivant pour créer les -tables nécessaires: - -.. code-block:: mysql - - CREATE TABLE users ( - id INT AUTO_INCREMENT PRIMARY KEY, - email VARCHAR(255) NOT NULL, - password VARCHAR(255) NOT NULL, - created DATETIME, - modified DATETIME - ); - - CREATE TABLE bookmarks ( - id INT AUTO_INCREMENT PRIMARY KEY, - user_id INT NOT NULL, - title VARCHAR(50), - description TEXT, - url TEXT, - created DATETIME, - modified DATETIME, - FOREIGN KEY user_key (user_id) REFERENCES users(id) - ); - - CREATE TABLE tags ( - id INT AUTO_INCREMENT PRIMARY KEY, - title VARCHAR(191), - created DATETIME, - modified DATETIME, - UNIQUE KEY (title) - ); - - CREATE TABLE bookmarks_tags ( - bookmark_id INT NOT NULL, - tag_id INT NOT NULL, - PRIMARY KEY (bookmark_id, tag_id), - FOREIGN KEY tag_key(tag_id) REFERENCES tags(id), - FOREIGN KEY bookmark_key(bookmark_id) REFERENCES bookmarks(id) - ); - -Vous avez peut-être remarqué que la table ``bookmarks_tags`` utilisait une -clé primaire composite. CakePHP accepte les clés primaires composites presque -partout, facilitant la construction des applications à tenant multiples. - -La table et les noms de colonnes que nous avons utilisés n'étaient pas -arbitraires. En utilisant les -:doc:`conventions de nommage ` de CakePHP, nous pouvons -mieux contrôler CakePHP et éviter d'avoir à configurer le framework. CakePHP est -assez flexible pour s'accommoder de tout schéma de base de données, mais -suivre les conventions va vous faire gagner du temps. - -Configuration de Base de Données -================================ - -Ensuite, indiquons à CakePHP où se trouve notre base de données et comment -s'y connecter. -Pour la plupart d'entre vous, ce sera la première et la dernière fois que vous -devrez configurer quelque chose. - -La configuration est assez simple: remplacez juste les valeurs dans le -tableau ``Datasources.default`` dans le fichier **config/app.php** avec -ceux qui correspondent à votre configuration. Un exemple simple de tableau -de configuration pourrait ressembler à ce qui suit:: - - return [ - // Plus de configuration au-dessus. - 'Datasources' => [ - 'default' => [ - 'className' => 'Cake\Database\Connection', - 'driver' => 'Cake\Database\Driver\Mysql', - 'persistent' => false, - 'host' => 'localhost', - 'username' => 'cakephp', - 'password' => 'AngelF00dC4k3~', - 'database' => 'cake_bookmarks', - 'encoding' => 'utf8', - 'timezone' => 'UTC', - 'cacheMetadata' => true, - ], - ], - // Plus de configuration en dessous. - ]; - -Une fois que vous avez sauvegardé votre fichier **config/app.php**, vous -devriez voir la section 'CakePHP est capable de se connecter à la base de -données' cochée. - -.. note:: - - Une copie du fichier de configuration par défaut de CakePHP se trouve dans - **config/app.default.php**. - -Génération de Code Scaffold -=========================== - -Comme notre base de données suit les conventions de CakePHP, nous pouvons -utiliser l'application de -:doc:`console bake ` pour -générer rapidement une application basique. Dans votre terminal, lancez -les commandes suivantes:: - - // Sur Windows vous devez utiliser bin\cake à la place. - bin/cake bake all users - bin/cake bake all bookmarks - bin/cake bake all tags - -Ceci va générer les controllers, models, views, leurs cas de tests -correspondants et les fixtures pour nos ressources users, bookmarks et tags. -Si vous avez stoppé votre serveur, relancez-le et allez sur -**http://localhost:8765/bookmarks**. - -Vous devriez voir une application basique mais fonctionnelle fournissant -des accès aux données vers les tables de la base de données de votre -application. Une fois que vous avez la liste des bookmarks, ajoutez quelques -users, bookmarks, et tags. - -.. note:: - - Si vous avez une page Not Found (404), vérifiez que le module mod_rewrite - d'Apache est chargé. - -Ajouter un Hashage de Mot de Passe -================================== - -Quand vous avez créé vos users, (en visitant -**http://localhost:8765/users**) vous avez probablement remarqué que -les mots de passe sont stockés en clair. C'est très mauvais d'un point du vue -sécurité, donc réglons ceci. - -C'est aussi un bon moment pour parler de la couche model dans CakePHP. Dans -CakePHP, nous séparons les méthodes qui agissent sur une collection d'objets, et -celles qui agissent sur un objet unique, dans des classes différentes. Les -méthodes qui agissent sur la collection des entities sont mises dans la classe -``Table``, alors que les fonctionnalités correspondant à un enregistrement -unique sont mises dans la classe ``Entity``. - -Par exemple, le hashage des mots de passe se fait pour un enregistrement -individuel, donc nous allons intégrer ce comportement sur l'objet entity. -Comme nous voulons hasher le mot de passe à chaque fois qu'il est défini nous -allons utiliser une méthode mutateur/setter. CakePHP va appeler les méthodes -setter basées sur les conventions à chaque fois qu'une propriété est définie -dans une de vos entities. Ajoutons un setter pour le mot de passe. Dans -**src/Model/Entity/User.php**, ajoutez ce qui suit:: - - namespace App\Model\Entity; - - use Cake\Auth\DefaultPasswordHasher; - use Cake\ORM\Entity; - - class User extends Entity - { - - // Code from bake. - - protected function _setPassword($value) - { - $hasher = new DefaultPasswordHasher(); - - return $hasher->hash($value); - } - } - -Maintenant mettez à jour un des users que vous avez créé précédemment, si vous -changez son mot de passe, vous devriez voir un mot de passe hashé à la place de -la valeur originale sur la liste ou les pages de vue. CakePHP hashe les mots de -passe avec -`bcrypt `_ par défaut. -Vous pouvez aussi utiliser sha1 ou md5 si vous travaillez avec une base de -données existante. - -.. note:: - - Si le mot de passe n'est pas haché, assurez-vous que vous avez suivi le même cas pour le mot de passe membre de la classe tout en nommant la fonction mutateur/setter - -Récupérer les Bookmarks avec un Tag Spécifique -============================================== - -Maintenant que vous avez stocké les mots de passe de façon sécurisé, nous -pouvons construire quelques fonctionnalités intéressantes dans notre -application. Une fois que vous avez une collection de bookmarks, il peut -être pratique de pouvoir les chercher par tag. Ensuite nous allons intégrer une -route, une action de controller, et une méthode finder pour chercher les -bookmarks par tag. - -Idéalement, nous aurions une URL qui ressemble à -**http://localhost:8765/bookmarks/tagged/funny/cat/gifs** Cela nous aide à -trouver tous les bookmarks qui ont les tags 'funny', 'cat' ou 'gifs'. Avant de -pouvoir intégrer ceci, nous allons ajouter une nouvelle route. Votre fichier -**config/routes.php** doit ressembler à ceci:: - - 'Bookmarks'], - function ($routes) { - $routes->connect('/tagged/*', ['action' => 'tags']); - } - ); - - Router::scope('/', function ($routes) { - // Connecte la page d'accueil par défaut et les routes /pages/*. - $routes->connect('/', [ - 'controller' => 'Pages', - 'action' => 'display', 'home' - ]); - $routes->connect('/pages/*', [ - 'controller' => 'Pages', - 'action' => 'display' - ]); - - // Connecte les routes basées sur les conventions par défaut. - $routes->fallbacks(); - }); - -Ce qui est au-dessus définit une nouvelle 'route' qui connecte le chemin -**/bookmarks/tagged/***, vers ``BookmarksController::tags()``. En définissant -les routes, vous pouvez isoler la définition de vos URLs, de la façon dont elles -sont intégrées. Si nous visitions **http://localhost:8765/bookmarks/tagged**, -nous verrions une page d'erreur de CakePHP. Intégrons maintenant la méthode -manquante. Dans **src/Controller/BookmarksController.php**, ajoutez ce qui -suit:: - - public function tags() - { - // La clé 'pass' est fournie par CakePHP et contient tous les segments - // d'URL de la "request" (instance de \Cake\Network\Request) - $tags = $this->request->getParam('pass'); - - // On utilise l'objet "Bookmarks" (une instance de - // \App\Model\Table\BookmarksTable) pour récupérer les bookmarks avec - // ces tags - $bookmarks = $this->Bookmarks->find('tagged', [ - 'tags' => $tags - ]); - - // Passe les variables au template de vue (view). - $this->set([ - 'bookmarks' => $bookmarks, - 'tags' => $tags - ]); - } - -Pour accéder aux autres parties des données de la "request", référez-vous à la -section :ref:`cake-request`. - -Créer la Méthode Finder ------------------------ - -Dans CakePHP, nous aimons garder les actions de notre controller légères, et -mettre la plupart de la logique de notre application dans les models. Si vous -visitez l'URL **/bookmarks/tagged** maintenant, vous verrez une erreur comme -quoi la méthode ``findTagged()`` n'a pas été encore intégrée, donc faisons-le. -Dans **src/Model/Table/BookmarksTable.php** ajoutez ce qui suit:: - - // L'argument $query est une instance de \Cake\ORM\Query. - // Le tableau $options contiendra les tags que nous avons passé à find('tagged') - // dans l'action de notre Controller - public function findTagged(Query $query, array $options) - { - $bookmarks = $this->find() - ->select(['id', 'url', 'title', 'description']); - - if (empty($options['tags'])) { - $bookmarks - ->leftJoinWith('Tags') - ->where(['Tags.title IS' => null]); - } else { - $bookmarks - ->innerJoinWith('Tags') - ->where(['Tags.title IN ' => $options['tags']]); - } - - return $bookmarks->group(['Bookmarks.id']); - } - -Nous intégrons juste :ref:`des finders personnalisés `. -C'est un concept très puissant dans CakePHP qui vous permet de faire un package -réutilisable de vos requêtes. Les finders attendent toujours un objet -:doc:`/orm/query-builder` et un tableau d'options en paramètre. Les finders -peuvent manipuler les requêtes et ajouter n'importe quels conditions ou -critères. Une fois qu'ils ont terminé, les finders doivent retourner l'objet -Query modifié. Dans notre finder nous avons amené les méthodes -``innerJoinWith()``, ``where()`` et ``group()`` qui nous permet de trouver les -bookmarks distinct qui ont un tag correspondante. Lorsque aucune tag n'est -fournie, nous utilisons un ``leftJoinWith()`` et modifions la condition -'where', en trouvant des bookmarks sans tags. - -Créer la Vue ------------- - -Maintenant si vous vous rendez à l'url **/bookmarks/tagged**, CakePHP va -afficher une erreur vous disant que vous n'avez pas de fichier de vue. -Construisons donc le fichier de vue pour notre action ``tags()``. Dans -**templates/Bookmarks/tags.php** mettez le contenu suivant:: - -

      - Bookmarks tagged with - Text->toList(h($tags)) ?> -

      - -
      - -
      - -

      Html->link($bookmark->title, $bookmark->url) ?>

      - url) ?> - - - Text->autoParagraph(h($bookmark->description)) ?> -
      - -
      - -Dans le code ci-dessus, nous utilisons le :doc:`Helper HTML ` -et le :doc:`Helper Text ` pour aider à la génération -du contenu de notre vue. Nous utilisons également la fonction :php:func:`h` -pour encoder la sortie en HTML. Vous devez vous rappeler de toujours utiliser -``h()`` lorsque vous affichez des données provenant des utilisateurs pour éviter -les problèmes d'injection HTML. - -Le fichier **tags.php** que nous venons de créer suit la convention de nommage -de CakePHP pour un ficher de template de vue. La convention d'avoir le nom -de template en minuscule et en underscore du nom de l'action du controller. - -Vous avez peut-être remarqué que nous pouvions utiliser les variables -``$tags`` et ``$bookmarks`` dans notre vue. Quand nous utilisons la méthode -``set()`` dans notre controller, nous définissons les variables spécifiques à -envoyer à la vue. La vue va rendre disponible toutes les variables passées -dans les templates en variables locales. - -Vous devriez maintenant pouvoir visiter l'URL **/bookmarks/tagged/funny** et -voir tous les bookmarks taggés avec 'funny'. - -Ainsi nous avons créé une application basique pour gérer des bookmarks, des -tags et des users. -Cependant, tout le monde peut voir tous les tags de tout le monde. Dans le -prochain chapitre, nous allons intégrer une authentification et restreindre -la visibilité des bookmarks à ceux qui appartiennent à l'utilisateur courant. - -Maintenant continuons avec -:doc:`/tutorials-and-examples/bookmarks/part-two` -pour construire votre application ou :doc:`plongez dans la documentation -` pour en apprendre plus sur ce que CakePHP peut faire pour vous. diff --git a/fr/tutorials-and-examples/bookmarks/part-two.rst b/fr/tutorials-and-examples/bookmarks/part-two.rst deleted file mode 100644 index c486403ab7..0000000000 --- a/fr/tutorials-and-examples/bookmarks/part-two.rst +++ /dev/null @@ -1,439 +0,0 @@ -Tutoriel de Bookmarker Part 2 -############################# - -Après avoir fini :doc:`la première partie de ce tutoriel -` vous devriez avoir une application -basique de bookmarking. Dans ce chapitre, nous ajouterons l'authentification -et nous allons restreindre les bookmarks pour que chaque utilisateur puisse -voir/modifier seulement ceux qui lui appartiennent. - -Ajouter la Connexion -==================== - -Dans CakePHP, l'authentification est gérée par les -:doc:`/controllers/components`. Les components peuvent être imaginés comme des -façons de créer des parties réutilisables de code du controller pour une -fonctionnalité spécifique ou un concept. Les components peuvent aussi se lancer -dans le cycle de vie de l'event du controller et interagir avec votre -application de cette façon. Pour commencer, nous ajouterons :doc:`AuthComponent -
      ` à notre application. Nous voulons -que chaque méthode nécessite l'authentification, donc nous allons ajouter -AuthComponent dans notre AppController:: - - // Dans src/Controller/AppController.php - namespace App\Controller; - - use Cake\Controller\Controller; - - class AppController extends Controller - { - public function initialize(): void - { - $this->loadComponent('Flash'); - $this->loadComponent('Auth', [ - 'authenticate' => [ - 'Form' => [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password' - ] - ] - ], - 'loginAction' => [ - 'controller' => 'Users', - 'action' => 'login' - ], - // Si l'utilisateur arrive sur une page non-autorisée, on le - // redirige sur la page précédente. - 'unauthorizedRedirect' => $this->referer() - ]); - - // Autorise l'action display pour que notre controller de pages - // continue de fonctionner. - $this->Auth->allow(['display']); - } - } - -Nous avons seulement indiqué à CakePHP que nous souhaitions charger les -components ``Flash`` et ``Auth``. En plus, nous avons personnalisé la -configuration de AuthComponent, puisque notre table users utilise ``email`` -comme username. Maintenant, si vous tapez n'importe quelle URL, vous serez -renvoyé vers **/users/login**, qui vous montrera une page d'erreur puisque -nous n'avons pas encore écrit ce code. Créons donc l'action login:: - - // Dans src/Controller/UsersController.php - - public function login() - { - if ($this->request->is('post')) { - $user = $this->Auth->identify(); - if ($user) { - $this->Auth->setUser($user); - - return $this->redirect($this->Auth->redirectUrl()); - } - $this->Flash->error('Votre username ou mot de passe est incorrect.'); - } - } - -Et dans **templates/Users/login.php**, ajoutez ce qui suit:: - -

      Connexion

      - Form->create() ?> - Form->control('email') ?> - Form->control('password') ?> - Form->button('Login') ?> - Form->end() ?> - -Maintenant que nous avons un formulaire simple de connexion, nous devrions -pouvoir nous connecter avec un de nos utilisateurs qui a un mot de passe -hashé. - -.. note:: - - Si aucun de vos utilisateurs n'a de mot de passe hashé, commentez la ligne - ``loadComponent('Auth')``. Puis allez modifier l'utilisateur, créez- - lui un nouveau mot de passe. - -Ajouter la Déconnexion -====================== - -Maintenant que les personnes peuvent se connecter, vous voudrez aussi -probablement fournir un moyen de se déconnecter. Encore une fois, dans -``UsersController``, ajoutez le code suivant:: - - public function initialize(): void - { - parent::initialize(); - $this->Auth->allow(['logout']); - } - - public function logout() - { - $this->Flash->success('Vous êtes maintenant déconnecté.'); - - return $this->redirect($this->Auth->logout()); - } - -Ce code autorise l'action ``logout`` en tant qu'action publique, -et implémente la méthode logout. Vous pouvez maintenant visiter la page -``/users/logout`` pour vous déconnecter. Vous devriez alors être renvoyé vers -la page de connexion. - -Permettre de s'Enregistrer -========================== - -Si vous n'êtes pas connecté et que vous essayez de visiter **/users/add** vous -serez renvoyés vers la page de connexion. Nous devrions régler cela puisque nous -voulons que les utilisateurs s'inscrivent à notre application. Dans -``UsersController``, ajoutez ce qui suit:: - - public function initialize(): void - { - parent::initialize(); - // Ajoute l'action 'add' à la liste des actions autorisées. - $this->Auth->allow(['logout', 'add']); - } - -Ce qui est au-dessus indique à ``AuthComponent`` que l'action ``add()`` *ne* -nécessite *pas* d'authentification ou d'autorisation. Vous pouvez prendre le -temps de nettoyer **Users/add.php** et de retirer les liens, ou continuez vers -la prochaine section. Nous ne ferons pas de fichier d'édition (edit) ou de vue -d'un utilisateur (view), ni de liste d'utilisateurs (index) dans ce tutoriel -donc ils ne fonctionneront pas puisque ``AuthComponent`` va vous refuser -l'accès pour ces actions de controller. - -Restreindre l'Accès aux Bookmarks -================================= - -Maintenant que les utilisateurs peuvent se connecter, nous voulons limiter -les bookmarks qu'ils peuvent voir à ceux qu'ils ont créés. Nous allons le faire -en utilisant un adaptateur 'authorization'. Puisque nos besoins sont -assez simples, nous pouvons écrire quelques lignes de code simple dans notre -``BookmarksController``. Mais avant de le faire, nous voulons dire à -AuthComponent comment notre application va autoriser les actions. Dans notre -``AppController``, ajoutez ce qui suit:: - - public function isAuthorized($user) - { - return false; - } - -Ajoutez aussi ce qui suit dans la configuration de ``Auth`` dans -``AppController``:: - - 'authorize' => 'Controller', - -Votre méthode ``initialize()`` doit maintenant ressembler à ceci:: - - public function initialize(): void - { - $this->loadComponent('Flash'); - $this->loadComponent('Auth', [ - 'authorize'=> 'Controller',//added this line - 'authenticate' => [ - 'Form' => [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password' - ] - ] - ], - 'loginAction' => [ - 'controller' => 'Users', - 'action' => 'login' - ], - 'unauthorizedRedirect' => $this->referer() - ]); - - // Allow the display action so our pages controller - // continues to work. - $this->Auth->allow(['display']); - } - -Nous allons par défaut refuser l'accès, et permettre un accès incrémental où -cela est utile. D'abord, nous allons ajouter la logique d'autorisation pour -les bookmarks. Dans notre ``BookmarksController``, ajoutez ce qui suit:: - - public function isAuthorized($user) - { - $action = $this->request->params['action']; - - // Add et index sont toujours permises. - if (in_array($action, ['index', 'add', 'tags'])) { - return true; - } - // Tout autre action nécessite un id. - if (!$this->request->getParam('pass.0')) { - return false; - } - - // Vérifie que le bookmark appartient à l'utilisateur courant. - $id = $this->request->getParam('pass.0'); - $bookmark = $this->Bookmarks->get($id); - if ($bookmark->user_id == $user['id']) { - return true; - } - - return parent::isAuthorized($user); - } - -Maintenant, si vous essayez de voir, de modifier ou de supprimer un bookmark qui -ne vous appartient pas, vous devriez être redirigé vers la page d'où vous venez. -Si aucun message ne s'affiche, ajoutez la ligne suivante dans votre layout:: - - // Dans templates/layout/default.php - Flash->render() ?> - -Vous devriez maintenant voir les messages d'erreur d'autorisation. - -Régler la Vue de Liste et les Formulaires -========================================= - -Alors que view et delete fonctionnent, edit, add et index ont quelques -problèmes: - -#. Lors de l'ajout d'un bookmark, vous pouvez choisir l'utilisateur. -#. Lors de l'édition d'un bookmark vous pouvez choisir l'utilisateur. -#. La page de liste montre les bookmarks des autres utilisateurs. - -Attaquons nous d'abord à add. Pour commencer, retirez ``control('user_id')`` de -**templates/Bookmarks/add.php**. Une fois retiré, nous allons aussi mettre à -jour l'action ``add()`` dans **src/Controller/BookmarksController.php** pour -ressembler à ceci:: - - public function add() - { - $bookmark = $this->Bookmarks->newEntity(); - if ($this->request->is('post')) { - $bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->getData()); - $bookmark->user_id = $this->Auth->user('id'); - if ($this->Bookmarks->save($bookmark)) { - $this->Flash->success('Le bookmark a été sauvegardé.'); - - return $this->redirect(['action' => 'index']); - } - $this->Flash->error('Le bookmark ne peut être sauvegardé. Merci de réessayer.'); - } - $tags = $this->Bookmarks->Tags->find('list'); - $this->set(compact('bookmark', 'tags')); - $this->viewBuilder()->setOption('serialize', ['bookmark']); - } - -En définissant la propriété entity avec les données de session, nous retirons -la possibilité que l'utilisateur puisse modifier l'auteur d'un bookmark. -Nous ferons la même chose pour le formulaire et l'action edit. Votre action -``edit()`` dans **src/Controller/BookmarksController.php** devrait ressembler à -ceci:: - - public function edit($id = null) - { - $bookmark = $this->Bookmarks->get($id, [ - 'contain' => ['Tags'] - ]); - if ($this->request->is(['patch', 'post', 'put'])) { - $bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->getData()); - $bookmark->user_id = $this->Auth->user('id'); - if ($this->Bookmarks->save($bookmark)) { - $this->Flash->success('Le bookmark a été sauvegardé.'); - - return $this->redirect(['action' => 'index']); - } else { - $this->Flash->error('Le bookmark ne peut être sauvegardé. Merci de réessayer.'); - } - } - $tags = $this->Bookmarks->Tags->find('list'); - $this->set(compact('bookmark', 'tags')); - $this->viewBuilder()->setOption('serialize', ['bookmark']); - } - -Vue de Liste ------------- - -Maintenant nous devons afficher les bookmarks pour l'utilisateur actuellement -connecté. Nous pouvons le faire en mettant à jour l'appel à ``paginate()``. -Faites en sorte que votre action ``index()`` dans -**src/Controller/BookmarksController.php** ressemble à ceci:: - - public function index() - { - $this->paginate = [ - 'conditions' => [ - 'Bookmarks.user_id' => $this->Auth->user('id'), - ] - ]; - $this->set('bookmarks', $this->paginate($this->Bookmarks)); - $this->viewBuilder()->setOption('serialize', ['bookmarks']); - } - -Nous devrions aussi mettre à jour l'action ``tags()`` et la méthode finder -liée, mais nous vous laisserons ceci en exercice que vous pouvez faire -vous-même. - -Améliorer l'Experience de Tag -============================= - -Actuellement, ajouter des nouveaux tags est un processus difficile, puisque -``TagsController`` interdit tous les accès. Plutôt que de permettre l'accès, -nous pouvons améliorer l'UI de sélection de tag en utilisant un champ de texte -séparé par des virgules. Cela donnera une meilleure expérience à nos -utilisateurs, et utilisera quelques unes des super fonctionnalités de l'ORM. - -Ajouter un Champ Computed -------------------------- - -Comme nous voulons un accès simple vers les tags formatés pour une entity, nous -pouvons ajouter un champ virtuel/calculé à l'entity. Dans -**src/Model/Entity/Bookmark.php** ajoutez ce qui suit:: - - use Cake\Collection\Collection; - - protected function _getTagString() - { - if (isset($this->_fields['tag_string'])) { - return $this->_fields['tag_string']; - } - if (empty($this->tags)) { - return ''; - } - $tags = new Collection($this->tags); - $str = $tags->reduce(function ($string, $tag) { - return $string . $tag->title . ', '; - }, ''); - - return trim($str, ', '); - } - -Cela nous laissera l'accès à la propriété calculée ``$bookmark->tag_string``. -Nous utiliserons cette propriété dans controls plus tard. Rappelez-vous -d'ajouter la propriété ``tag_string`` dans la liste ``_accessible`` de votre -entity, puisque nous voulons la 'sauvegarder' plus tard. - -Dans le fichier **src/Model/Entity/Bookmark.php**, ajoutez ``tag_string`` à -la propriété ``_accessible`` comme ceci:: - - protected array $_accessible = [ - 'user_id' => true, - 'title' => true, - 'description' => true, - 'url' => true, - 'user' => true, - 'tags' => true, - 'tag_string' => true, - ]; - -Mettre à Jour les Vues ----------------------- - -Avec l'entity mise à jour, nous pouvons ajouter un nouveau *control* pour nos tags. -Dans **templates/Bookmarks/add.php** et **templates/Bookmarks/edit.php**, -remplacez l'input ``tags._ids`` existant avec ce qui suit:: - - echo $this->Form->control('tag_string', ['type' => 'text']); - -Persister la Chaîne Tag ------------------------ - -Maintenant que nous pouvons voir les tags existants en chaîne, nous voudrions -aussi sauvegarder les données. Comme nous marquons les ``tag_string`` -accessibles, l'ORM va copier ces données à partir de la requête dans notre -entity. Nous pouvons utiliser une méthode hook ``beforeSave()`` pour parser la -chaîne de tag et trouver/construire les entities liées. Ajoutez ce qui suit dans -**src/Model/Table/BookmarksTable.php**:: - - public function beforeSave($event, $entity, $options) - { - if ($entity->tag_string) { - $entity->tags = $this->_buildTags($entity->tag_string); - } - } - - protected function _buildTags($tagString) - { - // Trim tags - $newTags = array_map('trim', explode(',', $tagString)); - // Retire tous les tags vides - $newTags = array_filter($newTags); - // Réduit les tags dupliqués - $newTags = array_unique($newTags); - - $out = []; - $query = $this->Tags->find() - ->where(['Tags.title IN' => $newTags]); - - // Retire les tags existants de la liste des tags nouveaux. - foreach ($query->extract('title') as $existing) { - $index = array_search($existing, $newTags); - if ($index !== false) { - unset($newTags[$index]); - } - } - // Ajoute les tags existants. - foreach ($query as $tag) { - $out[] = $tag; - } - // Ajoute les nouveaux tags. - foreach ($newTags as $tag) { - $out[] = $this->Tags->newEntity(['title' => $tag]); - } - - return $out; - } - -Alors que ce code est un peu plus compliqué que ce que nous avons déjà fait, -il permet de montrer la puissance de l'ORM de CakePHP. Vous pouvez facilement -manipuler les résultats de requête en utilisant les méthodes des -:doc:`/core-libraries/collections`, et gérer les scenarios où vous créez les -entities à la volée avec facilité. - -Récapitulatif -============= - -Nous avons élargi notre application de bookmarking pour gérer les scenarios de -contrôle d'authentification et d'autorisation/d'accès basique. Nous avons aussi -ajouté quelques améliorations UX en tirant parti du FormHelper et des capacités -de l'ORM. - -Merci d'avoir pris le temps d'explorer CakePHP. Ensuite, vous pouvez finir le -tutoriel du :doc:`/tutorials-and-examples/blog/blog`, en apprendre plus sur -l':doc:`ORM ` ou vous pouvez lire attentivement :doc:`/topics`. diff --git a/fr/tutorials-and-examples/cms/articles-controller.rst b/fr/tutorials-and-examples/cms/articles-controller.rst deleted file mode 100644 index f0ba714d51..0000000000 --- a/fr/tutorials-and-examples/cms/articles-controller.rst +++ /dev/null @@ -1,577 +0,0 @@ -Tutoriel CMS - Création du Controller Articles -############################################## - -Maintenant que notre model est créé, nous avons besoin d'un controller pour nos -articles. Dans CakePHP, les controllers se chargent de gérer les requêtes HTTP et -exécutent la logique métier des méthodes des models pour préparer une réponse. Nous -placerons le code de ce controller dans un nouveau fichier **ArticlesController.php**, -dans le dossier **src/Controller**. La base du controller ressemblera à ceci:: - - loadComponent('Paginator'); - $articles = $this->Paginator->paginate($this->Articles->find()); - $this->set(compact('articles')); - } - } - -Maintenant que nous avons une méthode ``index()`` dans notre ``ArticlesController``, -les utilisateurs peuvent y accéder via **www.example.com/articles/index**. -De la même manière, si nous définissions une méthode ``foobar()``, les utilisateurs -pourraient y accéder via **www.example.com/articles/foobar**. Vous pourriez être tenté -de nommer vos controllers et vos actions afin d'obtenir des URL spécifiques. Cependant, -ceci est déconseillé. Vous devriez plutôt suivre les :doc:`/intro/conventions` -et créer des noms d'actions lisibles ayant un sens pour votre application. Vous pouvez -ensuite utiliser le :doc:`/development/routing` pour obtenir les URLs que vous -souhaitez et les connecter aux actions que vous avez créées. - -Notre action est très simple. Elle récupère un jeu d'articles paginés dans la base de -données en utilisant l'objet Model Articles qui est chargé automatiquement via les -conventions de nommage. Elle utilise ensuite la méthode ``set()`` pour passer les -articles récupérés au Template (que nous créerons par la suite). CakePHP va -automatiquement rendre le Template une fois que notre action de Controller sera -entièrement exécutée. - -Création du Template de liste des Articles -========================================== - -Maintenant que notre controller récupère les données depuis le model et qu'il -prépare le contexte pour la view, créons le template pour notre action index. - -Les templates de view de CakePHP sont des morceaux de PHP qui sont insérés dans -le layout de votre application. Bien que nous créerons du HTML ici, les Views -peuvent générer du JSON, du CSV ou même des fichiers binaires comme des PDFs. - -Un layout est le code de présentation qui englobe la view d'une action. Les fichiers -de layout contiennent les éléments communs au site comme les headers, les footers et les -éléments de navigation. Votre application peut très bien avoir plusieurs layouts et -vous pouvez passer de l'un à l'autre. Mais pour le moment, utilisons seulement le -layout par défaut. - -Les fichiers de template de CakePHP sont stockés dans **templates** et dans -un dossier au nom du controller auquel ils sont attachés. Nous devons donc -créer un dossier nommé 'Articles' dans notre cas. Ajoutez le code suivant -dans ce fichier: - -.. code-block:: php - - - -

      Articles

      - - - - - - - - - - - - - - -
      TitreCréé le
      - Html->link($article->title, ['action' => 'view', $article->slug]) ?> - - created->format(DATE_RFC850) ?> -
      - -Dans la précédente section, nous avons assigné la variable 'articles' à la view en -utilisant la méthode ``set()``. Les variables passées à la view sont disponibles dans -les templates de view comme des variables locales, comme nous l'avons fait ci-dessus. - -Vous avez peut-être remarqué que nous utilisons un objet appelé ``$this->Html``. -C'est une instance du :doc:`HtmlHelper `. CakePHP inclut -plusieurs helpers de view qui peuvent créer des liens, des -formulaires et des éléments de paginations. Vous pouvez en apprendre -plus à propos des :doc:`/views/helpers` dans le chapitre de la documentation qui -leur est consacré, mais le plus important ici est la méthode ``link()``, qui générera -un lien HTML avec le texte fourni (le premier paramètre) et l'URL (le second paramètre). - -Quand vous spécifiez des URLs dans CakePHP, il est recommandé d'utiliser des -tableaux ou des :ref:`routes nommées`. Ces syntaxes vous permettent -de bénéficier du reverse routing fourni par CakePHP. - -A partir de maintenant, si vous accédez à **http://localhost:8765/articles/index**, -vous devriez voir votre view qui liste les articles avec leur titre et leur lien. - -Création de l'action View -========================= - -Si vous cliquez sur le lien d'un article dans la page qui liste nos articles, -vous tombez sur une page d'erreur vous indiquant que l'action n'a pas été implémentée. -Vous pouvez corriger cette erreur en créant l'action manquante correspondante:: - - // Ajouter au fichier existant src/Controller/ArticlesController.php - - public function view($slug = null) - { - $article = $this->Articles->findBySlug($slug)->firstOrFail(); - $this->set(compact('article')); - } - -Bien que cette action soit simple, nous avons utilisé quelques-unes des fonctionnalités -de CakePHP. Nous commençons par utiliser la méthode ``findBySlug()`` qui est un -:ref:`finder dynamique `. Cette méthode nous permet de créer -une requête basique qui permet de récupérer des articles par un "slug" donné. -Nous utilisons ensuite la méthode ``firstOrFail()`` qui nous permet de récupérer -le premier enregistrement ou lancera une ``NotFoundException`` si aucun article -correspondant n'est trouvé. - -Notre action attend un paramètre ``$slug``, mais d'où vient-il ? Si un utilisateur -requête ``/articles/view/first-post``, alors la valeur 'first-post' sera passée -à ``$slug`` par la couche de routing et de dispatching de CakePHP. Si nous rechargeons -notre navigateur, nous aurons une nouvelle erreur, nous indiquant qu'il manque un template -de View; corrigeons cela. - -Création du Template View -========================= - -Créons le template de view pour notre action "view" dans -**templates/Articles/view.php**. - -.. code-block:: php - - - -

      title) ?>

      -

      body) ?>

      -

      Créé le : created->format(DATE_RFC850) ?>

      -

      Html->link('Modifier', ['action' => 'edit', $article->slug]) ?>

      - -Vous pouvez vérifier que tout fonctionne en essayant de cliquer sur un lien de -``/articles/index`` ou en vous rendant manuellement sur une URL de la forme -``/articles/view/first-post``. - -Ajouter des articles -==================== - -Maintenant que les views de lecture ont été créées, il est temps de rendre possible -la création d'articles. Commencez par créer une action ``add()`` dans le -``ArticlesController``. Notre controller doit maintenant ressembler à ceci:: - - // src/Controller/ArticlesController.php - - namespace App\Controller; - - use App\Controller\AppController; - - class ArticlesController extends AppController - { - - public function initialize(): void - { - parent::initialize(); - - $this->loadComponent('Paginator'); - $this->loadComponent('Flash'); // Inclusion du FlashComponent - } - - public function index() - { - $articles = $this->Paginator->paginate($this->Articles->find()); - $this->set(compact('articles')); - } - - public function view($slug) - { - $article = $this->Articles->findBySlug($slug)->firstOrFail(); - $this->set(compact('article')); - } - - public function add() - { - $article = $this->Articles->newEmptyEntity(); - if ($this->request->is('post')) { - $article = $this->Articles->patchEntity($article, $this->request->getData()); - - // L'écriture de 'user_id' en dur est temporaire et - // sera supprimée quand nous aurons mis en place l'authentification. - $article->user_id = 1; - - if ($this->Articles->save($article)) { - $this->Flash->success(__('Votre article a été sauvegardé.')); - - return $this->redirect(['action' => 'index']); - } - $this->Flash->error(__('Impossible d\'ajouter votre article.')); - } - $this->set('article', $article); - } - } - -.. note:: - - Vous devez inclure le :doc:`/controllers/components/flash` dans tous les controllers - où vous avez besoin de l'utiliser. Il est souvent conseillé de le charger - directement dans le ``AppController``. - -Voici ce que l'action ``add()`` fait: - -* Si la méthode HTTP de la requête est un POST, cela tentera de sauvegarder les données - en utilisant le model Articles. -* Si pour une quelconque raison la sauvegarde ne se fait pas, cela rendra juste la view. - Cela nous donne ainsi une chance de montrer les erreurs de validation ou d'autres - messages à l'utilisateur. - -Toutes les requêtes de CakePHP incluent un objet request qui est accessible via -``$this->request``. L'objet request contient des informations à propos de la -requête qui vient d'être reçue. Nous utilisons la méthode -:php:meth:`Cake\\Http\\ServerRequest::is()` pour vérifier que la requête possède -bien le verbe HTTP POST. - -Les données passées en POST sont disponibles dans ``$this->request->getData()``. -Vous pouvez utiliser les fonctions :php:func:`pr()` ou :php:func:`debug()` pour -afficher les données si vous voulez voir à quoi elles ressemblent. Pour sauvegarder -les données, nous devons tout d'abord "marshaller" les données du POST en une -Entity Article. L'Entity sera ensuite persistée en utilisant la classe ArticlesTable -que nous avons créée plus tôt. - -Après la sauvegarde de notre article, nous utilisons la méthode ``success()`` du -FlashComponent pour définir le message en Session. La méthode ``success`` est -fournie via `les méthodes magiques de PHP -`_. -Les messages Flash seront affichés sur la page suivante après redirection. Dans -notre layout, nous avons ``Flash->render() ?>`` qui affichera un message -Flash et le supprimera du stockage dans la session. Enfin, après la sauvegarde, nous -utilisons :php:meth:`Cake\\Controller\\Controller::redirect` pour renvoyer -l'utilisateur à la liste des articles. Le paramètre ``['action' => 'index']`` -correspond à l'URL ``/articles``, c'est-à-dire l'action index du ``ArticlesController``. -Vous pouvez vous référer à la méthode :php:func:`Cake\\Routing\\Router::url()` dans -la `documentation API `_ pour voir les formats dans lesquels -vous pouvez spécifier une URL. - -Création du Template Add -======================== - -Voici le code de notre template de la view "add": - -.. code-block:: php - - - -

      Ajouter un article

      - Form->create($article); - // Hard code the user for now. - echo $this->Form->control('user_id', ['type' => 'hidden', 'value' => 1]); - echo $this->Form->control('title'); - echo $this->Form->control('body', ['rows' => '3']); - echo $this->Form->button(__('Sauvegarder l\'article')); - echo $this->Form->end(); - ?> - -Nous utilisons le FormHelper pour générer l'ouverture du formulaire HTML. -Voici le HTML que ``$this->Form->create()`` génère: - -.. code-block:: html - - - -Puisque nous appelons ``create()`` sans passer d'option URL, le ``FormHelper`` -va partir du principe que le formulaire doit être soumis sur l'action courante. - -La méthode ``$this->Form->control()`` est utilisée pour créer un élément de -formulaire du même nom. Le premier paramètre indique à CakePHP à quel champ -il correspond et le second paramètre vous permet de définir un très grand nombre -d'options - dans notre cas, le nombre de lignes (rows) pour le textarea. Il y a -un peu d'instrospection et de conventions utilisées ici. La méthode ``control()`` -affichera des éléments de formulaire différents en fonction du champ du model -spécifié et utilisera une inflection automatique pour définir le label associé. -Vous pouvez personnaliser le label, les inputs ou tout autre aspect du formulaire -en utilisant les options. La méthode ``$this->Form->end()`` ferme le formulaire. - -Retournons à notre template **templates/Articles/index.php** pour ajouter -un lien "Ajouter un article". Avant le ````, ajoutons la ligne -suivante:: - - Html->link('Ajouter un article', ['action' => 'add']) ?> - -Ajout de la génération de slug -============================== - -Si nous sauvons un article tout de suite, la sauvegarde échouerait car nous ne -créons pas l'attribut "slug" et la colonne correspondante est définie comme -``NOT NULL``. Un slug est généralement une version "URL compatible" du titre -d'un article. Nous pouvons utiliser le :ref:`callback beforeSave() ` -de l'ORM pour créer notre slug:: - - isNew() && !$entity->slug) { - $sluggedTitle = Text::slug($entity->title); - // On ne garde que le nombre de caractère correspondant à la longueur - // maximum définie dans notre schéma - $entity->slug = substr($sluggedTitle, 0, 191); - } - } - -Ce code est simple et ne prend pas en compte les potentiels doublons de slug. -Mais nous nous occuperons de ceci plus tard. - -Ajout de l'action Edit -====================== - -Notre application peut maintenant sauvegarder des articles, mais nous ne pouvons -pas modifier les articles existants. Rectifions cela maintenant. Ajoutez l'action suivante -dans votre ``ArticlesController``:: - - // dans src/Controller/ArticlesController.php - - // Ajouter la méthode suivante. - - public function edit($slug) - { - $article = $this->Articles - ->findBySlug($slug) - ->firstOrFail(); - - if ($this->request->is(['post', 'put'])) { - $this->Articles->patchEntity($article, $this->request->getData()); - if ($this->Articles->save($article)) { - $this->Flash->success(__('Votre article a été mis à jour.')); - - return $this->redirect(['action' => 'index']); - } - $this->Flash->error(__('Impossible de mettre à jour l\'article.')); - } - - $this->set('article', $article); - } - -Cette action va d'abord s'assurer que l'utilisateur essaie d'accéder à un -enregistrement existant. Si vous n'avez pas passé de paramètre ``$slug`` ou que -l'article n'existe pas, une ``NotFoundException`` sera lancée et le ErrorHandler -de CakePHP rendra la page d'erreur appropriée. - -Ensuite l'action va vérifier si la requête est une requête POST ou PUT. Si c'est le cas, -nous utiliserons alors les données du POST/PUT pour mettre à jour l'entity de l'article -en utilisant la méthode ``patchEntity()``. Enfin, nous appelons la méthode ``save()``, -nous définissons un message Flash approprié et soit nous redirigeons, soit nous affichons -les erreurs de validation en fonction du résultat de l'opération de sauvegarde. - -Création du Template Edit -========================= - -Le template edit devra ressembler à ceci: - -.. code-block:: php - - - -

      Modifier un article

      - Form->create($article); - echo $this->Form->control('user_id', ['type' => 'hidden']); - echo $this->Form->control('title'); - echo $this->Form->control('body', ['rows' => '3']); - echo $this->Form->button(__('Sauvegarder l\'article')); - echo $this->Form->end(); - ?> - -Ce template affiche le formulaire de modification (avec les valeurs déjà remplies), -ainsi que les messages d'erreurs de validation nécessaires. - -Vous pouvez maintenant mettre à jour notre view index avec les liens pour modifier -les articles: - -.. code-block:: php - - - -

      Articles

      -

      Html->link("Ajouter un article", ['action' => 'add']) ?>

      -
      - - - - - - - - - - - - - - - - -
      TitreCréé leAction
      - Html->link($article->title, ['action' => 'view', $article->slug]) ?> - - created->format(DATE_RFC850) ?> - - Html->link('Modifier', ['action' => 'edit', $article->slug]) ?> -
      - -Mise à jour des règles de validation pour les Articles -====================================================== - -Jusqu'à maintenant, nos Articles n'avaient aucune validation de données. Occupons-nous -de ça en utilisant un :ref:`validator `:: - - // src/Model/Table/ArticlesTable.php - - // Ajouter ce "use" juste sous la déclaration du namespace pour importer - // la classe Validator - use Cake\Validation\Validator; - - // Ajouter la méthode suivante. - public function validationDefault(Validator $validator): Validator - { - $validator - ->notEmptyString('title') - ->minLength('title', 10) - ->maxLength('title', 255) - - ->notEmptyString('body') - ->minLength('body', 10); - - return $validator; - } - -La méthode ``validationDefault()`` indique à CakePHP comment valider les données -quand la méthode ``save()`` est appelée. Ici, il est spécifié que les champs title -et body ne peuvent pas être vides et qu'ils ont aussi des contraintes sur la longueur. - -Le moteur de validation de CakePHP est à la fois puissant et flexible. Il vous fournit -un jeu de règles sur des validations communes comme les adresses emails, les adresses IP, -etc. mais aussi la flexibilité d'ajouter vos propres règles de validation. Pour plus -d'informations, rendez-vous dans la section :doc:`/core-libraries/validation` de -la documentation. - -Maintenant que nos règles de validation sont en place, utilisons l'application -et essayons d'ajouter un article avec un title ou un body vide pour voir ce qu'il -se passe. Puisque nous avons utiliser la méthode :php:meth:`Cake\\View\\Helper\\FormHelper::control()` -du FormHelper pour créer les éléments de formulaire, nos messages d'erreurs de -validation seront affichés automatiquement. - -Ajout de l'Action de Suppression -================================ - -Donnons maintenant la possibilité à nos utilisateurs de supprimer des articles. -Commencez par créer une action ``delete()`` dans ``ArticlesController``:: - - // src/Controller/ArticlesController.php - - // Ajouter la méthode suivante. - - public function delete($slug) - { - $this->request->allowMethod(['post', 'delete']); - - $article = $this->Articles->findBySlug($slug)->firstOrFail(); - if ($this->Articles->delete($article)) { - $this->Flash->success(__('L\'article {0} a été supprimé.', $article->title)); - - return $this->redirect(['action' => 'index']); - } - } - -Ce code va supprimer l'article ayant le slug ``$slug`` et utilisera la méthode -``$this->Flash->success()`` pour afficher un message de confirmation à l'utilisateur -après l'avoir redirigé sur ``/articles``. Si l'utilisateur essaie d'aller supprimer -un article avec une requête GET, la méthode ``allowMethod()`` lancera une exception. -Les exceptions non capturées sont récupérées par le gestionnaire d'exception de CakePHP -qui affichera une belle page d'erreur. Il existe plusieurs :doc:`Exceptions ` -intégrées qui peuvent être utilisées pour remonter les différentes erreurs HTTP -que votre application aurait besoin de générer. - -.. warning:: - - Permettre de supprimer des données via des requêtes GET est très dangereux, car - il est possible que des crawlers suppriment accidentellement du contenu. C'est - pourquoi nous utilisons la méthode ``allowMethod()`` dans notre controller. - -Puisque nous exécutons seulement de la logique et redirigeons directement sur une -autre action, cette action n'a pas de template. Vous devez ensuite mettre à jour -votre template index pour ajouter les liens qui permettront de supprimer les -articles: - -.. code-block:: php - - - -

      Articles

      -

      Html->link("Add Article", ['action' => 'add']) ?>

      - - - - - - - - - - - - - - - - - -
      TitreCréé leAction
      - Html->link($article->title, ['action' => 'view', $article->slug]) ?> - - created->format(DATE_RFC850) ?> - - Html->link('Modifier', ['action' => 'edit', $article->slug]) ?> - Form->postLink( - 'Supprimer', - ['action' => 'delete', $article->slug], - ['confirm' => 'Êtes-vous sûr ?']) - ?> -
      - -Utiliser :php:meth:`~Cake\\View\\Helper\\FormHelper::postLink()` va créer un lien -qui utilisera du JavaScript pour faire une requête POST et supprimer notre article. - -.. note:: - - Ce code de view utilise également le ``FormHelper`` pour afficher à l'utilisateur - une boîte de dialogue de confirmation en JavaScript avant la suppression - effective de l'article. - -Maintenant que nous avons un minimum de gestion sur nos articles, il est temps -de créer des actions basiques pour nos tables :doc:`Tags et Users `. diff --git a/fr/tutorials-and-examples/cms/authentication.rst b/fr/tutorials-and-examples/cms/authentication.rst deleted file mode 100644 index a2054e00b4..0000000000 --- a/fr/tutorials-and-examples/cms/authentication.rst +++ /dev/null @@ -1,320 +0,0 @@ -Tutoriel CMS - Authentification -############################### - -Maintenant que nous avons des utilisateurs dans notre CMS, nous devons leur donner -la possibilité de se connecter en utilisant le plugin -`cakephp/authentication `__.Nous commencerons -par nous assurer que les mots de passe sont stockés en toute sécurité dans -notre base de données. Ensuite, nous allons fournir une connexion et une déconnexion en -état de marche, et permettre aux nouveaux utilisateurs de s'inscrire. - -Installation du Plugin d'Authentication -======================================= - -Utilisez composer pour installer le Plugin d'authentification: - -.. code-block:: console - - composer require cakephp/authentication:^2.0 - - -Ajout du Hash du Mot de Passe ------------------------------ - -Vous devez avoir créé le ``Controller``, ``Table``, ``Entity`` et -les templates pour la table ``utilisateurs`` de votre base de données. Vous pouvez -le faire manuellement comme vous l'avez fait auparavant pour ArticlesController, ou -vous pouvez utiliser le Shell bake pour générer les classes pour vous en tapant: - -.. code-block:: console - - bin/cake bake all users - -Si vous créez ou mettez à jour un utilisateur, vous remarquerez que les mots de -passe sont stockés en clair, ce qui est évidemment très mauvais en terme de -sécurité, règlons cela. - -Corriger ce point nous permet de parler un peu plus de la couche model de CakePHP. -Dans CakePHP, nous séparons les méthodes qui s'occupent des collections d'objets -et d'un seul objet en différentes classes. Les méthodes qui s'occupent de -collections d'entity sont dans les classes ``Table`` tandis que les fonctionnalités -liées à un seul enregistrement sont mises dans les classes ``Entity``. - -Par exemple, hasher un mot de passe se fait enregistrement par enregistrement, -c'est pourquoi nous allons implémenter ce comportement dans l'objet Entity. -Puisque nous voulons hasher le mot de passe à chaque fois qu'il est défini, nous allons -utiliser une méthode mutator/setter. Par convention, CakePHP appellera les méthodes -de setter chaque fois qu'une propriété se voit affecter une valeur dans une entity. -Ajoutons un setter pour le mot de passe. Dans **src/Model/Entity/User.php**, ajoutez -le code suivant:: - - 0) { - return (new DefaultPasswordHasher())->hash($password); - } - return null; - } - } - -Maintenant, rendez-vous sur **http://localhost:8765/users** pour voir une liste -des utilisateurs existants. N'oubliez pas que votre serveur local doit fonctionner. -Démarrez un serveur PHP autonome utilisant ``bin/cake server``. - -Vous pouvez modifier l'utilisateur par défaut qui a été -créé pendant le chapitre :doc:`Installation ` du tutoriel. Si vous -changez le mot de passe de l'utilisateur, vous devriez voir une version hashée du -mot de passe à la place de la valeur par défaut sur l'action index ou view. CakePHP -hashe les mots de passe, par défaut, avec `bcrypt -`_. Nous recommandons -bcrypt pour toutes les nouvelles applications afin de garentir à votre application -un niveau de sécurité élevé. C'est l' -`algorithme de hachage de mot de passe recommandé pour PHP `_. - -.. note:: - - Créez maintenant un mot de passe haché pour au moins un des comptes utilisateurs! - Il sera nécessaire dans les prochaines étapes. - Après avoir mis à jour le mot de passe, vous verrez une longue chaîne stockée dans la colonne du mot de passe. - Notez que bcrypt générera un hachage différent même pour le même mot de passe enregistré deux fois. - -Ajouter la Connexion -==================== - -Il est maintenant temps de configurer le Plugin d'authentification. -Le plugin gérera le processus d'authentification en utilisant 3 classes différentes: - -* ``Application`` utilisera le middleware d'authentification et fournira un - AuthenticationService contenant toute la configuration que nous voulons définir, comment - nous allons vérifier les informations d'identification, et où les trouver. -* ``AuthenticationService`` sera une classe utilitaire pour vous permettre de configurer le - processus d'authentification. -* ``AuthenticationMiddleware`` sera exécuté dans le cadre de la file d'attente du middleware, - c'est à dire avant que vos contrôleurs ne soient traités par le framework, et collectera les - informations d'identification et les traitera pour vérifier si l'utilisateur est authentifié. - -Si vous vous en souvenez, par le passé nous utilisions :doc:`AuthComponent ` -afin de gérer toutes ces étapes. A présent, la logique est divisée en classes spécifiques et -le processus d'authentification se déroule avant votre couche de contrôleur. Il vérifie d'abord si l'utilisateur -est authentifié (en fonction de la configuration que vous avez fournie) et injecte l'utilisateur ainsi que le -résultat de l'authentification dans la requête afin que vous puissiez les utiliser ultérieurement. - -Dans **src/Application.php**, ajoutez les imports suivants:: - - // Dans src/Application.php, ajoutez les imports suivants - use Authentication\AuthenticationService; - use Authentication\AuthenticationServiceInterface; - use Authentication\AuthenticationServiceProviderInterface; - use Authentication\Middleware\AuthenticationMiddleware; - use Cake\Routing\Router; - use Psr\Http\Message\ServerRequestInterface; - -Ensuite, implémentez l'interface d'authentification pour votre classe ``Application```:: - - // dans src/Application.php - class Application extends BaseApplication - implements AuthenticationServiceProviderInterface - { - -Puis ajoutez le code suivant:: - - // src/Application.php - public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue - { - $middlewareQueue - // ... other middleware added before - ->add(new RoutingMiddleware($this)) - // add Authentication after RoutingMiddleware - ->add(new AuthenticationMiddleware($this)); - - return $middlewareQueue; - } - - public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface - { - $authenticationService = new AuthenticationService([ - 'unauthenticatedRedirect' => Router::url('/users/login'), - 'queryParam' => 'redirect', - ]); - - // Charge les identifiants et s'assure que nous vérifions les champs e-mail et mot de passe - $authenticationService->loadIdentifier('Authentication.Password', [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password', - ] - ]); - - // Charge les authenticators, nous voulons celui de session en premier - $authenticationService->loadAuthenticator('Authentication.Session'); - // Configure la vérification des données du formulaire pour choisir l'email et le mot de passe - $authenticationService->loadAuthenticator('Authentication.Form', [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password', - ], - 'loginUrl' => Router::url('/users/login'), - ]); - - return $authenticationService; - } - - Ajoutez le code suivant dans votre classe ``AppController``:: - - // src/Controller/AppController.php - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('RequestHandler'); - $this->loadComponent('Flash'); - - // Ajoutez cette ligne pour vérifier le résultat de l'authentification et verrouiller votre site - $this->loadComponent('Authentication.Authentication'); - -Désormais, à chaque demande, le ``AuthenticationMiddleware`` inspectera -la session contenue dans la requête afin d'y rechercher un utilisateur authentifié. -Si nous chargeons la page ``/users/login``, il inspectera également les données de formulaire -soumises (le cas échéant) pour extraire les informations d'identification. Par défaut, les informations -d'identification seront extraites des champs ``username`` and ``password`` dans les données de la demande. -Le résultat de l'authentification sera injecté dans un attribut de requête nommé -``authentification``. Vous pouvez consulter le résultat à tout moment en utilisant -``$this->request->getAttribute('authentication')`` à partir des actions de votre contrôleur. -Toutes vos pages seront restreintes car le ``AuthenticationComponent`` vérifie le -résultat à chaque demande. Lorsqu'il ne parvient pas à trouver un utilisateur authentifié, il redirige -l'utilisateur sur la page ``/users/login``. -Notez qu'à ce stade, le site ne fonctionnera pas car nous n'avons pas encore de page de connexion. -Si vous visitez votre site, vous obtiendrez une boucle de redirection infinie. Alors, réglons ça ! - -Dans votre ``UsersController``, ajoutez le code suivant:: - - public function beforeFilter(\Cake\Event\EventInterface $event) - { - parent::beforeFilter($event); - // Configurez l'action de connexion pour ne pas exiger d'authentification, - // évitant ainsi le problème de la boucle de redirection infinie - $this->Authentication->addUnauthenticatedActions(['login']); - } - - public function login() - { - $this->request->allowMethod(['get', 'post']); - $result = $this->Authentication->getResult(); - // indépendamment de POST ou GET, rediriger si l'utilisateur est connecté - if ($result && $result->isValid()) { - // rediriger vers /articles après la connexion réussie - $redirect = $this->request->getQuery('redirect', [ - 'controller' => 'Articles', - 'action' => 'index', - ]); - - return $this->redirect($redirect); - } - // afficher une erreur si l'utilisateur a soumis un formulaire - // et que l'authentification a échoué - if ($this->request->is('post') && !$result->isValid()) { - $this->Flash->error(__('Votre identifiant ou votre mot de passe est incorrect.')); - } - } - -Ajoutez la logique du template pour votre action de connexion:: - - -
      - Flash->render() ?> -

      Connexion

      - Form->create() ?> -
      - - Form->control('email', ['required' => true]) ?> - Form->control('password', ['required' => true]) ?> -
      - Form->submit(__('Login')); ?> - Form->end() ?> - - Html->link("Ajouter un utilisateur", ['action' => 'add']) ?> -
      - -Maintenant, la page de connexion nous permettra de nous connecter correctement à l'application. -Testez-le en affichant n'importe quelle page de votre site. Après avoir été redirigé -à la page ``/users/login``, saisissez l'email et le mot de passe -choisis lors de la création de votre utilisateur. Vous devriez être redirigé -avec succès après la connexion. - -Nous devons ajouter quelques détails supplémentaires pour configurer notre application. -Nous voulons que toutes les pages ``view`` and ``index`` soient accessibles sans connexion, -nous allons donc ajouter cette configuration spécifique dans AppController :: - - // dans src/Controller/AppController.php - public function beforeFilter(\Cake\Event\EventInterface $event) - { - parent::beforeFilter($event); - // pour tous les contrôleurs de notre application, rendre les actions - // index et view publiques, en ignorant la vérification d'authentification - $this->Authentication->addUnauthenticatedActions(['index', 'view']); - } - -.. note:: - - Si aucun de vos utilisateurs ne possède de mot de passe hashé, commentez le bloc - ``$this->loadComponent('Authentication.Authentication')`` dans votre - AppController ainsi que toutes les autres lignes dans lesquelles le - composant Authenticationest est utilisé. Puis allez à ``/users/add`` - Après avoir sauvegardé le mot de passe pour l'utilisateur, décommentez les - lignes que vous venez tout juste de commenter. - -Essayez-le en visitant ``/articles/add`` avant de vous connecter! Puisque l'action -n'est pas autorisée, vous serez redirigé vers la page de connexion. Après vous être connecté -avec succès, CakePHP vous redirigera automatiquement vers ``/articles/add``. - -Ajout de la Déconnexion -======================= - -Ajoutez l'action de logout à la classe ``UsersController``:: - - // dans src/Controller/UsersController.php - public function logout() - { - $result = $this->Authentication->getResult(); - // indépendamment de POST ou GET, rediriger si l'utilisateur est connecté - if ($result && $result->isValid()) { - $this->Authentication->logout(); - - return $this->redirect(['controller' => 'Users', 'action' => 'login']); - } - } - -Vous pouvez maintenant visiter ``/users/logout`` pour vous déconnecter. -Vous devriez alors être envoyé à la page de connexion. - -Autoriser la Création de Compte -=============================== - -Si vous n'êtes pas connecté et essayez de visiter **/users/add**, vous serez -redirigé sur la page de connexion. Puisque nous voulons autoriser nos utilisateurs -à créer un compte sur notre application, ajoutez ceci à votre ``UsersController``:: - - // Ajoutez la méthode beforeFilter au UsersController - $this->Authentication->addUnauthenticatedActions(['login', 'add']); - -Le code ci-dessus indique à ``AuthenticationComponent`` que la méthode ``add()`` du -``UsersController`` peut être visitée *sans* être authentifié ou avoir besoin -d'autorisation. Vous pouvez prendre le temps de nettoyer **Users/add.php** -en retirant les liens qui n'ont plus de sens pour cette page ou passer à la section -suivante. Nous ne nous occuperons pas des actions d'édition, d'affichage ou de liste -spécifiques aux utilisateurs, mais c'est un exercice que vous -pouvez faire par vous-même. - -Maintenant que les utilisateurs peuvent se connecter, nous voulons limiter les utilisateurs -à modifier uniquement les articles qui ils ont été créés par: -doc: `application des politiques d'autorisation <./autorisation>`. diff --git a/fr/tutorials-and-examples/cms/authorization.rst b/fr/tutorials-and-examples/cms/authorization.rst deleted file mode 100644 index 7d2f0456ab..0000000000 --- a/fr/tutorials-and-examples/cms/authorization.rst +++ /dev/null @@ -1,253 +0,0 @@ -Tutoriel CMS - Autorisation -############################ - -Maintenant que nos utilisateurs peuvent se connecter à notre CMS, nous voulons appliquer des règles d'autorisation -pour nous assurer que chaque utilisateur ne puisse éditer que les messages dont il est l'auteur. Nous allons -utiliser le `plugin authorization `__ pour nous en assurer. - -Installer le plugin Authorization -================================= - -Utilisez composer pour installer le plugin Authorization: - -.. code-block:: console - - composer require "cakephp/authorization:^2.0" - -Chargez le plugin en ajoutant le code suivant à la méthode ``bootstrap()`` dans le fichier **src/Application.php**:: - - $this->addPlugin('Authorization'); - -Activer le plugin Authorization -=============================== - -Le plugin Authorization s'intègre dans votre application grâce à la couche middleware et -optionnellement par un composant dans vos contrôleurs pour faciliter les vérifications des droits. -Premièrement, attachons-nous au middleware. Dans **src/Application.php** ajoutez le code suivant -dans les import de la classe:: - - use Authorization\AuthorizationService; - use Authorization\AuthorizationServiceInterface; - use Authorization\AuthorizationServiceProviderInterface; - use Authorization\Middleware\AuthorizationMiddleware; - use Authorization\Policy\OrmResolver; - use Psr\Http\Message\ResponseInterface; - -Ajoutez ``AuthorizationProviderInterface`` aux interfaces déjà importées par votre application:: - - class Application extends BaseApplication - implements AuthenticationServiceProviderInterface, - AuthorizationServiceProviderInterface - -Enfin, ajoutez le code suivant à votre méthode ``middleware()``:: - - // Attention: Ajoutez Authorization **après** Authentication - $middlewareQueue->add(new AuthorizationMiddleware($this)); - -``AuthorizationMiddleware`` va appeler une méthode hook sur votre application qui va permettre -de définir le ``AuthorizationService`` à utiliser. Ajoutez la méthode suivante au -fichier **src/Application.php**:: - - public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface - { - $resolver = new OrmResolver(); - - return new AuthorizationService($resolver); - } - -L'OrmResolver permet au plugin Authorization de trouver les classes de stratégies (policies) pour les -entités et les requêtes de l'ORM. Des resolvers supplémentaires peuvent être utilisés pour -trouver des stratégies pour d'autres types de ressources. - -Maintenant, ajoutons ``AuthorizationComponent`` à ``AppController``. Dans -**src/Controller/AppController.php** ajoutez le code suivant à la méthode ``initialize()``:: - - $this->loadComponent('Authorization.Authorization'); - -Enfin, nous allons définir les actions add, login et logout comme ne nécessitant pas -d'autorisation en ajoutant le code suivante à **src/Controller/UsersController.php**:: - - // Dans les méthodes add, login, et logout - $this->Authorization->skipAuthorization(); - -La méthode ``skipAuthorization()`` doit être appelée dans chaque controller ayant des -actions accessibles à tous les utilisateurs, même ceux qui ne sont pas identifiés. - -Créons notre Première Stratégie -=============================== - -Le plugin Authorization représente les autorisation et les permissions par des classes Policy. -Ces classes contiennent la logique pour vérifier qu'une **identity** a la permission -de **faire une action** sur une **ressource**. Notre **identity** sera un utilisateur authentifié, -et nos **ressources** sont des entités de l'ORM ainsi que des requêtes sur la base de données. -Utilisons **bake** pour créer la base de notre première stratégie. - -.. code-block:: console - - bin/cake bake policy --type entity Article - -Cette commande va générer une classe de Police vide pour notre entity ``Article``. -Vous retrouverez le fichier généré dans **src/Policy/ArticlePolicy.php**. Maintenant, -modifiez la stratégie pour qu'elle ressemble à cela:: - - isAuthor($user, $article); - } - - public function canDelete(IdentityInterface $user, Article $article) - { - // Les utilisateurs authentfiés ne peuvent supprimer que leurs articles. - return $this->isAuthor($user, $article); - } - - protected function isAuthor(IdentityInterface $user, Article $article) - { - return $article->user_id === $user->getIdentifier(); - } - } - -Ici nous n'avons défini que quelques règles basiques, libre à vous d'utiliser des logiques plus -complexes. - -Utiliser Authorization dans ArticlesController -============================================== - -Maintenant que nos stratégies sont créées nous pouvons vérifier les autorisations -dans chaque action de notre controller. Si nous oublions de vérifier les autorisations -dans une action du controller, le plugin Authorization lèvera une exception. -Dans **src/Controller/ArticlesController.php**, ajoutez le code suivant aux méthodes -``add``, ``edit`` et ``delete``:: - - public function add() - { - $article = $this->Articles->newEmptyEntity(); - $this->Authorization->authorize($article); - // Le reste de la méthode.. - } - - public function edit($slug) - { - $article = $this->Articles - ->findBySlug($slug) - ->contain('Tags') // charge les Tags associés - ->firstOrFail(); - $this->Authorization->authorize($article); - // Le reste de la méthode.. - } - - public function delete($slug) - { - $this->request->allowMethod(['post', 'delete']); - - $article = $this->Articles->findBySlug($slug)->firstOrFail(); - $this->Authorization->authorize($article); - // Le reste de la méthode.. - } - -La méthode ``AuthorizationComponent::authorize()`` va utiliser le nom de l'action pour -retrouver la méthode de la stratégie à appeler. Si vous préférez définir vous-même la méthode -de la stratégie à utiliser vous devrez passer le nom de l'opération à ``authorize``:: - - $this->Authorization->authorize($article, 'update'); - -Maintenant, ajoutez le code suivant aux méthodes ``tags``, ``view``, et ``index`` de votre -``ArticlesController``:: - - // Les actions view, index et tags sont des méthodes accessibles - // à tous et ne nécessitent pas de vérifications. - $this->Authorization->skipAuthorization(); - -Amélioration des actions Add & Edit -=================================== - -Bien que nous ayons bloqué l'accès à l'édition, nous sommes toujours vulnérables -au changement de l'attribut ``user_id`` de l'article par l'utilisateur durant l'édition. -Nous allons remédier à cela. Commençons avec l'action ``add``. - -Lorsque nous créons des articles, nous voulons fixer le ``user_id`` comme étant -l'utilisateur actuellement authentifié. Remplacez l'action ``add`` par le code suivant:: - - // Dans src/Controller/ArticlesController.php - - public function add() - { - $article = $this->Articles->newEmptyEntity(); - $this->Authorization->authorize($article); - - if ($this->request->is('post')) { - $article = $this->Articles->patchEntity($article, $this->request->getData()); - - // Changement: Chercher le user_id de l'utilisateur authentifié. - $article->user_id = $this->request->getAttribute('identity')->getIdentifier(); - - if ($this->Articles->save($article)) { - $this->Flash->success(__('Votre article a été enregistré avec succès.')); - - return $this->redirect(['action' => 'index']); - } - $this->Flash->error(__('Impossible d\'ajouter votre article.')); - } - $tags = $this->Articles->Tags->find('list')->all(); - $this->set(compact('article', 'tags')); - } - -Ensuite nous allons modifier l'action ``edit``. Remplacez la méthode d'édition par ce qui suit:: - - // Dans src/Controller/ArticlesController.php - - public function edit($slug) - { - $article = $this->Articles - ->findBySlug($slug) - ->contain('Tags') // charge les Tags associés - ->firstOrFail(); - $this->Authorization->authorize($article); - - if ($this->request->is(['post', 'put'])) { - $this->Articles->patchEntity($article, $this->request->getData(), [ - // Ajout: Empêcher la modification de user_id. - 'accessibleFields' => ['user_id' => false] - ]); - if ($this->Articles->save($article)) { - $this->Flash->success(__('Votre article a été sauvegardé.')); - - return $this->redirect(['action' => 'index']); - } - $this->Flash->error(__('Impossible de mettre à jour votre article.')); - } - $tags = $this->Articles->Tags->find('list')->all(); - $this->set(compact('article', 'tags')); - } - -Ici nous définissons les propriétés qui peuvent être assignées en masse en -utilisant ``patchEntity()``. Voir la section :ref:`changing-accessible-fields` -pour plus d'informations. N'oubliez pas d'enlever le contrôle du ``user_id`` -dans **templates/Articles/edit.php**, nous n'en avons plus besoin. - -Conclusion -========== - -Nous avons construit une application CMS basique qui permet à des utilisateurs de -s'authentifier, d'écrire des articles, d'y ajouter des tags, de parcourir les -articles rédigés, et avons mis en place des contrôles pour nos articles. -Nous avons même ajouté des améliorations à l'interface en exploitant le -FormHelper et les capacités de l'ORM - -Merci d'avoir pris le temps d'explorer CakePHP. Nous vous proposons de continuer -votre apprentissage avec :doc:`/orm` ou de lire attentivement :doc:`/topics`. diff --git a/fr/tutorials-and-examples/cms/database.rst b/fr/tutorials-and-examples/cms/database.rst deleted file mode 100644 index afd3e834dc..0000000000 --- a/fr/tutorials-and-examples/cms/database.rst +++ /dev/null @@ -1,230 +0,0 @@ -Tutoriel CMS - Création de la base de données -############################################# - -Maintenant que CakePHP est installé, il est temps d'installer la base de données -pour notre application :abbr:`CMS (Content Management System)`. Si vous ne l'avez -pas encore fait, créez une base de données vide qui servira pour ce tutoriel, avec -le nom de votre choix (par exemple ``cake_cms``). -Si vous utilisez MySQL/MariaDB, vous pouvez exécuter le SQL suivant pour créer le -tables nécessaires: - -.. code-block:: SQL - - USE cake_cms; - - CREATE TABLE users ( - id INT AUTO_INCREMENT PRIMARY KEY, - email VARCHAR(255) NOT NULL, - password VARCHAR(255) NOT NULL, - created DATETIME, - modified DATETIME - ); - - CREATE TABLE articles ( - id INT AUTO_INCREMENT PRIMARY KEY, - user_id INT NOT NULL, - title VARCHAR(255) NOT NULL, - slug VARCHAR(191) NOT NULL, - body TEXT, - published BOOLEAN DEFAULT FALSE, - created DATETIME, - modified DATETIME, - UNIQUE KEY (slug), - FOREIGN KEY user_key (user_id) REFERENCES users(id) - ) CHARSET=utf8mb4; - - CREATE TABLE tags ( - id INT AUTO_INCREMENT PRIMARY KEY, - title VARCHAR(191), - created DATETIME, - modified DATETIME, - UNIQUE KEY (title) - ) CHARSET=utf8mb4; - - CREATE TABLE articles_tags ( - article_id INT NOT NULL, - tag_id INT NOT NULL, - PRIMARY KEY (article_id, tag_id), - FOREIGN KEY tag_key(tag_id) REFERENCES tags(id), - FOREIGN KEY article_key(article_id) REFERENCES articles(id) - ); - - INSERT INTO users (email, password, created, modified) - VALUES - ('cakephp@example.com', 'secret', NOW(), NOW()); - - INSERT INTO articles (user_id, title, slug, body, published, created, modified) - VALUES - (1, 'First Post', 'first-post', 'This is the first post.', 1, NOW(), NOW()); - -Si vous utilisez PostgreSQL, connectez-vous à la base de données ``cake_cms`` et exécutez le -code SQL suivant à la place: - -.. code-block:: SQL - - CREATE TABLE users ( - id SERIAL PRIMARY KEY, - email VARCHAR(255) NOT NULL, - password VARCHAR(255) NOT NULL, - created TIMESTAMP, - modified TIMESTAMP - ); - - CREATE TABLE articles ( - id SERIAL PRIMARY KEY, - user_id INT NOT NULL, - title VARCHAR(255) NOT NULL, - slug VARCHAR(191) NOT NULL, - body TEXT, - published BOOLEAN DEFAULT FALSE, - created TIMESTAMP, - modified TIMESTAMP, - UNIQUE (slug), - FOREIGN KEY (user_id) REFERENCES users(id) - ); - - CREATE TABLE tags ( - id SERIAL PRIMARY KEY, - title VARCHAR(191), - created TIMESTAMP, - modified TIMESTAMP, - UNIQUE (title) - ); - - CREATE TABLE articles_tags ( - article_id INT NOT NULL, - tag_id INT NOT NULL, - PRIMARY KEY (article_id, tag_id), - FOREIGN KEY (tag_id) REFERENCES tags(id), - FOREIGN KEY (article_id) REFERENCES articles(id) - ); - - INSERT INTO users (email, password, created, modified) - VALUES - ('cakephp@example.com', 'secret', NOW(), NOW()); - - INSERT INTO articles (user_id, title, slug, body, published, created, modified) - VALUES - (1, 'First Post', 'first-post', 'This is the first post.', TRUE, NOW(), NOW()); - -Vous avez peut-être remarqué que la table ``articles_tags`` utilise une clé primaire -composée. CakePHP supporte les clés primaires composées presque partout, -vous permettant d'avoir des shémas plus simples qui ne nécessitent pas de -colonnes ``id`` supplémentaires. - -Les noms de tables et de colonnes utilisés ne sont pas arbitraires. En utilisant les -:doc:`conventions de nommages ` de CakePHP, nous allons bénéficier -des avantages de CakePHP de manière plus efficace et allons éviter d'avoir trop de -configuration à effectuer. Bien que CakePHP soit assez flexible pour supporter presque -n'importe quel schéma de base de données, adhérer aux conventions va vous faire gagner -du temps. - -Configuration de la base de données -=================================== - -Ensuite, disons à CakePHP où est notre base de données et comment nous y connecter. -Remplacez les valeurs dans le tableau ``Datasources.default`` de votre fichier -**config/app.php** avec celle de votre installation de base de données. Un exemple -de configuration complétée ressemblera à ceci:: - - [ - 'default' => [ - 'className' => Connection::class, - // Remplacez Mysql par Postgres si vous utilisez PostgreSQL - 'driver' => Mysql::class, - 'persistent' => false, - 'host' => 'localhost', - 'username' => 'cakephp', - 'password' => 'AngelF00dC4k3~', - 'database' => 'cake_cms', - // Commentez la ligne ci-dessous si vous utilisez PostgreSQL - 'encoding' => 'utf8mb4', - 'timezone' => 'UTC', - 'cacheMetadata' => true, - ], - ], - // D'autres configurations en dessous - ]; - -Une fois que vous avez sauvegardé votre fichier **config/app.php**, vous devriez -voir que CakePHP est capable de se connecter à la base de données sur la page d'accueil -de votre projet. - -.. note:: - - Si vous avez **config/app_local.php** dans votre dossier d'application, vous devez - plutôt configurer votre connexion à la base de données dans ce fichier. - -Création du premier Model -========================= - -Les models font partie du coeur des applications CakePHP. Ils nous permettent -de lire et modifier les données, de construire des relations entre nos données, -de valider les données et d'appliquer les règles spécifiques à notre application. -Les models sont les fondations nécessaires pour construire nos actions de controllers -et nos templates. - -Les models de CakePHP sont composés d'objets ``Table`` et ``Entity``. Les objets -``Table`` nous permettent d'accéder aux collections d'entities stockées dans une -table spécifique. Ils sont stockés dans le dossier **src/Model/Table**. Le fichier -que nous allons créer sera sauvegardé dans **src/Model/Table/ArticlesTable.php**. -Le fichier devra contenir ceci:: - - addBehavior('Timestamp'); - } - } - -Nous y avons attaché le behavior :doc:`/orm/behaviors/timestamp` qui remplira -automatiquement les colonnes ``created`` et ``modified`` de notre table. En -nommant notre objet Table ``ArticlesTable``, CakePHP va utiliser les conventions -de nommages pour savoir que notre model va utiliser la table ``articles``. Toujours -en utilisant les conventions, il saura que la colonne ``id`` est notre clé primaire. - -.. note:: - - CakePHP créera dynamiquement un objet model s'il n'en trouve pas un qui - correspond dans le dossier **src/Model/Table**. Cela veut dire que si vous - faites une erreur lors du nommage du fichier (par exemple articlestable.php ou - ArticleTable.php), CakePHP ne reconnaitra pas votre configuration et utilisera - ce model généré à la place. - -Nous allons également créer une classe Entity pour nos Articles. Les Entities -représentent un enregistrement spécifique en base et donnent accès aux données -d'une ligne de notre base. Notre Entity sera sauvegardée dans **src/Model/Entity/Article.php**. -Le fichier devra ressembler à ceci:: - - true, - 'id' => false, - 'slug' => false, - ]; - } - -Notre entity est assez simple pour l'instant et nous y avons seulement défini la -propriété ``_accessible`` qui permet de contrôler quelles propriétés peuvent être -modifiées via :ref:`entities-mass-assignment`. - -Pour l'instant, nous ne pouvons pas faire grande chose avec notre model. Pour -intéragir avec notre model, nous allons ensuite créer nos premiers -:doc:`Controller et Template `. diff --git a/fr/tutorials-and-examples/cms/installation.rst b/fr/tutorials-and-examples/cms/installation.rst deleted file mode 100644 index 5f0d15549c..0000000000 --- a/fr/tutorials-and-examples/cms/installation.rst +++ /dev/null @@ -1,121 +0,0 @@ -Tutoriel d'un système de gestion de contenu -########################################### - -Ce tutoriel vous accompagnera dans la création d'une application de type -:abbr:`CMS (Content Management System)`. Pour commencer, nous installerons -CakePHP, créerons notre base de données et construirons un système simple -de gestion d'articles. - -Voici les pré-requis: - -#. Un serveur de base de données. Nous utiliserons MySQL dans ce tutoriel. Vous - avez besoin de connaître assez de SQL pour créer une base de données et exécuter - quelques requêtes SQL que nous fournirons dans ce tutoriel. CakePHP se chargera - de construire les requêtes nécessaires pour votre application. Puisque nous allons - utiliser MySQL, assurez-vous que ``pdo_mysql`` est bien activé dans PHP. -#. Les connaissances de base en PHP. - -Avant de commencer, assurez-vous que votre version de PHP est à jour: - -.. code-block:: console - - php -v - -Vous devez avoir au minimum PHP |minphpversion| installé (en CLI). Votre version -serveur de PHP doit au moins être aussi |minphpversion| et, dans l'idéal, devrait -également être la même que pour votre version en ligne de commande (CLI). - -Récupérer CakePHP -================= - -La manière la plus simple d'installer CakePHP est d'utiliser Composer. Composer -est une manière simple d'installer CakePHP via votre terminal. Premièrement, vous -devez télécharger et installer Composer si vous ne l'avez pas déjà fait. Si vous -avez cURL installé, exécutez la commande suivante: - -.. code-block:: console - - curl -s https://getcomposer.org/installer | php - -Ou vous pouvez télécharger ``composer.phar`` depuis le -`site de Composer `_. - -Ensuite, tapez la commande suivante dans votre terminal pour installer le squelette -d'application CakePHP dans le dossier **cms** du dossier courant: - -.. code-block:: console - - php composer.phar create-project --prefer-dist cakephp/app:5.* cms - -Si vous avez téléchargé et utilisé `l'Installer de Composer pour Windows -`_, tapez la commande suivante dans -votre terminal depuis le dossier d'installation (par exemple C:\\wamp\\www\\dev): - -.. code-block:: console - - composer self-update && composer create-project --prefer-dist cakephp/app:4.* cms - -Utiliser Composer a l'avantage d'exécuter automatiquement certaines tâches -importantes d'installation, comme définir les bonnes permissions sur les dossiers -et créer votre fichier **config/app.php**. - -Il existe d'autres moyens d'installer CakePHP. Si vous ne pouvez pas (ou ne -voulez pas) utiliser Composer, rendez-vous dans la section :doc:`/installation`. - -Quelque soit la manière de télécharger et installer CakePHP, une fois que la mise -en place est terminée, votre dossier d'installation devrait ressembler à ceci:: - - /cms - /bin - /config - /logs - /plugins - /resources - /src - /templates - /tests - /tmp - /vendor - /webroot - .editorconfig - .gitignore - .htaccess - .travis.yml - composer.json - index.php - phpunit.xml.dist - README.md - -C'est le bon moment pour en apprendre d'avantage sur le fonctionnement de la -structure des dossiers de CakePHP : rendez-vous dans la section :doc:`/intro/cakephp-folder-structure` -pour en savoir plus. - -Si vous vous perdez pendant ce tutoriel, vous pouvez voir le résultat final `on GitHub -`_. - -Vérifier l'installation -======================= - -Il est possible de vérifier que l'installation est terminée en vous rendant sur -la page d'accueil. Avant de faire ça, vous allez devoir lancer le serveur de -développement: - -.. code-block:: console - - cd /path/to/our/app - - bin/cake server - -.. note:: - - Pour Windows, la commande doit être ``bin\cake server`` (notez le backslash). - -Cela démarrera le serveur embarqué de PHP sur le port 8765. Ouvrez -**http://localhost:8765** dans votre navigateur pour voir la page d'accueil. -Tous les éléments de la liste devront être validés sauf le point indiquant si -CakePHP arrive à se connecter à la base de données. Si d'autres points ne sont -pas validés, vous avez peut-être besoin d'installer des extensions PHP supplémentaires -ou définir les bonnes permissions sur certains dossiers. - -Ensuite, nous allons créer notre :doc:`base de données et créer notre premier -model `. diff --git a/fr/tutorials-and-examples/cms/tags-and-users.rst b/fr/tutorials-and-examples/cms/tags-and-users.rst deleted file mode 100644 index ef1f5b45e3..0000000000 --- a/fr/tutorials-and-examples/cms/tags-and-users.rst +++ /dev/null @@ -1,523 +0,0 @@ -Tutoriel CMS - Tags et Users -############################ - -Maintenant que nous avons implémenté une gestion basique de la création d'articles, -il est temps de permettre à plusieurs auteurs de travailler sur notre CMS. Dans les -étapes précédentes, nous avons créé nos models, nos views et nos controllers à la -main. Cette fois, nous allons utiliser :doc:`/bake` pour créer la base de notre -code. Bake est un outil :abbr:`CLI (Command Line Interface)` de génération de -code qui se base sur les conventions de CakePHP pour créer des applications -:abbr:`CRUD (Create, Read, Update, Delete)` basique très rapidement. Nous allons -utiliser ``bake`` pour créer le code relatif à la gestion d'utilisateurs: - -.. code-block:: console - - cd /path/to/our/app - - bin/cake bake model users - bin/cake bake controller users - bin/cake bake template users - -Ces 3 commandes vont générer: - -* Les fichiers de Table, Entity, et Fixture. -* Le Controller -* Les templates CRUD. -* Les fichiers de Tests pour chaque classe générée. - -Bake va aussi utiliser les conventions CakePHP pour définir les associations -et les validations pour vos models. - -Ajouter un système de Tags aux Articles -======================================= - -Avec plusieurs utilisateurs capables d'accéder à notre petit CMS, il serait bien d'avoir, -pour notre application :abbr:`CMS`, un moyen de catégoriser notre contenu. Nous allons donc -utiliser des tags pour permettre aux utilisateurs d'ajouter des catégories et des labels à -leurs contenus. Une fois de plus, nous allons utiliser ``bake`` pour générer rapidement un -code de base: - -.. code-block:: console - - bin/cake bake all tags - -Une fois que le code de base est généré, créez quelques tags en vous rendant sur -la page **http://localhost:8765/tags/add**. - -Maintenant que nous avons une table Tags, nous pouvons créer une association entre -la table Articles et la table Tags. Nous pouvons le faire en ajoutant le code suivant -à la méthode ``initialize`` de ArticlesTable:: - - public function initialize(array $config): void - { - $this->addBehavior('Timestamp'); - $this->belongsToMany('Tags'); // Ajoutez cette ligne - } - -Cette association fonctionnera avec cette définition qui tient sur une seule ligne -car nous avons suivi les conventions de CakePHP lors de la création de nos tables. -Pour plus d'informations, rendez-vous dans la section :doc:`/orm/associations`. - -Mettre à jour la gestion des Articles pour permettre d'ajouter des Tags -======================================================================= - -Maintenant que notre application gère les tags, nous devons donner la possibilité -à nos utilisateurs d'ajouter les tags sur les articles. Premièrement, mettez à jour -l'action ``add`` pour qu'elle ressemble à ceci:: - - Articles->newEmptyEntity(); - if ($this->request->is('post')) { - $article = $this->Articles->patchEntity($article, $this->request->getData()); - - // L'écriture de 'user_id' en dur est temporaire et - // sera supprimée quand nous aurons mis en place l'authentification. - $article->user_id = 1; - - if ($this->Articles->save($article)) { - $this->Flash->success(__('Votre article a été sauvegardé.')); - - return $this->redirect(['action' => 'index']); - } - $this->Flash->error(__('Impossible de sauvegarder l\'article.')); - } - // Récupère une liste des tags. - $tags = $this->Articles->Tags->find('list'); - - // Passe les tags au context de la view - $this->set('tags', $tags); - - $this->set('article', $article); - } - - // Les autres actions - } - -Les lignes de code ajoutées chargent une liste des tags sous forme de tableau associatif -de la forme ``id => title``. Ce format nous permet de créer un nouvel input de tags dans -notre template. Ajoutez la ligne suivante dans le bloc PHP avec les autres appels à -``control()`` dans **templates/Articles/add.php**:: - - echo $this->Form->control('tags._ids', ['options' => $tags]); - -Cela rendra un select multiple qui utilisera la variable ``$tags`` pour générer -les options du select. Vous devriez maintenant créer quelques articles en leur -mettant des tags car dans la section suivante, nous allons ajouter la possibilité -de trouver des articles par leurs tags. - -Vous devriez également mettre à jour la méthode ``edit`` pour permettre l'ajout -et la modification de tags sur les articles existant. La méthode ``edit`` devrait -maintenant ressembler à ceci:: - - public function edit($slug) - { - $article = $this->Articles - ->findBySlug($slug) - ->contain('Tags') // charge les Tags associés - ->firstOrFail(); - if ($this->request->is(['post', 'put'])) { - $this->Articles->patchEntity($article, $this->request->getData()); - if ($this->Articles->save($article)) { - $this->Flash->success(__('Votre article a été modifié.')); - - return $this->redirect(['action' => 'index']); - } - $this->Flash->error(__('Impossible de mettre à jour votre article.')); - } - - // Récupère une liste des tags. - $tags = $this->Articles->Tags->find('list'); - - // Passe les tags au context de la view - $this->set('tags', $tags); - - $this->set('article', $article); - } - -Pensez à ajouter le nouveau select multiple qui permet de sélectionner les tags -comme nous l'avons fait dans le template **add.php** au template -**templates/Articles/edit.php**. - -Trouver des Articles via les Tags -================================= - -Une fois que les utilisateurs ont catégorisé leurs contenus, ils voudront probablement -retrouver ces contenus en fonction des tags utilisés. Pour développer ces fonctionnalités, -nous allons implémenter une nouvelle route, une nouvelle action de controller et une -fonction de finder pour chercher les articles par tags. - -Idéalement, nous voulons une URL qui ressemblera à -**http://localhost:8765/articles/tagged/funny/cat/gifs**. Cela nous permettra -de trouver tous les articles avec le tag 'funny', 'cat' ou 'gifs'. Nous avons tout -d'abord besoin d'ajouter une nouvelle route. Votre fichier **config/routes.php** -(avec les commentaires générés par bake supprimés) devra ressembler à:: - - scope('/', function (RouteBuilder $builder) { - $builder->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']); - $builder->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']); - - // Ceci est la route à ajouter pour notre nouvelle action. - // Le `*` à la fin permet de préciser à CakePHP que cette action - // a des paramètres qui lui seront passés - $builder->scope('/articles', function (RouteBuilder $builder) { - $builder->connect('/tagged/*', ['controller' => 'Articles', 'action' => 'tags']); - }); - - $builder->fallbacks(); - }); - -Le code ci-dessus définit une nouvelle 'route' qui permet de connecter le chemin -URL **/articles/tagged/** à ``ArticlesController::tags()``. En définissant des routes, -vous pouvez isoler le format de vos URLs de la manière dont elles sont implémentées. -Si nous venions à visiter **http://localhost:8765/articles/tagged**, nous verrions -une page d'erreur de CakePHP vous indiquant que l'action du controller n'existe -pas. Créons de ce pas cette nouvelle méthode. Dans **src/Controller/ArticlesController.php**, -ajoutez ce qui suit:: - - public function tags() - { - // La clé 'pass' est fournie par CakePHP et contient tous les - // segments d'URL passés dans la requête - $tags = $this->request->getParam('pass'); - - // Utilisation de ArticlesTable pour trouver les articles taggés - $articles = $this->Articles->find('tagged', [ - 'tags' => $tags - ]) - ->all(); - - // Passage des variables dans le contexte de la view du template - $this->set([ - 'articles' => $articles, - 'tags' => $tags - ]); - } - -Pour accéder aux autres parties des données de la requête, consultez la section -:ref:`cake-request`. - -Puisque les arguments passés sont aussi fournis comme paramètres de la méthode -d'action, nous pourrions également écrire l'action en utilisant les arguments -variadic de PHP:: - - public function tags(...$tags) - { - // Utilisation de ArticlesTable pour trouver les articles taggés - $articles = $this->Articles->find('tagged', [ - 'tags' => $tags - ]) - ->all(); - - // Passage des variable dans le contexte de la view du template - $this->set([ - 'articles' => $articles, - 'tags' => $tags - ]); - } - -Création de la Méthode Finder ------------------------------ - -Dans CakePHP, nous aimons garder nos actions de controller le plus minimaliste -possible et mettons la majorité de la logique de notre application dans la couche -model. Si vous veniez à visiter l'URL **/articles/tagged**, vous verriez une erreur -vous indiquant que la méthode ``findTagged()`` n'existe pas. Dans -**src/Model/Table/ArticlesTable.php**, ajoutez le code suivant:: - - // Ajouter ce 'use' juste sous la déclaration du namespace pour importer - // la classe Query - use Cake\ORM\Query; - - // L'argument $query est une instance du Query builder. - // Le tableau $options va contenir l'option 'tags' que nous avons passé - // à find('tagged') dans notre action de controller. - public function findTagged(Query $query, array $options) - { - $columns = [ - 'Articles.id', 'Articles.user_id', 'Articles.title', - 'Articles.body', 'Articles.published', 'Articles.created', - 'Articles.slug', - ]; - - $query = $query - ->select($columns) - ->distinct($columns); - - if (empty($options['tags'])) { - // si aucun tag n'est fourni, trouvons les articles qui n'ont pas de tags - $query->leftJoinWith('Tags') - ->where(['Tags.title IS' => null]); - } else { - // Trouvons les articles qui ont au moins un des tags fourni - $query->innerJoinWith('Tags') - ->where(['Tags.title IN' => $options['tags']]); - } - - return $query->group(['Articles.id']); - } - -Nous venons d'implémenter :ref:`un custom finder `. Ce concept -très pratique de CakePHP vous permet de définir des requêtes réutilisables. Les -méthodes finder récupèrent toujours en paramètres un objet :doc:`/orm/query-builder` -et un tableau d'options. Les finders peuvent manipuler la requête et ajouter -n'importe quels condition ou critère. Une fois la logique terminée, le finder doit -retourner une instance modifiée de l'objet query. Dans notre finder, nous utilisons -les méthodes ``distinct()`` et ``leftJoin()`` qui nous permettent de trouver les articles -différents qui ont les tags correspondant. - -Création de la View -------------------- - -Si vous visitez à nouveau **/articles/tagged**, CakePHP vous affichera une nouvelle -erreur qui vous fait savoir qu'il manque le fichier de view. A présent, créons le fichier -de vue pour notre action ``tags()`` action:: - - -

      - Articles avec les tags - Text->toList(h($tags), 'or') ?> -

      - -
      - -
      - -

      Html->link( - $article->title, - ['controller' => 'Articles', 'action' => 'view', $article->slug] - ) ?>

      - created) ?> -
      - -
      - -Dans le code ci-dessus, nous utilisons les Helpers :doc:`/views/helpers/html` et -:doc:`/views/helpers/text` pour nous aider à générer le contenu de notre view. -Nous utilisons également la fonction raccourcie :php:func:`h` pour échapper le -contenu HTML. Pensez à utiliser ``h()`` quand vous affichez des données pour -éviter les injections de HTML. - -Le fichier **tags.php** que nous venons de créer suit les conventions CakePHP -pour les templates de view. La convention est d'utiliser le nom de l'action du -controller en minuscule et avec un underscore en séparateur. - -Vous avez peut-être remarqué que nous utilisons les variables ``$tags`` et -``$articles`` dans notre template de view. Quand nous utilisons la méthode -``set()`` dans notre controller, nous définissons les variables qui doivent -être envoyées à notre view. La classe View fera alors en sorte de passer les -variables au scope du template comme variables locales. - -Vous devriez maintenant être capable de visiter la page **/articles/tagged/funny** -et voir tous les articles avec le tag 'funny'. - -Améliorer la Gestion des Tags -============================= - -Pour le moment, ajouter des tags est assez fastidieux puisque les rédacteurs auront -besoin de créer les tags à utiliser avant de les assigner. Nous pouvons améliorer -l'UI de notre gestion de tag en utilisant une liste de valeurs séparées par des -virgules. Cela nous permettra d'améliorer l'expérience utilisateur et de découvrir -d'autres fonctionnalités de l'ORM. - -Ajouter un Champ Pré-calculé ----------------------------- - -Puisque nous souhaitons une manière simple d'accéder aux tags formattés pour une -entity, nous ajoutons un champ virtuel/pré-calculé pour l'entity. Dans -**src/Model/Entity/Article.php** ajoutez la méthode suivante:: - - // Ajouter ce 'use' juste sous la déclaration du namespace pour importer - // la classe Collection - use Cake\Collection\Collection; - - // Mettez à jour la propriété accessible pour qu'elle contienne `tag_string` - protected array $_accessible = [ - //autres champs... - 'tag_string' => true - ]; - protected function _getTagString() - { - if (isset($this->_fields['tag_string'])) { - return $this->_fields['tag_string']; - } - if (empty($this->tags)) { - return ''; - } - $tags = new Collection($this->tags); - $str = $tags->reduce(function ($string, $tag) { - return $string . $tag->title . ', '; - }, ''); - - return trim($str, ', '); - } - -Cela nous permettra d'accéder à la propriété virtuelle ``$article->tag_string``. -Nous utiliserons cette propriété plus tard dans nos contrôles (control). - -Mettre à jour nos View ----------------------- - -Maintenant que notre entity est mise à jour, nous pouvons ajouter un nouvel -élément de contrôle pour nos tags. Dans -**templates/Articles/add.php** et **templates/Articles/edit.php**, -remplacez l'élément de contrôle existant ``tags._ids`` avec la déclaration -suivante:: - - echo $this->Form->control('tag_string', ['type' => 'text']); - - -Nous devrons également mettre à jour le modèle de vue d'article. Dans -**templates/Articles/view.php**, ajoutez la ligne comme indiqué:: - - - -

      title) ?>

      -

      body) ?>

      - // Add the following line -

      Tags: tag_string) ?>

      - -Vous devriez aussi mettre à jour la méthode de vue pour permettre de récupérer -les tags existants:: - - // fichier src/Controller/ArticlesController.php - - public function view($slug = null) - { - // Mettre à jour la récupération des tags avec contain() - $article = $this->Articles - ->findBySlug($slug) - ->contain('Tags') - ->firstOrFail(); - $this->set(compact('article')); - } - -Persister la Chaîne de Tags ---------------------------- - -Maintenant que nous voyons les tags existant sous forme d'une chaîne, nous avons -besoin de sauvegarder les tags sous ce format. Puisque que nous avons rendu ``tag_string`` -accessible, l'ORM copiera les données de la requête dans notre entity. Nous -pouvons utiliser le hook ``beforeSave()`` pour parser la chaîne de tags et -trouver/construire les entities correspondantes. Ajoutez le code suivant à -**src/Model/Table/ArticlesTable.php**:: - - public function beforeSave($event, $entity, $options) - { - if ($entity->tag_string) { - $entity->tags = $this->_buildTags($entity->tag_string); - } - - // Le code déjà existant - } - - protected function _buildTags($tagString) - { - // Trim des tags - $newTags = array_map('trim', explode(',', $tagString)); - // Retire les tags vides - $newTags = array_filter($newTags); - // Dé-doublonne les tags - $newTags = array_unique($newTags); - - $out = []; - $query = $this->Tags->find() - ->where(['Tags.title IN' => $newTags]) - ->all(); - - // Retire les tags existant de la liste des nouveaux tags. - foreach ($query->extract('title') as $existing) { - $index = array_search($existing, $newTags); - if ($index !== false) { - unset($newTags[$index]); - } - } - // Ajout des tags existant. - foreach ($query as $tag) { - $out[] = $tag; - } - // Ajout des nouveaux tags. - foreach ($newTags as $tag) { - $out[] = $this->Tags->newEntity(['title' => $tag]); - } - - return $out; - } - -Si vous créez ou modifiez maintenant des articles, vous devriez pouvoir enregistrer les balises -sous forme de liste de balises séparées par des virgules et créer automatiquement les balises et -les enregistrements de liaison. - -Bien que ce code soit plus compliqué que tout ce que nous avons fait jusqu'ici, -il permet de mettre en avant les fonctions avancées de l'ORM : vous pouvez manipuler -le résultat de la requête en utilisant les méthodes de la classe Collection -(voir la section :doc:`/core-libraries/collections`) et pouvez également gérer -les scénarios où vous avez besoin de créer des entities à la volée. - - -Remplir automatiquement les Tags -================================ - -Avant de terminer, nous aurons besoin d'un mécanisme qui chargera les Tag associés (le cas échéant) -chaque fois que nous chargerons un article. - -Dans votre **src/Model/Table/ArticlesTable.php**, changez:: - - public function initialize(array $config): void - { - $this->addBehavior('Timestamp'); - // Modifiez cette ligne - $this->belongsToMany('Tags', [ - 'joinTable' => 'articles_tags', - 'dependent' => true - ]); - } - - -Cela indiquera au modèle de table Articles qu'une table de jointure est associée -avec des tags. L'option 'dépendent' indique à la table de supprimer tout -enregistrement associé de la table de jointure si un article est supprimé. - -Enfin, mettez à jour les appels de la méthode ``findBySlug()`` dans -**src/Controller/ArticlesController.php**:: - - public function edit($slug) - { - // Mettez à jour cette ligne - $article = $this->Articles - ->findBySlug($slug) - ->contain('Tags') - ->firstOrFail(); - ... - } - - public function view($slug = null) - { - // Mettez à jour cette ligne - $article = $this->Articles - ->findBySlug($slug) - ->contain('Tags') - ->firstOrFail(); - $this->set(compact('article')); - } - -La méthode ``contain ()`` indique à l'objet ``ArticlesTable`` de remplir également l'association -Tags lorsque l'article est chargé. Maintenant, quand tag_string est appelé pour -une entité Article, il y aura des données présentes pour créer la chaîne! - -Dans le chapitre suivant, nous ajouter une couche -:doc:`d'authentification `. diff --git a/fr/views.rst b/fr/views.rst deleted file mode 100644 index 77ed3ff394..0000000000 --- a/fr/views.rst +++ /dev/null @@ -1,788 +0,0 @@ -Views (Vues) -############ - -.. php:namespace:: Cake\View - -.. php:class:: View - -Les Views (Vues) sont le **V** dans MVC. Les vues sont chargées de générer la -sortie spécifique requise par la requête. Souvent, cela est fait sous forme -HTML, XML ou JSON, mais le streaming de fichiers et la création de PDFs que les -utilisateurs peuvent télécharger sont aussi de la responsabilité de la -couche View. - -CakePHP a quelques classes de vue déjà construites pour gérer les scénarios -de rendu les plus communs: - -- Pour créer des services web XML ou JSON, vous pouvez utiliser - :doc:`views/json-and-xml-views`. -- Pour servir des fichiers protégés, ou générer des fichiers dynamiquement, - vous pouvez utiliser :ref:`cake-response-file`. -- Pour créer plusieurs vues pour un thème, vous pouvez utiliser - :doc:`views/themes`. - -.. _app-view: - -La View App -=========== - -``AppView`` est la classe View par défaut de votre application. ``AppView`` -étend elle-même la classe ``Cake\View\View`` de CakePHP et est définie dans -**src/View/AppView.php** comme suit: - -.. code-block:: php - - loadHelper('MyUtils'); - } - - } - -.. _view-templates: - -Templates de Vues -================= - -La couche view de CakePHP c'est la façon dont vous parlez à vos utilisateurs. -La plupart du temps, vos vues afficheront des documents HTML/XHTML pour les -navigateurs, mais vous pourriez aussi avoir besoin de fournir des données AMF -à un objet Flash, répondre à une application distante via SOAP ou produire un -fichier CSV pour un utilisateur. - -Les fichiers de template de CakePHP possèdent une extension **.php** (CakePHP Template) -et utilisent la `syntaxe alternative de PHP -`_ -pour les structures de contrôle et les sorties. Ces fichiers contiennent la logique nécessaire -pour servir les données reçues d'un controller dans un format de présentation qui est lisible -par votre public. - -Sorties Alternatives --------------------- - -Utilisez ``echo`` ou ``print`` sur une variable dans votre template:: - - - -Avec le support de "Short Tag":: - - - -Structures de Contrôle Alternatives ------------------------------------ - -Les structures de contrôle tel que ``if``, ``for``, ``foreach``, ``switch``, et ``while`` -peuvent être écrites dans un format simplifié. Remarquez l'absence d'accolades. À la place, -l'accolade de fin du ``foreach`` est remplacée par ``endforeach``. Chacune des structures de contrôle -listées ci-dessous a une syntaxe de fermeture similaire: ``endif``, -``endfor``, ``endforeach``, et ``endwhile``. Vous remarquerez aussi qu'à la place du ``point-virgule`` après -chaque structure (à l'exception de la dernière), il y a un ``double-point``. - -Voici un example de ``foreach``: - -.. code-block:: php - -
        - -
      • - -
      - -Un autre exemple utilisant if/elseif/else. Remarquez les doubles points: - -.. code-block:: php - - -

      Hi Sally

      - -

      Hi Joe

      - -

      Hi unknown user

      - - -Si vous préférez utiliser un langage de template comme `Twig `_, une sous-classe de View va faire le pont entre le langage du template et CakePHP. - -Un fichier de template est stocké dans **templates/**, dans un sous-dossier -portant le nom du controller qui utilise ce fichier. Il a un nom de fichier -correspondant à son action. Par exemple, le fichier de vue pour l'action -"view()" du controller Products devra normalement se trouver dans -**templates/Products/view.php**. - -La couche vue de CakePHP peut être constituée d'un certain nombre de parties -différentes. Chaque partie a différents usages qui seront présentés dans ce -chapitre: - -- **templates**: Les templates sont la partie de la page qui est unique pour l'action - lancée. Elles sont la substance de la réponse de votre application. -- **elements** : morceaux de code de view plus petits, réutilisables. Les - elements sont habituellement rendus dans les vues. -- **layouts** : fichiers de template contenant le code de présentation qui se - retrouve dans plusieurs interfaces de votre application. La plupart des - vues sont rendues à l'intérieur d'un layout. -- **helpers** : ces classes encapsulent la logique de vue qui est requise - à de nombreux endroits de la couche view. Parmi d'autres choses, les helpers - de CakePHP peuvent vous aider à créer des formulaires, des fonctionnalités - AJAX, à paginer les données du model ou à délivrer des flux RSS. -- **cells**: Ces classes fournissent des fonctionnalités de type controller - en miniature pour créer des components avec une UI indépendante. Regardez - la documentation :doc:`/views/cells` pour plus d'informations. - -Variables de Vue ----------------- - -Toute variable que vous définissez dans votre controller avec ``set()`` sera -disponible à la fois dans la vue et dans le layout que votre action utilise. -En plus, toute variable définie sera aussi disponible dans tout element. -Si vous avez besoin de passer des variables supplémentaires de la -vue vers le layout, vous pouvez soit appeler ``set()`` dans le template de vue, -soit utiliser un :ref:`view-blocks`. - -Vous devriez vous rappeler de **toujours** échapper les données d'utilisateur -avant de les afficher puisque CakePHP n'échappe automatiquement la sortie. Vous -pouvez échapper le contenu d'utilisateur avec la fonction ``h()``:: - - bio); ?> - -Définir les Variables de Vue ----------------------------- - -.. php:method:: set(string $var, mixed $value) - -Les vues ont une méthode ``set()`` qui fonctionne de la même façon que -``set()`` qui se trouve dans les objets Controller. Utiliser set() à -partir de la vue va ajouter les variables au layout et aux elements qui seront -rendus plus tard. Regardez :ref:`setting-view_variables` pour plus -d'informations sur l'utilisation de ``set()``. - -Dans votre fichier de vue, vous pouvez faire:: - - $this->set('activeMenuButton', 'posts'); - -Ensuite, dans votre layout, la variable ``$activeMenuButton`` sera disponible -et contiendra la valeur 'posts'. - -.. _extending-views: - -Vues étendues -------------- - -Une vue étendue vous permet d'encapsuler une vue dans une autre. En combinant -cela avec :ref:`view blocks `, cela vous donne une façon puissante -pour garder vos vues :term:`DRY`. Par exemple, votre application a une sidebar -qui a besoin de changer selon la vue spécifique en train d'être rendue. En -étendant un fichier de vue commun, vous pouvez éviter de répéter la balise -commune pour votre sidebar, et seulement définir les parties qui changent: - -.. code-block:: php - - -

      fetch('title') ?>

      - fetch('content') ?> - -
      -

      Related actions

      -
        - fetch('sidebar') ?> -
      -
      - -Le fichier de vue ci-dessus peut être utilisé comme une vue parente. Il -s'attend à ce que la vue l'étendant définisse des blocks ``sidebar`` -et ``title``. Le block ``content`` est un block spécial que CakePHP -crée. Il contiendra tous les contenus non capturés de la vue étendue. -En admettant que notre fichier de vue a une variable ``$post`` avec les -données sur notre post. Notre vue pourrait ressembler à ceci: - -.. code-block:: php - - - extend('/Common/view'); - - $this->assign('title', $post->title); - - $this->start('sidebar'); - ?> -
    • - Html->link('edit', [ - 'action' => 'edit', - $post->id - ]); ?> -
    • - end(); ?> - - // The remaining content will be available as the 'content' block - // In the parent view. - body) ?> - -L'exemple ci-dessus vous montre comment vous pouvez étendre une vue, et -remplir un ensemble de blocks. Tout contenu qui ne serait pas déjà dans un block -défini, sera capturé et placé dans un block spécial appelé ``content``. Quand -une vue contient un appel vers un ``extend()``, l'exécution continue jusqu'à la -fin de la vue actuelle. Une fois terminé, la vue étendue va être générée. En -appelant ``extend()`` plus d'une fois dans un fichier de vue, le dernier appel -va outrepasser les précédents:: - - $this->extend('/Common/view'); - $this->extend('/Common/index'); - -Le code précédent va définir **/Common/index.php** comme étant la vue parente -de la vue actuelle. - -Vous pouvez imbriquer les vues autant que vous le voulez et que cela vous est -nécessaire. Chaque vue peut étendre une autre vue si vous le souhaitez. Chaque -vue parente va récupérer le contenu de la vue précédente en tant que block -``content``. - -.. note:: - - Vous devriez éviter d'utiliser ``content`` comme nom de block dans votre - application. CakePHP l'utilise pour définir le contenu non-capturé pour - les vues étendues. - -Vous pouvez récupérer la liste de tous blocks existants en utilisant la méthode -``blocks()``:: - - $list = $this->blocks(); - -.. _view-blocks: - -Utiliser les Blocks de Vues -=========================== - -Les blocks de vue fournissent une API flexible qui vous permet de -définir des slots (emplacements), ou blocks, dans vos vues / layouts qui peuvent -être définies ailleurs. Par exemple, les blocks pour implémenter des choses -telles que les sidebars, ou des régions pour charger des ressources dans -l'en-tête / pied de page du layout. Un block peut être défini de deux manières. -Soit en tant que block capturant, soit en le déclarant explicitement. Les -méthodes ``start()``, ``append()``, ``prepend()``, ``assign()``, ``fetch()`` -et ``end()`` vous permettent de travailler avec les blocks capturant:: - - // Créer le block sidebar. - $this->start('sidebar'); - echo $this->element('sidebar/recent_topics'); - echo $this->element('sidebar/recent_comments'); - $this->end(); - - // Le rattacher à la sidebar plus tard. - $this->start('sidebar'); - echo $this->fetch('sidebar'); - echo $this->element('sidebar/popular_topics'); - $this->end(); - -Vous pouvez aussi ajouter dans un block en utilisant ``append()``:: - - $this->append('sidebar'); - echo $this->element('sidebar/popular_topics'); - $this->end(); - - // Le même que ci-dessus. - $this->append('sidebar', $this->element('sidebar/popular_topics')); - -Si vous devez nettoyer ou écraser un block, vous avez plusieurs alternatives. -La méthode ``reset()`` va nettoyer ou écraser un block à n'importe quel moment. -La méthode ``assign()`` avec une chaîne de caractères vide peut également être -utilisée.:: - - // Nettoyer le contenu précédent du block de sidebar - $this->reset('sidebar'); - - // Assigner une chaine vide aura le même effet. - $this->assign('sidebar', ''); - -Assigner le contenu d'un block est souvent utile lorsque vous voulez convertir -une variable de vue en un block. Par exemple, vous pourriez vouloir utiliser -un block pour le titre de la page et parfois le définir depuis le controller:: - - // Dans une view ou un layout avant $this->fetch('title') - $this->assign('title', $title); - -La méthode ``prepend()`` a été ajoutée pour ajouter du contenu avant un block -existant:: - - // Ajoutez avant la sidebar - $this->prepend('sidebar', 'ce contenu va au-dessus de la sidebar'); - -.. note:: - - Vous devriez éviter d'utiliser ``content`` comme nom de bloc. Celui-ci est - utilisé par CakePHP en interne pour étendre les vues, et le contenu des - vues dans le layout. - -Afficher les Blocks -------------------- - -Vous pouvez afficher les blocks en utilisant la méthode ``fetch()``. Cette -dernière va, de manière sécurisée, générer un block, en retournant '' si le -block n'existe pas:: - - fetch('sidebar') ?> - -Vous pouvez également utiliser fetch pour afficher du contenu, sous conditions, -qui va entourer un block existant. Ceci est très utile dans les layouts, ou -dans les vues étendues lorsque vous voulez, sous conditions, afficher des -en-têtes ou autres balises: - -.. code-block:: php - - // dans templates/layout/default.php - fetch('menu')): ?> - - - -Vous pouvez aussi fournir une valeur par défaut pour un block -qui ne devrait pas avoir de contenu. Cela vous permet d'ajouter du contenu -placeholder, pour des déclarations vides. Vous pouvez fournir une valeur par -défaut en utilisant le 2ème argument: - -.. code-block:: php - -
      -

      Your Cart

      - fetch('cart', 'Votre Caddie est vide') ?> -
      - -Utiliser des Blocks pour les Fichiers de Script et les CSS ----------------------------------------------------------- - -:php:class:`HtmlHelper` est lié aux blocks de vue, et ses méthodes -:php:meth:`~HtmlHelper::script()`, :php:meth:`~HtmlHelper::css()`, et -:php:meth:`~HtmlHelper::meta()` mettent à jour chacun un block avec le même nom -quand il est utilisé avec l'option ``block = true``: - -.. code-block:: php - - Html->script('carousel', ['block' => true]); - $this->Html->css('carousel', ['block' => true]); - ?> - - // Dans votre fichier de layout. - - - - <?= $this->fetch('title') ?> - fetch('script') ?> - fetch('css') ?> - - // reste du layout à la suite - -Le :php:meth:`HtmlHelper` vous permet aussi de contrôler vers quels blocks vont -les scripts:: - - // dans votre vue - $this->Html->script('carousel', ['block' => 'scriptBottom']); - - // dans votre layout - fetch('scriptBottom') ?> - -.. _view-layouts: - -Layouts -======= - -Un layout contient le code de présentation qui entoure une vue. -Tout ce que vous voulez voir dans toutes vos vues devra être placé dans un -layout. - -Le fichier de layout par défaut de CakePHP est placé dans -**templates/layout/default.php**. Si vous voulez changer entièrement le -look de votre application, alors c'est le bon endroit pour commencer, parce que -le code de vue de rendu du controller est placé à l'intérieur du layout par -défaut quand la page est rendue. - -Les autres fichiers de layout devront être placés dans **templates/layout**. -Quand vous créez un layout, vous devez dire à CakePHP où placer -la sortie pour vos vues. Pour ce faire, assurez-vous que votre layout contienne -``$this->fetch('content')``. Voici un exemple de ce à quoi un layout pourrait -ressembler: - -.. code-block:: php - - - - - <?= $this->fetch('title'); ?> - - - fetch('meta'); - echo $this->fetch('css'); - echo $this->fetch('script'); - ?> - - - - - - - - fetch('content') ?> - - - - - - - -Les blocks ``script``, ``css`` et ``meta`` contiennent tout contenu défini -dans les vues en utilisant le helper HTML intégré. Il est utile pour inclure -les fichiers JavaScript et les CSS à partir des vues. - -.. note:: - - Quand vous utilisez ``HtmlHelper::css()`` ou - ``HtmlHelper::script()`` dans les fichiers de template, spécifiez - ``'block' => true`` pour placer la source html dans un - block avec le même nom. (Regardez l'API pour plus de détails sur leur - utilisation). - -Le block ``content`` contient les contenus de la vue rendue. - -Vous pouvez aussi définir le block ``title`` depuis l'intérieur -d'un fichier de vue:: - - $this->assign('title', $titleContent); - -Vous pouvez créer autant de layouts que vous souhaitez: placez les juste dans -le répertoire **templates/layout**, et passez de l'un à l'autre depuis les -actions de votre controller en utilisant la propriété -``$layout`` de votre controller ou de votre vue:: - - // À partir d'un controller - public function view() - { - // Définir le layout - $this->viewBuilder()->setLayout('admin'); - } - - // À partir d'un fichier de vue - $this->layout = 'loggedin'; - -Par exemple, si une section de mon site incorpore un plus petit espace pour -une bannière publicitaire, je peux créer un nouveau layout avec le plus -petit espace de publicité et le spécifier comme un layout pour toutes les -actions du controller en utilisant quelque chose comme:: - - namespace App\Controller; - - class UsersController extends AppController - { - public function viewActive() - { - $this->set('title', 'View Active Users'); - $this->viewBuilder()->setLayout('default_small_ad'); - } - - public function viewImage() - { - $this->viewBuilder()->setLayout('image'); - - // Output user image - } - } - -Outre le layout par défaut, le squelette officiel d'application CakePHP dispose -également d'un layout 'ajax'. Le layout AJAX est pratique pour élaborer des -réponses AJAX - c'est un layout vide (la plupart des appels ajax ne nécessitent -qu'un peu de balise en retour, et pas une interface de rendu complète). - -Le squelette d'application dispose également d'un layout par défaut pour aider -à générer du RSS. - -Utiliser les layouts à partir de plugins ----------------------------------------- - -Si vous souhaitez utiliser un layout qui existe dans un plugin, vous pouvez -utiliser la :term:`syntaxe de plugin`. Par exemple pour utiliser le layout de -contact à partir du plugin Contacts:: - - namespace App\Controller; - - class UsersController extends AppController - { - public function view_active() - { - $this->viewBuilder()->layout('Contacts.contact'); - } - } - -.. _view-elements: - -Elements -======== - -.. php:method:: element(string $elementPath, array $data, array $options = []) - -Beaucoup d'applications ont des petits blocks de code de présentation qui -doivent être répliqués d'une page à une autre, parfois à des endroits -différents dans le layout. CakePHP peut vous aider à répéter des parties -de votre site web qui doivent être réutilisées. Ces parties réutilisables -sont appelées des Elements. Les publicités, les boites d'aides, les contrôles -de navigation, les menus supplémentaires, les formulaires de connexion et de -sortie sont souvent intégrés dans CakePHP en elements. Un element est tout -bêtement une mini-vue qui peut être inclue dans d'autres vues, dans les -layouts, et même dans d'autres elements. Les elements peuvent être utilisés -pour rendre une vue plus lisible, en plaçant le rendu d'éléments répétitifs -dans ses propres fichiers. Ils peuvent aussi vous aider à réutiliser des -fragments de contenu dans votre application. - -Les elements se trouvent dans le dossier **templates/element/**, et ont une -extension .php. Ils sont rendus en utilisant la méthode element de la vue:: - - echo $this->element('helpbox'); - -Passer des Variables à l'intérieur d'un Element ------------------------------------------------ - -Vous pouvez passer des données dans un element grâce au deuxième argument:: - - echo $this->element('helpbox', [ - 'helptext' => 'Oh, ce texte est très utile.' - ]); - -Dans le fichier element, toutes les variables passées sont disponibles comme -des membres du paramètre du tableau (de la même manière que -:php:meth:`Controller::set()` fonctionne dans le controller avec les fichiers -de template). Dans l'exemple ci-dessus, le fichier -**templates/element/helpbox.php** peut utiliser la variable ``$helptext``:: - - // A l'intérieur de templates/element/helpbox.php - echo $helptext; //affiche 'Oh, ce texte est très utile.' - -La méthode :php:meth:`View::element()` supporte aussi les options pour -l'element. Les options supportées sont 'cache' et 'callbacks'. Un exemple:: - - echo $this->element('helpbox', [ - 'helptext' => "Ceci est passé à l'element comme $helptext", - 'foobar' => "Ceci est passé à l'element via $foobar", - ], - [ - // utilise la configuration de cache `long_view` - 'cache' => 'long_view"', - // défini à true pour avoir before/afterRender appelé pour l'element - 'callbacks' => true - ] - ); - -La mise en cache d'element est facilitée par la classe :php:class:`Cache`. Vous -pouvez configurer les elements devant être stockés dans toute configuration de -Cache que vous avez défini. Cela vous donne une grande flexibilité pour -choisir où et combien de temps les elements sont stockés. Pour mettre en cache -les différentes versions du même element dans une application, -fournissez une valeur unique de la clé cache en utilisant le format suivant:: - - $this->element('helpbox', [], [ - 'cache' => ['config' => 'short', 'key' => 'unique value'] - ] - ); - -Si vous avez besoin de plus de logique dans votre element, comme des données -dynamiques à partir d'une source de données, pensez à utiliser une View Cell -plutôt qu'un element. Vous pouvez en savoir plus en consultant :doc:`les View -Cells `. - -Mise en cache des Elements --------------------------- - -Vous pouvez tirer profit de la mise en cache de vue de CakePHP si vous -fournissez un paramètre cache. Si défini à ``true``, cela va mettre en cache -l'element dans la configuration 'default' de Cache. Sinon, vous pouvez définir -la configuration de cache devant être utilisée. Regardez -:doc:`/core-libraries/caching` pour plus d'informations sur la façon de -configurer ``Cache``. Un exemple simple de mise en cache d'un element -serait par exemple:: - - echo $this->element('helpbox', [], ['cache' => true]); - -Si vous rendez le même element plus d'une fois dans une vue et que vous avez -activé la mise en cache, assurez-vous de définir le paramètre 'key' avec -un nom différent à chaque fois. Cela évitera que chaque appel successif -n'écrase le résultat de la mise en cache du précédent appel de ``element()``. -Par exemple:: - - echo $this->element( - 'helpbox', - ['var' => $var], - ['cache' => ['key' => 'first_use', 'config' => 'view_long']] - ); - - echo $this->element( - 'helpbox', - ['var' => $differenVar], - ['cache' => ['key' => 'second_use', 'config' => 'view_long']] - ); - -Ce qui est au-dessus va s'enquérir que les deux résultats d'element sont -mis en cache séparément. Si vous voulez que tous les elements mis en cache -utilisent la même configuration du cache, vous pouvez sauvegarder quelques -répétitions, en configurant ``View::$elementCache`` dans la -configuration de Cache que vous souhaitez utiliser. CakePHP va utiliser cette -configuration, quand aucune n'est donnée. - -Requêter les Elements à partir d'un Plugin ------------------------------------------- - -Si vous utilisez un plugin et souhaitez utiliser les elements à partir de -l'intérieur d'un plugin, utilisez juste la :term:`syntaxe de plugin` -habituelle. Si la vue est rendue pour un controller/action d'un plugin, le nom -du plugin va automatiquement être préfixé pour tous les elements utilisés, à -moins qu'un autre nom de plugin ne soit présent. -Si l'element n'existe pas dans le plugin, il ira voir dans le dossier principal -APP:: - - echo $this->element('Contacts.helpbox'); - -Si votre vue fait partie d'un plugin, vous pouvez ne pas mettre le nom du -plugin. Par exemple, si vous êtes dans le ``ContactsController`` du plugin -Contacts:: - - echo $this->element('helpbox'); - // et - echo $this->element('Contacts.helpbox'); - -Sont équivalents et résulteront à l'affichage du même element. - -Pour les elements dans le sous-dossier d'un plugin -(e.g., **plugins/Contacts/sidebar/helpbox.php**), utilisez ce qui suit:: - - echo $this->element('Contacts.sidebar/helpbox'); - -Préfix de Routing et Elements ------------------------------ - -Si vous avez configuré un préfix de routage, la résolution des chemins d'accès -aux Elements peut chercher dans un chemin préfixé, comme les Layouts et les vues -d'Action le font déjà. -En partant du postulat que vous avez configuré le préfix "Admin" et que vous -appelez:: - - echo $this->element('my_element'); - -L'element va d'abord être cherché dans **templates/Admin/Element/**. Si un -tel fichier n'existe pas, il sera ensuite cherché dans le chemin par défaut. - -Mettre en Cache des Sections de votre View ------------------------------------------- - -.. php:method:: cache(callable $block, array $options = []) - -Parfois, générer une section de l'affichage de votre view peut être coûteux -à cause du rendu des :doc:`/views/cells` ou du fait d'opérations de helper -coûteuses. Pour que votre application s'exécute plus rapidement, CakePHP fournit -un moyen de mettre en cache des sections de view:: - - // En supposant l'existence des variables locales - echo $this->cache(function () use ($user, $article) { - echo $this->cell('UserProfile', [$user]); - echo $this->cell('ArticleFull', [$article]); - }, ['key' => 'my_view_key']); - -Par défaut, le contenu de la view ira dans la config de cache -``View::$elementCache``, mais vous pouvez utiliser l'option ``config`` pour -changer ceci. - -.. _view-events: - -Events de View -============== - -Tout comme le Controller, la View lance plusieurs events/callbacks (méthodes de -rappel) que vous pouvez utiliser pour insérer de la logique durant tout le cycle -de vie du processus de rendu: - -Liste des Events ----------------- - -* ``View.beforeRender`` -* ``View.beforeRenderFile`` -* ``View.afterRenderFile`` -* ``View.afterRender`` -* ``View.beforeLayout`` -* ``View.afterLayout`` - -Vous pouvez attacher les :doc:`listeners d'events ` de -votre application à ces events ou utiliser les :ref:`Callbacks de Helper `. - -Créer vos propres Classes de View -================================= - -Vous avez peut-être besoin de créer vos propres classes de vue pour activer des -nouveaux types de données de vue, ou ajouter de la logique supplémentaire -pour le rendu de vue personnalisée. Comme la plupart des components de -CakePHP, les classes de vue ont quelques conventions: - -* Les fichiers de classe de View doivent être mis dans **src/View**. Par - exemple **src/View/PdfView.php**. -* Les classes de View doivent être suffixées avec ``View``. Par exemple - ``PdfView``. -* Quand vous référencez les noms de classe de vue, vous devez omettre le - suffixe ``View``. Par exemple ``$this->viewBuilder()->className('Pdf');``. - -Vous voudrez aussi étendre ``View`` pour vous assurer que les choses -fonctionnent correctement:: - - // Dans src/View/PdfView.php - namespace App\View; - - use Cake\View\View; - - class PdfView extends View - { - public function render($view = null, $layout = null) - { - // logique personnalisée ici. - } - } - -Remplacer la méthode render vous laisse le contrôle total sur la façon dont -votre contenu est rendu. - -En savoir plus sur les vues -=========================== - -.. toctree:: - :maxdepth: 1 - - views/cells - views/themes - views/json-and-xml-views - views/helpers - -.. meta:: - :title lang=fr: Views (Vues) - :keywords lang=fr: logique de vue,fichier csv,éléments de réponse,éléments de code,extension par défaut,json,objet flash,remote application,twig,sous-classe,ajax,répondre,soap,fonctionnalité,cakephp,fréquentation,xml,mvc diff --git a/fr/views/cells.rst b/fr/views/cells.rst deleted file mode 100644 index b944bdc0ec..0000000000 --- a/fr/views/cells.rst +++ /dev/null @@ -1,276 +0,0 @@ -View Cells -########## - -View cells sont des mini-controllers qui peuvent invoquer de la logique de vue -et afficher les templates. L'idée des cells est empruntée aux `cells dans ruby -`_, où elles remplissent un rôle et un -sujet similaire. - -Quand utiliser les Cells -======================== - -Les Cells sont idéales pour la construction de components de page réutilisables -qui nécessitent une interaction avec les models, la logique de view, et la -logique de rendu. Un exemple simple serait un caddie dans un magasin en ligne, -ou un menu de navigation selon des données dans un CMS. - -Créer une Cell -============== - -Pour créer une cell, vous définissez une classe dans **src/View/Cell**, et un -template dans **templates/cell/**. Dans cet exemple, nous ferons une cell -pour afficher le nombre de messages dans la boite de messages de notification de -l'utilisateur. D'abord, créons le fichier de classe. Son contenu devrait -ressembler à ceci:: - - namespace App\View\Cell; - - use Cake\View\Cell; - - class InboxCell extends Cell - { - - public function display() - { - } - - } - -Sauvegardez ce fichier dans **src/View/Cell/InboxCell.php**. Comme vous pouvez -le voir, comme pour les autres classes dans CakePHP, les Cells ont quelques -conventions: - -* Les Cells se trouvent dans le namespace ``App\View\Cell``. Si vous faîtes une - cell dans un plugin, le namespace sera ``PluginName\View\Cell``. -* Les noms de classe doivent finir en Cell. -* Les classes doivent hériter de ``Cake\View\Cell``. - -Nous avons ajouté une méthode vide ``display()`` à notre cell, c'est la méthode -conventionnelle par défaut pour le rendu de cell. Nous couvrirons la façon -d'utiliser les autres méthodes plus tard dans la doc. Maintenant, créons le -fichier **templates/cell/Inbox/display.php**. Ce sera le template pour notre -nouvelle cell. - -Vous pouvez générer ce bout de code rapidement en utilisant ``bake``:: - - bin/cake bake cell Inbox - -Générera le code que nous avons tapé. - -Implémenter la Cell -------------------- - -Supposons que nous travaillions sur une application qui permette aux -utilisateurs d'envoyer des messages aux autres. Nous avons un model -``Messages``, et nous voulons montrer le nombre de messages non lus sans avoir -à polluer AppController. C'est un cas d'utilisation parfait pour une cell. Dans -la classe, nous avons juste ajouté ce qui suit:: - - namespace App\View\Cell; - - use Cake\View\Cell; - - class InboxCell extends Cell - { - - public function display() - { - $unread = $this->fetchTable('Messages')->find('unread'); - $this->set('unread_count', $unread->count()); - } - - } - -Puisque les cells utilisent ``LocatorAwareTrait`` et ``ViewVarsTrait``, elles -se comportent un peu comme un controller. Nous pouvons utiliser les méthodes -``fetchTable()`` et ``set()`` un peu comme nous le ferions dans un controller. -Dans notre fichier de template, ajoutons ce qui suit:: - - -
      - Vous avez messages non lus. -
      - -.. note:: - - Les templates des cells ont une portée isolée et ne partage pas la même - instance de View que celle utilisée pour rendre le template et le layout - de l'action du controller courant ou d'autres cells. Ils ne sont donc pas - au courant de tous les appels aux helpers ou aux blocs définis dans - template / layout de l'action et vice versa. - -Charger les Cells -================= - -Les cells peuvent être chargées à partir des views en utilisant la méthode -``cell()`` et fonctionne de la même manière dans les deux contextes:: - - // Charge une cell d'une application - $cell = $this->cell('Inbox'); - - // Charge une cell d'un plugin - $cell = $this->cell('Messaging.Inbox'); - -Ce qui est au-dessus va charger la classe de cell nommée et exécuter la méthode -``display()``. -Vous pouvez exécuter d'autres méthodes en utilisant ce qui suit:: - - // Lance la méthode expanded() dans la cell Inbox - $cell = $this->cell('Inbox::expanded'); - -Si vous avez besoin que votre controller décide quelles cells doivent être -chargées dans une requête, vous pouvez utiliser le ``CellTrait`` dans votre -controller pour y activer la méthode ``cell()``:: - - namespace App\Controller; - - use App\Controller\AppController; - use Cake\View\CellTrait; - - class DashboardsController extends AppController - { - use CellTrait; - - // More code. - } - -Passer des Arguments à une Cell -------------------------------- - -Vous voudrez souvent paramétrer les méthodes cell pour rendre les cells plus -flexibles. En utilisant les deuxième et troisième arguments de ``cell()``, vous -pouvez passer des paramètres d'action, et des options supplémentaires à vos -classes de cell, en tableau indexé:: - - $cell = $this->cell('Inbox::recent', ['-3 days']); - -Ce qui est au-dessus correspondra à la signature de la fonction suivante:: - - public function recent($since) - { - } - -Afficher une Cell -================= - -Une fois qu'une cell a été chargée et exécutée, vous voudrez probablement -l'afficher. La façon la plus simple pour rendre une cell est de faire une echo:: - - - -Ceci va afficher le template correspondant à la version en minuscule et avec des -underscores de notre nom d'action, par exemple **display.php**. - -Puisque les cells utilisent ``View`` pour afficher les templates, vous pouvez -charger les cells supplémentaires dans un template de cell si nécessaire. - -.. note:: - - L'affichage d'une cell utilise la méthode magique PHP ``__toString()`` qui - empêche PHP de montrer le nom du fichier et le numéro de la ligne pour - toutes les erreurs fatales levées. Pour obtenir un message d'erreur qui a - du sens, il est recommandé d'utiliser la méthode ``Cell::render()``, par - exemple ``render() ?>``. - -Afficher un Template alternatif -------------------------------- - -Par convention, les cells affichent les templates qui correspondent à l'action -qu'ils exécutent. Si vous avez besoin d'afficher un template de vue différent, -vous pouvez spécifier le template à utiliser lors de l'affichage de la cell:: - - // Appel de render() explicitement - echo $this->cell('Inbox::recent', ['-3 days'])->render('messages'); - - // Définit le template avant de faire un echo de la cell. - $cell = $this->cell('Inbox'); ?> - $cell->viewBuilder()->setTemplate('messages'); - - echo $cell; - -Mettre en Cache la Sortie de Cell ---------------------------------- - -Quand vous affichez une cell, vous pouvez mettre en cache la sortie rendue si -les contenus ne changent pas souvent ou pour aider à améliorer la performance -de votre application. Vous pouvez définir l'option ``cache`` lors de la création -d'une cell pour activer & configurer la mise en cache:: - - // Le Cache utilisant la config par défaut et une clé générée - $cell = $this->cell('Inbox', [], ['cache' => true]); - - // Mise en cache avec une config de cache spécifique et une clé générée - $cell = $this->cell('Inbox', [], ['cache' => ['config' => 'cell_cache']]); - - // Spécifie la clé et la config à utiliser. - $cell = $this->cell('Inbox', [], [ - 'cache' => ['config' => 'cell_cache', 'key' => 'inbox_' . $user->id] - ]); - -Si une clé est générée, la version en underscore de la classe cell et le nom du -template seront utilisés. - -.. note:: - - Une nouvelle instance de ``View`` est utilisée pour retourner chaque cell et - ces nouveaux objets ne partagent pas de contexte avec le template /layout - principal. Chaque cell est auto-contenu et a seulement accès aux variables - passés en arguments par l'appel de ``View::cell()``. - -Paginer des Données dans une Cell -================================= - -Créer une cell qui qui rend des résultats paginés peut être fait en utilisant -la classe ``Paginator`` de l'ORM. Voici un exemple de pagination des messages -favoris d'un utilisateur:: - - namespace App\View\Cell; - - use Cake\View\Cell; - use Cake\Datasource\Paginator; - - class FavoritesCell extends Cell - { - public function display($user) - { - // Création du paginator - $paginator = new Paginator(); - - // Pagination du model - $results = $paginator->paginate( - $this->fetchTable('Messages'), - $this->request->getQueryParams(), - [ - // Utilisation d'un finder personnalisé avec paramètre - 'finder' => ['favorites' => [$user]], - - // Utilisation de paramètre de query 'scoped'. - 'scope' => 'favorites', - ] - ); - $this->set('favorites', $results); - } - } - -La cell ci-dessus va paginer le model ``Messages`` en utilisant les -:ref:`paramètres de pagination 'scopés' `. - -Utiliser des Helpers dans une Cell -================================== - -Les cells ont leur propre contexte et leur propre instance View, mais les -helpers chargés dans ``AppView::initialize()`` restent chargés comme d'habitude. - -Pour charger un Helper spécifique uniquement pour une Cell spécifique, procédez -de la façon suivante:: - - namespace App\View\Cell; - - use Cake\View\Cell; - - class FavoritesCell extends Cell - { - public function initialize(): void { - $this->viewBuilder()->addHelper('MonHelperPersonnalise'); - } - } diff --git a/fr/views/helpers.rst b/fr/views/helpers.rst deleted file mode 100644 index befa9a9422..0000000000 --- a/fr/views/helpers.rst +++ /dev/null @@ -1,429 +0,0 @@ -Helpers (Assistants) -#################### - -Les Helpers (Assistants) sont des classes comme les components, pour la couche -de présentation de votre application. Ils contiennent la logique de -présentation qui est partagée entre plusieurs vues, elements ou layouts. Ce -chapitre vous montrera comment créer vos propres helpers et soulignera les -tâches basiques que les helpers du cœur de CakePHP peuvent vous aider à -accomplir. - -CakePHP dispose d'un certain nombre de helpers qui aident à la création des -vues. Ils aident à la création de balises bien-formatées (y compris les -formulaires), aident à la mise en forme du texte, les durées et les nombres, -et peuvent même accélérer la fonctionnalité AJAX. Pour plus d'informations sur -les helpers inclus dans CakePHP, regardez le chapitre pour chaque helper: - -.. toctree:: - :maxdepth: 1 - - /views/helpers/breadcrumbs - /views/helpers/flash - /views/helpers/form - /views/helpers/html - /views/helpers/number - /views/helpers/paginator - /views/helpers/text - /views/helpers/time - /views/helpers/url - -.. _configuring-helpers: - -Configurer les Helpers -====================== - -Dans CakePHP, vous chargez les helpers en les déclarant dans une classe view. -Une classe ``AppView`` est fournie avec chaque application CakePHP et est le -lieu idéal pour charger les helpers:: - - class AppView extends View - { - public function initialize(): void - { - parent::initialize(); - $this->loadHelper('Html'); - $this->loadHelper('Form'); - $this->loadHelper('Flash'); - } - } - -Le chargement des helpers depuis les plugins utilise la -:term:`syntaxe de plugin` utilisée partout ailleurs dans CakePHP:: - - $this->loadHelper('Blog.Comment'); - -Vous n'avez pas à charger explicitement les helpers fournis par CakePHP ou de -votre application. Ces helpers seront chargés paresseusement (lazily) au moment -de la première utilisation. Par exemple:: - - // Charge le FormHelper s'il n'a pas été chargé précédemment. - $this->Form->create($article); - -A partir d'une vue de plugin, les helpers de plugin peuvent également être -chargés paresseusement. Par exemple, les templates de vues dans le plugin -'Blog', peuvent charger paresseusement les helpers provenant du même plugin. - -Chargement Conditionnel des Helpers ------------------------------------ - -Vous pouvez utiliser le nom de l'action courante pour charger -conditionnellement des helpers:: - - class AppView extends View - { - public function initialize(): void - { - parent::initialize(); - if ($this->request->getParam('action') === 'index') { - $this->loadHelper('ListPage'); - } - } - } - -Vous pouvez également utiliser la méthode ``beforeRender()`` de vos controllers -pour charger des helpers:: - - class ArticlesController extends AppController - { - public function beforeRender(Event $event) - { - parent::beforeRender($event); - $this->viewBuilder()->helpers(['MyHelper']); - } - } - -Options de Configuration ------------------------- - -Vous pouvez passer des options de configuration dans les helpers. Ces options -peuvent être utilisées pour définir les valeurs d'attributs ou modifier le -comportement du helper:: - - namespace App\View\Helper; - - use Cake\View\Helper; - use Cake\View\View; - - class AwesomeHelper extends Helper - { - public function initialize(array $config): void - { - debug($config); - } - } - - class AwesomeController extends AppController - { - public $helpers = ['Awesome' => ['option1' => 'value1']]; - } - -Les options peuvent être spécifiées lors de la déclaration des helpers dans le -controller comme montré ci-dessous:: - - namespace App\Controller; - - use App\Controller\AppController; - - class AwesomeController extends AppController - { - public $helpers = ['Awesome' => ['option1' => 'value1']]; - } - -Par défaut, toutes les options de configuration sont fusionnées avec la -propriété ``$_defaultConfig``. Cette propriété doit définir les valeurs par -défaut de toute configuration dont votre helper a besoin. Par exemple:: - - namespace App\View\Helper; - - use Cake\View\Helper; - use Cake\View\StringTemplateTrait; - - class AwesomeHelper extends Helper - { - - use StringTemplateTrait; - - protected $_defaultConfig = [ - 'templates' => [ - 'label' => '', - ], - ]; - } - -Toute configuration fournie au constructeur de votre helper sera fusionnée avec -les valeurs par défaut pendant la construction et les données fusionnées seront -définies à ``_config``. Vous pouvez utiliser la méthode ``config()`` pour lire -la configuration actuelle:: - - // Lit l'option de config autoSetCustomValidity . - $class = $this->Awesome->config('autoSetCustomValidity '); - -L'utilisation de la configuration du helper vous permet de configurer de manière -déclarative vos helpers et de garder la logique de configuration en dehors des -actions de votre controller. Si vous avez des options de configuration qui ne -peuvent pas être inclues comme une partie de la classe de déclaration, vous -pouvez les définir dans le callback ``beforeRender()`` de votre controller:: - - class PostsController extends AppController - { - public function beforeRender(Event $event) - { - parent::beforeRender($event); - $builder = $this->viewBuilder(); - $builder->helpers([ - 'CustomStuff' => $this->_getCustomStuffConfig() - ]); - } - } - -.. _aliasing-helpers: - -Faire des Alias de Helpers --------------------------- - -Une configuration habituelle à utiliser est l'option ``className``, qui vous -permet de créer des alias de helpers dans vos vues. Cette fonctionnalité est -utile quand vous voulez remplacer ``$this->Html`` ou une autre référence du -Helper habituel avec une implémentation personnalisée:: - - // src/View/AppView.php - class AppView extends View - { - public function initialize(): void - { - $this->loadHelper('Html', [ - 'className' => 'MyHtml' - ]); - } - } - - // src/View/Helper/MyHtmlHelper.php - namespace App\View\Helper; - - use Cake\View\Helper\HtmlHelper; - - class MyHtmlHelper extends HtmlHelper - { - // Ajout de code pour surcharger le HtmlHelper du cœur - } - -Ce qui est au-dessus va faire un *alias* de ``MyHtmlHelper`` vers -``$this->Html`` dans vos vues. - -.. note:: - - Faire un alias remplace cette instance partout où le helper est utilisé, - ainsi que dans les autres Helpers. - -Utiliser les Helpers -==================== - -Une fois que vous avez configuré les helpers que vous souhaitiez utiliser, dans -votre controller, chaque helper est exposé en propriété publique dans la vue. -Par exemple, si vous utilisiez :php:class:`HtmlHelper`, vous serez capable -d'y accéder en faisant ce qui suit:: - - echo $this->Html->css('styles'); - -Ce qui est au-dessus appellera la méthode ``css`` du HtmlHelper. Vous pouvez -accéder à n'importe quel helper chargé en utilisant ``$this->{$helperName}``. - -Charger les Helpers à la Volée ------------------------------- - -Il peut y avoir des cas où vous aurez besoin de charger dynamiquement un helper -depuis l'intérieur d'une vue. Pour cela, vous pouvez utiliser le -:php:class:`Cake\\View\\HelperRegistry`:: - - // Les deux solutions fonctionnent. - $mediaHelper = $this->loadHelper('Media', $mediaConfig); - $mediaHelper = $this->helpers()->load('Media', $mediaConfig); - -Le HelperCollection est une :doc:`registry ` -et supporte l'API collection utilisée partout ailleurs dans CakePHP. - -Méthodes de Callback -==================== - -Les Helpers disposent de plusieurs callbacks qui vous permettent d'augmenter -le processus de rendu de vue. Allez voir la documentation de :ref:`helper-api` -et :doc:`/core-libraries/events` pour plus d'informations. - -Créer des Helpers -================= - -Vous pouvez créer des classes de helper personnalisées pour les utiliser dans -votre application ou dans vos plugins. -Comme la plupart des components de CakePHP, les classes de helper ont quelques -conventions: - -* Les fichiers de classe Helper doivent être placés dans **src/View/Helper**. - Par exemple: **src/View/Helper/LinkHelper.php** -* Les classes Helper doivent être suffixées avec ``Helper``. Par exemple: - ``LinkHelper``. -* Quand vous référencez les noms de classe helper, vous devez omettre le suffixe - ``Helper``. Par exemple: ``$this->loadHelper('Link');``. - -Vous devrez étendre ``Helper`` pour vous assurer que les choses fonctionnent -correctement:: - - /* src/View/Helper/LinkHelper.php */ - namespace App\View\Helper; - - use Cake\View\Helper; - - class LinkHelper extends Helper - { - public function makeEdit($title, $url) - { - // La logique pour créer le lien spécialement formaté se place ici - } - } - -Inclure d'autres Helpers ------------------------- - -Vous souhaitez peut-être utiliser quelques fonctionnalités déjà existantes dans -un autre helper. Pour faire cela, vous pouvez spécifier les helpers que -vous souhaitez utiliser avec un tableau ``$helpers``, formaté comme vous le -feriez dans un controller:: - - /* src/View/Helper/LinkHelper.php (utilisant d'autres helpers) */ - - namespace App\View\Helper; - - use Cake\View\Helper; - - class LinkHelper extends Helper - { - public $helpers = ['Html']; - - public function makeEdit($title, $url) - { - // Utilise le Helper Html pour afficher la sortie - // des données formatées: - - $link = $this->Html->link($title, $url, ['class' => 'edit']); - - return '
      ' . $link . '
      '; - } - } - -.. _using-helpers: - -Utiliser votre Helper ---------------------- - -Une fois que vous avez créé votre helper et l'avez placé dans -**src/View/Helper/**, vous pouvez le charger dans vos vues:: - - class AppView extends View - { - public function initialize(): void - { - parent::initialize(); - $this->loadHelper('Link'); - } - } - -Une fois que votre helper est chargé, vous pouvez l'utiliser dans vos vues en -accédant à l'attribut de vue correspondant:: - - - Link->makeEdit('Changez cette Recette', '/recipes/edit/5') ?> - -.. note:: - - ``HelperRegistry`` va tenter de charger automatiquement les helpers qui ne - sont pas spécifiquement identifiés dans votre ``Controller``. - -Accéder aux variables de la View dans votre Helper --------------------------------------------------- - -Si vous voulez accéder à une variable de la View dans votre helper, vous pouvez -utiliser ``$this->_View->viewVars``, comme illustré ci-dessous:: - - class AwesomeHelper extends Helper - { - - public $helpers = ['Html']; - - public someMethod() - { - // Définit la meta description - echo $this->Html->meta( - 'description', $this->_View->viewVars['metaDescription'], ['block' => 'meta'] - ); - } - } - -Rendre un Element de Vue dans votre Helper ------------------------------------------- - -Si vous souhaiter rendre un Element dans votre Helper, vous pouvez utiliser -**$this->_View->element()** comme ceci:: - - class AwesomeHelper extends Helper - { - public someFunction() - { - // Affiche directement dans votre helper - echo $this->_View->element('/path/to/element',['foo'=>'bar','bar'=>'foo']); - - // ou le retourne dans votre vue - return $this->_View->element('/path/to/element',['foo'=>'bar','bar'=>'foo']); - } - } - -.. _helper-api: - -Classe Helper -============= - -.. php:class:: Helper - -Callbacks ---------- - -En implémentant une méthode de callback dans un helper, CakePHP va -automatiquement inscrire votre helper à l'évènement correspondant. A la -différence des versions précédentes de CakePHP, vous *ne* devriez *pas* appeler -``parent`` dans vos callbacks, puisque la classe Helper de base n'implémente -aucune des méthodes de callback. - -.. php:method:: beforeRenderFile(Event $event, $viewFile) - - Est appelé avant que tout fichier de vue soit rendu. Cela inclut les - elements, les vues, les vues parentes et les layouts. - -.. php:method:: afterRenderFile(Event $event, $viewFile, $content) - - Est appelé après que tout fichier de vue est rendu. Cela inclut les - elements, les vues, les vues parentes et les layouts. Un callback - peut modifier et retourner ``$content`` pour changer la manière dont - le contenu rendu est affiché dans le navigateur. - -.. php:method:: beforeRender(Event $event, $viewFile) - - La méthode ``beforeRender()`` est appelée après la méthode - ``beforeRender()`` du controller, mais avant les rendus du controller de la - vue et du layout. Reçoit le fichier à rendre en argument. - -.. php:method:: afterRender(Event $event, $viewFile) - - Est appelé après que la vue est rendu, mais avant que le rendu du - layout ait commencé. - -.. php:method:: beforeLayout(Event $event, $layoutFile) - - Est appelé avant que le rendu du layout commence. Reçoit le nom du fichier - layout en argument. - -.. php:method:: afterLayout(Event $event, $layoutFile) - - Est appelée après que le rendu du layout est fini. Reçoit le nom du fichier - layout en argument. - -.. meta:: - :title lang=fr: Helpers (Assistants) - :keywords lang=fr: classe php,fonction time,couche de présentation,puissance du processeur,ajax,balise,tableau,fonctionnalité,logique,syntaxe,éléments,cakephp,plugins diff --git a/fr/views/helpers/breadcrumbs.rst b/fr/views/helpers/breadcrumbs.rst deleted file mode 100644 index 417d889164..0000000000 --- a/fr/views/helpers/breadcrumbs.rst +++ /dev/null @@ -1,206 +0,0 @@ -Breadcrumbs -########### - -.. php:namespace:: Cake\View\Helper - -.. php:class:: BreadcrumbsHelper(View $view, array $config = []) - -BreadcrumbsHelper vous offre la possibilité de gérer la création et le rendu -de vos *breadcrumbs* (fil d'Ariane) pour vos applications. - -Créer un fil d'Ariane -===================== - -Vous pouvez ajouter un élément à la liste en utilisant la méthode ``add()``. -Elle accepte trois arguments: - -- ``title`` La chaîne affichée comme titre de l'élément. -- ``url`` Une chaîne ou un tableau de paramètres qui sera passé au :doc:`/views/helpers/url` -- ``options`` Un tableau d'attribut pour les templates ``item`` et - ``itemWithoutLink``. Référez-vous à la section sur :ref:`la définition d'attributs pour un élément ` - pour plus d'informations. - -En plus de pouvoir ajouter un élément à la fin de la liste, vous pouvez effectuer -diverses opérations:: - - // Ajoute à la fin de la liste - $this->Breadcrumbs->add( - 'Produits', - ['controller' => 'products', 'action' => 'index'] - ); - - // Ajoute plusieurs éléments à la fin de la liste - $this->Breadcrumbs->add([ - ['title' => 'Produits', 'url' => ['controller' => 'products', 'action' => 'index']], - ['title' => 'Nom du produit', 'url' => ['controller' => 'products', 'action' => 'view', 1234]] - ]); - - // Ajoute l'élément en premier dans la liste - $this->Breadcrumbs->prepend( - 'Produits', - ['controller' => 'products', 'action' => 'index'] - ); - - // Ajoute plusieurs éléments en premier dans la liste - $this->Breadcrumbs->prepend([ - ['title' => 'Produits', 'url' => ['controller' => 'products', 'action' => 'index']], - ['title' => 'Nom du produit', 'url' => ['controller' => 'products', 'action' => 'view', 1234]] - ]); - - // Insert l'élément à un index spécifique. Si l'index n'existe pas - // une exception sera levée. - $this->Breadcrumbs->insertAt( - 2, - 'Produits', - ['controller' => 'products', 'action' => 'index'] - ); - - // Insert l'élément avant un autre, basé sur le titre. - // Si l'élément ne peut pas être trouvé, une exception sera levée - $this->Breadcrumbs->insertBefore( - 'Un nom de produit', // le titre de l'élément devant lequel on veut faire l'insertion - 'Produits', - ['controller' => 'products', 'action' => 'index'] - ); - - // Insert l'élément après un autre, basé sur le titre. - // Si l'élément ne peut pas être trouvé, une exception sera levée - $this->Breadcrumbs->insertAfter( - 'Un nom de produit', // le titre de l'élément derrière lequel on veut faire l'insertion - 'Produits', - ['controller' => 'products', 'action' => 'index'] - ); - -Utilisez ces méthodes vous donne la possibilité de contourner la façon dont -CakePHP rend les vues. Puisque les templates et les layouts sont rendu de -l'intérieur vers l'extérieur (entendez par là que les éléments inclus sont -rendus avant les éléments qui les inclusent), cela vous permet de définir -précisemment où vous voulez ajouter un élément. - -Afficher le fil d'Ariane -======================== - -Affichage simple ----------------- - -Après avoir ajouté un élément à la liste, vous pouvez facilement l'afficher -avec la méthode ``render()``. -Cette méthode accepte deux tableaux comme arguments: - -- ``$attributes`` : Un tableau d'attributs qui seront appliqués au template - ``wrapper``. Cela vous donne lapossibilité d'ajouter des attributs au tag - HTML utilisé. Il accepte également la clé ``templateVars`` ce qui vous permet - d'insérer des variables de template personnalisées dans le template. -- ``$separator`` : Un tableau d'attributs pour le template ``separator``. - Voici les propriétés disponibles: - - - ``separator`` La chaîne qui sera utilisée comme séparateur - - ``innerAttrs`` Pour fournir des attributs dans le cas où votre séparateur - est en deux éléments - - ``templateVars`` Vous permet de définir des variables de templates - personnalisées dans le template - - Toutes les autres propriétés seront converties en attributs HTML et - remplaceront la clé ``attrs`` dans le template. Si vous fournissez un tableau - vide (le défaut) pour cet argument, aucun séparateur ne sera affiché. - -Voici un exemple d'affichage d'un fil d'Ariane:: - - echo $this->Breadcrumbs->render( - ['class' => 'breadcrumbs-trail'], - ['separator' => ''] - ); - -Personnaliser l'affichage -------------------------- - -Personnaliser les templates -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Le BreadcrumbsHelper utiliser le trait ``StringTemplateTrait`` en interne, ce -qui vous permet de facilement personnaliser le rendu des différentes chaînes -HTML qui composent votre fil d'Ariane. -Quatre templates sont inclus. Voici leur déclaration par défaut:: - - [ - 'wrapper' => '{{content}}
    ', - 'item' => '{{title}}{{separator}}', - 'itemWithoutLink' => '{{title}}{{separator}}', - 'separator' => '{{separator}}' - ] - -Vous pouvez facilement personnaliser ces templates via la méthode ``setTemplates()`` -du ``StringTemplateTrait``:: - - $this->Breadcrumbs->setTemplates([ - 'wrapper' => '', - ]); - -Puisque les templates supportent l'option ``templateVars``, vous pouvez ajouter -vos propres variables de templates:: - - $this->Breadcrumbs->setTemplates([ - 'item' => '{{icon}}{{title}}{{separator}}' - ]); - -Et pour définir le paramètre ``{{icon}}``, vous n'avez qu'à la spécifier -lorsque vous ajouter l'élément à la liste:: - - $this->Breadcrumbs->add( - 'Produits', - ['controller' => 'products', 'action' => 'index'], - [ - 'templateVars' => [ - 'icon' => '' - ] - ] - ); - -.. _defining_attributes_item: - -Defining Attributes for the Item -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si vous voulez déclarez des attributs HTML à l'élément et ses sous-éléments, -vous pouvez utiliser la clé ``innerAttrs`` supportée par l'argument ``$options``. -Toutes les clés exceptées ``innerAttrs`` et ``templateVars`` seront affichés -comme attributs HTML:: - - $this->Breadcrumbs->add( - 'Produits', - ['controller' => 'products', 'action' => 'index'], - [ - 'class' => 'products-crumb', - 'data-foo' => 'bar', - 'innerAttrs' => [ - 'class' => 'inner-products-crumb', - 'id' => 'the-products-crumb' - ] - ] - ); - - // En se basant sur le template par défaut, la chaîne suivante sera affichée: -
  • - Produits -
  • - -Réinitialiser la Liste d'éléments -================================= - -Vous pouvez réinitialiser la liste d'éléments à l'aide de la méthode ``reset()``. -Ceci est particulièrement utile quand vous souhaitez modifier les éléments et -complètement réinitialiser la liste:: - - $crumbs = $this->Breadcrumbs->getCrumbs(); - $crumbs = collection($crumbs)->map(function ($crumb) { - $crumb['options']['class'] = 'breadcrumb-item'; - - return $crumb; - })->toArray(); - - $this->Breadcrumbs->reset()->add($crumbs); - -.. meta:: - :title lang=fr: BreadcrumbsHelper - :description lang=fr: Le BreadcrumbsHelper de CakePHP vous permet de gérer facilement un fil d'Ariane - :keywords lang=fr: breadcrumbs helper,cakephp crumbs,fil d'ariane,cakephp fil d'ariane diff --git a/fr/views/helpers/flash.rst b/fr/views/helpers/flash.rst deleted file mode 100644 index 508e61c110..0000000000 --- a/fr/views/helpers/flash.rst +++ /dev/null @@ -1,68 +0,0 @@ -Flash -##### - -.. php:namespace:: Cake\View\Helper - -.. php:class:: FlashHelper(View $view, array $config = []) - -FlashHelper fournit une façon de rendre les messages flash qui sont définis dans -``$_SESSION`` par :doc:`FlashComponent `. -:doc:`FlashComponent ` et FlashHelper -utilisent principalement des elements pour rendre les messages flash. Les -elements flash se trouvent dans le répertoire **templates/element/flash**. -Vous remarquerez que le template de l'App de CakePHP est livré avec trois -éléments flash : **success.php**, **default.php** et **error.php**. - -Rendre les Messages Flash -========================= - -Pour rendre un message flash, vous pouvez simplement utiliser la méthode -``render()`` du FlashHelper:: - - Flash->render() ?> - -Par défaut, CakePHP utilise une clé "flash" pour les messages flash dans une -session. Mais si vous spécifiez une clé lors de la définition du message -flash dans :doc:`FlashComponent `, vous -pouvez spécifier la clé flash à rendre:: - - Flash->render('other') ?> - -Vous pouvez aussi surcharger toutes les options qui sont définies dans -FlashComponent:: - - // Dans votre Controller - $this->Flash->set('The user has been saved.', [ - 'element' => 'success' - ]); - - // Dans votre View: Va utiliser great_success.php au lieu de succcess.php - Flash->render('flash', [ - 'element' => 'great_success' - ]); - -.. note:: - - Quand vous construisez vos propres templates de messages flash, assurez- - vous de correctement encoder les données utilisateurs. CakePHP n'échappera - pas les paramètres passés aux templates des messages flash pour vous. - -Pour plus d'informations sur le tableau d'options disponibles, consultez la -section :doc:`FlashComponent `. - -Préfixe de Routage et Messages Flash -==================================== - -Si vous avez configuré un préfixe de Routage, vous pouvez maintenant stocker vos -elements de messages Flash dans **templates/{Prefix}/element/flash**. De -cette manière, vous pouvez avoir des layouts de messages spécifiques en -fonction des différentes parties de votre application : par exemple, avoir des -layouts différents pour votre front-end et votre administration. - -Les Messages Flash et les Themes -================================ - -FlashHelper utilise des elements normaux pour afficher les messages et va donc -correspondre à n'importe quel thème que vous avez éventuellement spécifié. Donc -quand votre thème a un fichier **templates/element/flash/error.php**, il sera -utilisé, comme avec tout Element et View. diff --git a/fr/views/helpers/form.rst b/fr/views/helpers/form.rst deleted file mode 100644 index dc1e838e7b..0000000000 --- a/fr/views/helpers/form.rst +++ /dev/null @@ -1,2670 +0,0 @@ -Form -#### - -.. php:namespace:: Cake\View\Helper - -.. php:class:: FormHelper(View $view, array $config = []) - -Le FormHelper prend en charge la plupart des opérations lourdes de la création -de formulaire. Le FormHelper se concentre sur la possibilité de créer des -formulaires rapidement, d'une manière qui permettra de rationaliser la -validation, la re-population et la mise en page (layout). Le FormHelper est -aussi flexible - il va faire à peu près tout pour vous en utilisant les -conventions, ou vous pouvez utiliser des méthodes spécifiques pour ne prendre -uniquement que ce dont vous avez besoin. - -Création de Formulaire -====================== - -.. php:method:: create(mixed $context = null, array $options = []) - -* ``$context`` - Le contexte pour lequel le formulaire est créé. Cela peut être - une Entity de l'ORM, un retour (ResultSet) de l'ORM, un tableau de meta-données - ou ``false/null`` (dans le cas où vous créez un formulaire qui ne serait lié à - aucun Model). -* ``$options`` - Un tableau d'options et / ou d'attributs HTML. - -La première méthode que vous aurez besoin d'utiliser pour tirer pleinement -profit du FormHelper est ``create()``. Cette méthode affichera une balise -d'ouverture de formulaire. - -Tous les paramètres sont optionnels. Si ``create()`` est appelée sans paramètre, -CakePHP supposera que vous voulez créer un formulaire en rapport avec le -controller courant, via l'URL actuelle. Par défaut, la méthode de soumission par -des formulaires est POST. Si vous appelez ``create()`` dans une vue pour -``UsersController::add()``, vous verrez la sortie suivante dans la vue: - -.. code-block:: html - - - -L'argument ``$context`` est utilisé comme 'context' du formulaire. Il y a -plusieurs contextes de formulaires intégrés et vous pouvez ajouter les vôtres, -ce que nous allons voir dans la prochaine section. Ceux intégrés correspondent -aux valeurs suivantes de ``$context``: - -* Une instance ``Entity`` ou un iterateur qui mappe vers - `EntityContext `_; - ce contexte permet au FormHelper de fonctionner avec les retours de l'ORM - intégré. - -* Un tableau contenant la clé ``schema``, qui mappe vers - `ArrayContext `_ - ce qui vous permet de créer des structures simples de données pour construire - des formulaires. - -* ``null`` et ``false`` mappe vers - `NullContext `_; - cette classe de contexte satisfait simplement l'interface requise par FormHelper. - Ce contexte est utile si vous voulez construire un formulaire court qui ne nécessite - pas de persistance via l'ORM. - -Une fois qu'un formulaire a été créé avec un contexte, tous les inputs que vous -créez vont utiliser le contexte actif. Dans le cas d'un formulaire basé sur -l'ORM, FormHelper peut accéder aux données associées, aux erreurs de validation -et aux metadata du schema. Vous pouvez fermer le contexte actif en utilisant la -méthode ``end()``, ou en appelant ``create()`` à nouveau. Pour créer un -formulaire pour une entity, faites ce qui suit:: - - // Si vous êtes sur /articles/add - // $article devra être une entity Article vide. - echo $this->Form->create($article); - -Affichera: - -.. code-block:: html - - - -Celui-ci va POSTer les données de formulaire à l'action ``add()`` de -``ArticlesController``. Cependant, vous pouvez utiliser la même logique pour -créer un formulaire d'édition. Le FormHelper utilise l'objet ``Entity`` pour -détecter automatiquement s'il faut créer un formulaire d'ajout (*add*) ou un -d'édition (*edit*). Si l'entity fournie n'est pas 'nouvelle', le form va être -créé comme un formulaire d'édition. - -Par exemple, si nous naviguons vers **http://example.org/articles/edit/5**, -nous pourrions faire ce qui suit:: - - // src/Controller/ArticlesController.php: - public function edit($id = null) - { - if (empty($id)) { - throw new NotFoundException; - } - $article = $this->Articles->get($id); - // La logique d'enregistrement ici - $this->set('article', $article); - } - - // View/Articles/edit.php: - // Puisque $article->isNew() est false, nous aurons un formulaire d'édition - Form->create($article) ?> - -Affichera: - -.. code-block:: html - - - - -.. note:: - - Puisque c'est un formulaire d'édition, un champ input caché est généré - pour surcharger la méthode HTTP par défaut. - -Dans certains cas, l'ID de l'entité est automatiquement ajoutée à la fin de -l'URL ``action`` du formulaire. Si vous voulez *éviter* qu'un ID soit ajouté à -l'URL, vous pouvez passer une chaîne dans ``$options['url']``, telle que -``'/my-account'`` ou -``\Cake\Routing\Router::url(['controller' => 'Users', 'action' => 'myAccount'])``. - -Options pour la Création de Formulaire --------------------------------------- - -Le tableau ``$options`` est l'endroit où se passe l'essentiel de la -configuration du formulaire. Ce tableau spécial peut contenir un -certain nombre de paires clé-valeur différentes qui affectent la façon dont -la balise form est générée. Voici les valeurs autorisées: - -* ``'type'`` - Vous permet de choisir le type de formulaire à créer. Si vous ne - fournissez pas de type, il sera automatiquement détecté en fonction du 'context' - du formulaire. Cette option peut prendre une des valeurs suivantes: - - * ``'get'`` - Définira la ``method`` du formulaire à GET. - * ``'file'`` - Définira la ``method`` du formulaire à POST et le ``'enctype'`` - à "multipart/form-data". - * ``'post'`` - Définira la ``method`` à POST. - * ``'put', 'delete', 'patch'`` - Écrasera la méthode HTTP avec PUT, DELETE ou - PATCH, respectivement, quand le formulaire sera soumis. - -* ``'method'`` - Vous permet de définir explicitement la ``method`` du formulaire. - Les valeurs autorisés sont les même que pour le paramètre ci-dessus. - -* ``'url'`` - Permet de spécifier l'URL à laquelle le formulaire postera les données. - Peut être une chaîne ou un tableau de paramètre d'URL. - -* ``'encoding'`` - Permet de définir l'attribut ``accept-charset`` du formulaire. - Par défaut, la valeur de ``Configure::read('App.encoding')`` sera utilisée. - -* ``'enctype'`` - Vous permet de définir l'encodage du formulaire de manière - explicite. - -* ``'templates'`` - Les templates pour les éléments à utiliser pour ce formulaire. - Tous les templates fournis écraseront les templates déjà chargés. Ce paramètre - peut soit être un nom de fichier (sans extension) du dossier ``/config`` ou un - tableau de templates. - -* ``'context'`` - Options supplémentaires qui seront fournies à la classe de - 'context' liée au formulaire. (Par exemple, le 'context' ``EntityContext`` - accepte une option ``table`` qui permet de définir la classe Table sur - laquelle le formulaire devra se baser). - -* ``'idPrefix'`` - Préfixe à utiliser pour les attributs ``id`` des éléments du - formulaire. - -* ``'templateVars'`` - Vous permet de définir des variables de template pour le - template ``formStart``. - -* ``autoSetCustomValidity`` - Défini à ``true`` pour utiliser des messages de - validation personnalisés pour required et notBlank dans le message de validité - HTML5 du contrôle. Par défaut ``true``. - -.. tip:: - - Vous pouvez, en plus des options définies ci-dessus, définir dans l'argument - ``$options``, tous les attributs HTML que vous pourriez vouloir passer à - l'élément ``form`` (des classes, des attributs ``data``, etc.). - -.. _form-values-from-query-string: - -Récupérer les valeurs du formulaire depuis d'autres sources ------------------------------------------------------------ - -Les sources de valeurs du FormHelper définissent d'où les éléments du -formulaire reçoivent leurs valeurs. - -Les sources supportées sont ``context``, ``data`` et ``query``. Vous pouvez -utiliser une ou plusieurs de ces sources en définissant l'option -``valueSources`` ou en appelant ``setValuesSource()``. Tous les éléments générés -par ``FormHelper`` vont collecter leurs valeurs à partir de ces sources, dans -l'ordre que vous aurez défini. - -Par défaut, Formhelper récupère ses valeurs depuis les ``data`` ou le "context", -c'est-à-dire qu'il va récupérer les données avec ``$request->getData()`` ou, si -elles sont absentes, à partir des données du contexte actif, qui sont les -données de l'entity dans le cas de ``EntityContext``. - -Cependant, si vous construisez un formulaire qui a besoin d'aller récupérer ses -valeurs dans la query string, vous pouvez utiliser ``valueSource()`` pour -définir où le ``FormHelper`` doit aller récupérer les valeurs de ses champs:: - - // Donner la priorité à la query string plutôt qu'au contexte - echo $this->Form->create($article, [ - 'type' => 'get', - 'valueSources' => ['query', 'context'] - ]); - - // Même effet: - echo $this->Form - ->setValueSources(['query', 'context']) - ->create($articles, ['type' => 'get']); - -Lorsque les données reçues ont besoin d'être traitées par l'entity (c'est-à-dire -les convertir, traiter une table ou computer des entités) et affichées après -une ou plusieurs soumissions de formulaire pendant lesquelles les données de la -requête sont conservées, vous aurez besoin de placer ``context`` en premier:: - - // Donner la priorité au contexte par rapport aux données de la requête: - echo $this->Form->create($article, - 'valueSources' => ['context', 'data'] - ]); - -Les sources définies seront réinitialisées à leur valeur par défaut -``['data', 'context']`` quand ``end()`` sera appelée. - -Changer la méthode HTTP pour un Formulaire ------------------------------------------- - -En utilisant l'option ``type``, vous pouvez changer la méthode HTTP qu'un -formulaire va utiliser:: - - echo $this->Form->create($article, ['type' => 'get']); - -Affichera: - -.. code-block:: html - - - -En spécifiant ``file`` à l'option ``type``, cela changera la méthode de -soumission à 'post', et ajoutera un ``enctype`` "multipart/form-data" dans le tag -du formulaire. Vous devez l'utiliser si vous avez des demandes de fichiers dans -votre formulaire. L'absence de cet attribut ``enctype`` empêchera le fonctionnement de -l'envoi de fichiers:: - - echo $this->Form->create($article, ['type' => 'file']); - -Affichera: - -.. code-block:: html - - - -Quand vous utilisez ``put``, ``patch`` ou ``delete`` dans l'option ``type``, -votre formulaire aura un fonctionnement équivalent à un formulaire de type -'post', mais quand il sera envoyé, la méthode de requête HTTP sera respectivement -réécrite avec 'PUT', 'PATCH' ou 'DELETE'. Cela permet à CakePHP d'émuler un support -REST dans les navigateurs web. - -Définir l'URL pour le Formulaire --------------------------------- - -Utiliser l'option ``url`` vous permet de diriger le formulaire vers une -action spécifique dans votre controller courant ou dans toute votre application. -Par exemple, si vous voulez diriger le formulaire vers une action ``publish()`` -du controller courant, vous pouvez fournir le tableau ``$options`` comme suit:: - - echo $this->Form->create($article, ['url' => ['action' => 'publish']]); - -Affichera: - -.. code-block:: html - - - -Si l'action que vous désirez appeler avec le formulaire n'est pas dans le -controller courant, vous pouvez spécifier une URL dans le formulaire. L'URL -fournie peut être relative à votre application CakePHP:: - - echo $this->Form->create(null, [ - 'url' => [ - 'controller' => 'Articles', - 'action' => 'publish' - ] - ]); - -Affichera: - -.. code-block:: html - - - -ou pointer vers un domaine extérieur:: - - echo $this->Form->create(null, [ - 'url' => 'https://www.google.com/search', - 'type' => 'get' - ]); - -Affichera: - -.. code-block:: html - - - -Utilisez ``'url' => false`` si vous ne souhaitez pas d'URL en tant qu'action de -formulaire. - -Utiliser des Validateurs Personnalisés --------------------------------------- - -Les models vont souvent avoir des ensembles de validation multiples et vous -voudrez que FormHelper marque les champs nécessaires basés sur les règles de -validation spécifiques que l'action de votre controller est en train -d'appliquer. Par exemple, votre table Users a des règles de validation -spécifiques qui s'appliquent uniquement quand un compte est enregistré:: - - echo $this->Form->create($user, [ - 'context' => ['validator' => 'register'] - ]); - -L'exemple précédent va utiliser les règles de validation définies dans le -validateur ``register``, définies par ``UsersTable::validationRegister()``, -pour le ``$user`` et toutes les associations liées. Si vous créez un -formulaire pour les entities associées, vous pouvez définir les règles de -validation pour chaque association en utilisant un tableau:: - - echo $this->Form->create($user, [ - 'context' => [ - 'validator' => [ - 'Users' => 'register', - 'Comments' => 'default' - ] - ] - ]); - -Ce qui est au-dessus va utiliser ``register`` pour l'utilisateur, et ``default`` -pour les commentaires de l'utilisateur. FormHelper utilise les validateurs pour -générer les attributs HTML5 *required*, les attributs ARIA appropriés, et -définir les messages d'erreur avec la `browser validator API -`_ -. Si vous voulez désactiver les messages de validation HTML5, utilisez:: - - $this->Form->setConfig('autoSetCustomValidity', false); - -Cela ne désactivera pas les attributs``required``/``aria-required``. - -Créer des Classes de Contexte ------------------------------ - -Alors que les classes de contexte intégrées essaient de couvrir les cas -habituels que vous pouvez rencontrer, vous pouvez avoir besoin de construire -une nouvelle classe de contexte si vous utilisez un ORM différent. Dans ces -situations, vous devrez implémenter `Cake\\View\\Form\\ContextInterface -`_ . Une -fois que vous avez implémenté cette interface, vous pouvez connecter votre -nouveau contexte dans le FormHelper. Le mieux est souvent de le faire dans un -event listener ``View.beforeRender``, ou dans une classe de vue de -l'application:: - - $this->Form->addContextProvider('myprovider', function ($request, $data) { - if ($data['entity'] instanceof MyOrmClass) { - return new MyProvider($data); - } - }); - -Les fonctions de fabrique de contexte sont l'endroit où vous pouvez ajouter la -logique pour vérifier les options du formulaire pour le type d'entity approprié. -Si une donnée d'entrée correspondante est trouvée, vous pouvez retourner un -objet. Si n'y a pas de correspondance, retournez null. - -.. _automagic-form-elements: - -Création d'éléments de Formulaire -================================= - -.. php:method:: control(string $fieldName, array $options = []) - -* ``$fieldName`` - Nom du champ (attribut ``name``) de l'élément sous la forme - ``'Modelname.fieldname'``. -* ``$options`` - Un tableau d'option qui peut inclure à la fois des :ref:`control-specific-options` - et des options d'autres méthodes (que la méthode ``control()`` utilise en interne - pour générer les différents éléments HTML) ainsi que attribut HTML valide. - -La méthode ``control()`` vous permet de générer des inputs de formulaire -complets. Ces inputs inclueront une div enveloppante, un label, un widget -d'input, et une erreur de validation si besoin. En utilisant les metadonnées -dans le contexte du formulaire, cette méthode va choisir un type d'input -approprié pour chaque champ. En interne, ``control()`` utilise les autres -méthodes de FormHelper. - -.. tip:: - - Veuillez notez que, même si les éléments générés par la méthode ``control()`` - sont appelés des "inputs" sur cette page, techniquement parlant, la méthode - ``control()`` peut générer non seulement n'importe quel type de balise - ``input`` mais aussi tous les autres types d'éléments HTML de formulaire - (``select``, ``button``, ``textarea``). - -Par défaut, la méthode ``control()`` utilisera les templates de widget suivant:: - - 'inputContainer' => '
    {{content}}
    ' - 'input' => '' - -En cas d'erreurs de validation, elle utilisera également:: - - 'inputContainerError' => '
    {{content}}{{error}}
    ' - -Le type d'élément créé, dans le cas où aucune autre option n'est fournie pour -générer le type d'élément, est induit par l'introspection du Model et dépendra -du datatype de la colonne en question: - -Column Type - Champ de formulaire résultant -string, uuid (char, varchar, etc.) - text -boolean, tinyint(1) - checkbox -decimal - number -float - number -integer - number -text - textarea -text, avec le nom de password, passwd, ou psword - password -text, avec le nom de email - email -text, avec le nom de tel, telephone, ou phone - tel -date - date -datetime, timestamp - datetime-local -datetimefractional, timestampfractional - datetime-local -time - time -month - month -year - select avec des années -binary - file - -Le paramètre ``$options`` vous permet de choisir un type d'input spécifique si -vous avez besoin:: - - echo $this->Form->control('published', ['type' => 'checkbox']); - -.. tip:: - - Veuillez notez que, par défaut, générer un élément via la méthode ``control()`` - générera systématiquement un ``div`` autour de l'élément généré. - Cependant, générer le même élément mais avec la méthode spécifique du ``FormHelper`` - (par exemple ``$this->Form->checkbox('published');``) ne générera pas, dans la - majorité des cas, un ``div`` autour de l'élément. En fonction de votre cas d'usage, - utilisez l'une ou l'autre méthode. - -.. _html5-required: - -Un nom de classe ``required`` sera ajouté à la ``div`` enveloppante si les règles de -validation pour le champ du model indiquent qu'il est requis et ne peut pas être -vide. Vous pouvez désactiver les ``required`` automatiques en utilisant l'option -``required``:: - - echo $this->Form->control('title', ['required' => false]); - -Pour empêcher la validation faite par le navigateur pour l'ensemble du -formulaire, vous pouvez définir l'option ``'formnovalidate' => true`` pour le -bouton input que vous générez en utilisant -:php:meth:`~Cake\\View\\Helper\\FormHelper::submit()` ou définir -``'novalidate' => true`` dans les options pour -:php:meth:`~Cake\\View\\Helper\\FormHelper::create()`. - -Par exemple, supposons que votre model User intègre les champs pour un -*username* (varchar), *password* (varchar), *approved* (datetime) et -*quote* (text). Vous pouvez utiliser la méthode ``control()`` du FormHelper pour -créer les bons inputs pour tous ces champs de formulaire:: - - echo $this->Form->create($user); - // Va générer un input type="text" - echo $this->Form->control('username'); - // Va générer un input type="password" - echo $this->Form->control('password'); - // En partant du principe que 'approved' est un "datetime" ou un "timestamp", - // va générer un input de type "datetime-local" - echo $this->Form->control('approved'); - // Va générer un textarea - echo $this->Form->control('quote'); - - echo $this->Form->button('Ajouter'); - echo $this->Form->end(); - -Un exemple plus complet montrant quelques options pour le champ de date:: - - echo $this->Form->control('birth_date', [ - 'label' => 'Date de naissance', - 'min' => date('Y') - 70, - 'max' => date('Y') - 18, - ]); - -Outre les :ref:`control-specific-options` vues ci-dessus, vous pouvez spécifier -n'importe quelle option acceptée par la méthode spécifique au widget choisi (ou -déduit par CakePHP) et n'importe quel attribut HTML (par exemple ``onfocus``). - -Si vous voulez un ``select`` utilisant une relation *belongsTo* ou *hasOne*, -vous pouvez ajouter ceci dans votre controller Users (en supposant que -l'User *belongsTo* Group):: - - $this->set('groups', $this->Users->Groups->find('list')->all()); - -Après cela, ajoutez les lignes suivantes à votre template de vue du formulaire:: - - echo $this->Form->control('group_id', ['options' => $groups]); - -Pour créer un ``select`` pour l'association *belongsToMany* Groups, vous pouvez -ajouter ce qui suit dans votre UsersController:: - - $this->set('groups', $this->Users->Groups->find('list')->all()); - -Ensuite, ajouter les lignes suivantes à votre template de vue:: - - echo $this->Form->control('groups._ids', ['options' => $groups]); - -Si votre nom de model est composé de deux mots ou plus (ex. "UserGroup"), -quand vous passez les données en utilisant ``set()`` vous devrez nommer vos -données dans un `format CamelCase `_ -(les Majuscules séparent les mots) et au pluriel comme ceci:: - - $this->set('userGroups', $this->UserGroups->find('list')); - -.. note:: - - N'utilisez pas ``FormHelper::control()`` pour générer - les boutons submit. Utilisez plutôt - :php:meth:`~Cake\\View\\Helper\\FormHelper::submit()`. - -Conventions de Nommage des Champs ---------------------------------- - -Lors de la création de widgets, vous devez nommer vos champs d'après leur -attribut correspondant dans l'entity du formulaire. Par exemple, si vous -créez un formulaire pour un ``$article``, vous créez des champs nommés d'après -les propriétés. Par exemple -``title``, ``body`` et ``published``. - -Vous pouvez créer des inputs pour les models associés, ou pour des models -arbitraires en le passant dans ``association.fieldname`` en premier paramètre:: - - echo $this->Form->control('association.fieldname'); - -Tout point dans vos noms de champs sera converti en données de requête -imbriquées. Par exemple, si vous créez un champ avec un nom -``0.comments.body`` vous aurez un nom d'attribut qui sera -``0[comments][body]``. Cette convention coorespond à celle de l'ORM. Plus de -détails pour tous les types d'associations se trouvent -dans la section :ref:`associated-form-inputs`. - -Lors de la création d'inputs de type datetime, FormHelper va ajouter un -suffixe au champ. Vous pouvez remarquer des champs supplémentaires nommés -``year``, ``month``, ``day``, ``hour``, ``minute``, ou ``meridian`` qui -ont été ajoutés. Ces champs seront automatiquement convertis en objets -``DateTime`` quand les entities seront traitées. - -.. _control-specific-options: - -Options pour la méthode control() ---------------------------------- - -``FormHelper::control()`` supporte un nombre important d'options via son -paramètre ``$options``. En plus de ses propres options, ``control()`` -accepte des options pour les champs input générés (devinés ou choisis, comme les -``checkbox`` ou les ``textarea``), ou encore les attributs HTML. Ce qui suit va -couvrir les options spécifiques de ``FormHelper::control()``. - -* ``$options['type']`` - Une chaîne qui précise le type de widget à générer. - En plus des types de champs vus dans :ref:`automagic-form-elements`, vous - pouvez aussi créer input de type ``file``, ``password`` et tous les types - supportés par HTML5. En spécifiant vous-même le type de l'élément à générer, - vous écraserez le type automatique deviné par l'introspection du Model. Le défaut - est ``null``:: - - echo $this->Form->control('field', ['type' => 'file']); - echo $this->Form->control('email', ['type' => 'email']); - - Affichera: - - .. code-block:: html - -
    - - -
    - - -* ``$options['label']`` Soit une chaîne qui sera utilisée comme valeur pour - l'élément HTML ``