diff --git a/pt/appendices.rst b/pt/appendices.rst index 4e51a999f9..804abaa20c 100644 --- a/pt/appendices.rst +++ b/pt/appendices.rst @@ -4,16 +4,31 @@ Apêndices Os apêndices contêm informações sobre os novos recursos introduzidos em cada versão e a forma de executar a migração entre versões. -Guia de Migração para a versão 4.x +Guia de Migração +================ + +:doc:`appendices/migration-guides` + +Retrocompatibilidade por Adaptação ================================== -.. toctree:: - :maxdepth: 1 +Se você precisar/quiser corrigir o comportamento do 4.x ou migrar parcialmente em etapas, confira +o plugin `Shim `__ que pode ajudar a mitigar algumas alterações que quebram o BC. + +Compatibilidade Futura +====================== + +A correção de compatibilidade com versões anteriores pode preparar seu aplicativo 4.x para o próximo grande +lançamento (5.x). - appendices/4-0-migration-guide +Se você já deseja aplicar o comportamento do 5.x no 4.x, confira o `plugin Shim +`__. Este plugin visa mitigar +algumas falhas de compatibilidade com versões anteriores e ajudar a retroportar recursos do 5.x para +o 4.x. Quanto mais próximo o seu aplicativo 3.x estiver do 4.x, menor será a diferença +das mudanças e mais suave será a atualização final. Informações Gerais -================== +=================== .. toctree:: :maxdepth: 1 @@ -23,4 +38,4 @@ Informações Gerais .. meta:: :title lang=pt: Apêndices - :keywords lang=pt: guia de migração,como migrar,migração,nova versão,ajuda,glossário,rota de migração,novas funcionalidades + :keywords lang=pt: guia de migração,rota de migração,novas funcionalidades,glossário, diff --git a/pt/appendices/4-0-migration-guide.rst b/pt/appendices/4-0-migration-guide.rst deleted file mode 100644 index 1ac9c3ea33..0000000000 --- a/pt/appendices/4-0-migration-guide.rst +++ /dev/null @@ -1,12 +0,0 @@ -4.0 Migration Guide -################### - -.. note:: - Atualmente, a documentação desta página não é suportada em português. - - Por favor, sinta-se a vontade para nos enviar um *pull request* para o - `Github `_ ou use o botão - **IMPROVE THIS DOC** para propor suas mudanças diretamente. - - Você pode consultar a versão em inglês deste tópico através do seletor de - idiomas localizado ao lado direito do campo de buscas da documentação. diff --git a/pt/appendices/5-0-migration-guide.rst b/pt/appendices/5-0-migration-guide.rst new file mode 100644 index 0000000000..d81e7561de --- /dev/null +++ b/pt/appendices/5-0-migration-guide.rst @@ -0,0 +1,428 @@ +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/pt/appendices/5-0-upgrade-guide.rst b/pt/appendices/5-0-upgrade-guide.rst new file mode 100644 index 0000000000..5f34ec911c --- /dev/null +++ b/pt/appendices/5-0-upgrade-guide.rst @@ -0,0 +1,74 @@ +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/pt/appendices/5-1-migration-guide.rst b/pt/appendices/5-1-migration-guide.rst new file mode 100644 index 0000000000..caac02853f --- /dev/null +++ b/pt/appendices/5-1-migration-guide.rst @@ -0,0 +1,198 @@ +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/pt/appendices/5-2-migration-guide.rst b/pt/appendices/5-2-migration-guide.rst new file mode 100644 index 0000000000..66567f439a --- /dev/null +++ b/pt/appendices/5-2-migration-guide.rst @@ -0,0 +1,137 @@ +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/pt/appendices/migration-guides.rst b/pt/appendices/migration-guides.rst new file mode 100644 index 0000000000..2a86606039 --- /dev/null +++ b/pt/appendices/migration-guides.rst @@ -0,0 +1,14 @@ +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/pt/appendices/phpunit10.rst b/pt/appendices/phpunit10.rst new file mode 100644 index 0000000000..1cd56d3d3a --- /dev/null +++ b/pt/appendices/phpunit10.rst @@ -0,0 +1,65 @@ +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/pt/console-and-shells.rst b/pt/console-and-shells.rst deleted file mode 100644 index f25b6e8281..0000000000 --- a/pt/console-and-shells.rst +++ /dev/null @@ -1,953 +0,0 @@ -Console e Shells -################ - -.. php:namespace:: Cake\Console - -O CakePHP não oferece um framework apenas para desenvolvimento web, -mas também um framework para criação de aplicações de console. Estas -aplicações são ideais para manipular variadas tarefas em segundo plano como -manutenção e complementação de trabalho fora do ciclo requisição-resposta. -As aplicações de console do CakePHP permitem a vocë reutilizar suas classes -de aplicação a partir da linha de comando. - -O CakePHP traz consigo algumas aplicações de console nativas. Algumas dessas -aplicações são utilizadas em conjunto com outros recursos do CakePHP -(como i18n), e outros de uso geral para aceleração de trabalho. - -O Console do CakePHP -==================== - -Esta seção provê uma introdução à linha de comando do CakePHP. -Ferramentas de console são ideais para uso em cron jobs, ou utilitários -baseados em linha de comando que não precisam ser acessíveis por um navegador -web. - -O PHP provê um cliente CLI que faz interface com o seu -sistema de arquivos e aplicações de forma muito mais suave. O console do CakePHP -provê um framework para criar scripts shell. O console utiliza uma configuração -tipo dispatcher para carregar uma shell ou tarefa, e prover seus parâmetros. - -.. note:: - - Uma linha de comando (CLI) constutuída a partir do PHP deve estar - disponível no sistema se você planeja utilizr o Console. - -Antes de entrar em detalhes, vamos ter certeza de que você pode executar o -console do CakePHP. Primeiro, você vai precisar executar um sistema shell. Os -exemplos apresentados nesta seção serão em bash, mas o Console do CakePHP é -compatível com o Windows também. Este exemplo assume que o usuário está -conectado em um prompt do bash e está atualmente na raiz de uma aplicação -CakePHP. - -Aplicações CakePHP possuem um diretório `Console``` que contém todas as -shells e tarefas para uma aplicação. Ele também vem com um executável:: - - $ cd /path/to/app - $ bin/cake - -Executar o Console sem argumentos produz esta mensagem de ajuda:: - - Welcome to CakePHP v3.0.0 Console - --------------------------------------------------------------- - App : App - Path: /Users/markstory/Sites/cakephp-app/src/ - --------------------------------------------------------------- - Current Paths: - - -app: src - -root: /Users/markstory/Sites/cakephp-app - -core: /Users/markstory/Sites/cakephp-app/vendor/cakephp/cakephp - - Changing Paths: - - Your working path should be the same as your application path. To change your path use the '-app' param. - Example: -app relative/path/to/myapp or -app /absolute/path/to/myapp - - Available Shells: - - [Bake] bake - - [Migrations] migrations - - [CORE] i18n, orm_cache, plugin, server - - [app] behavior_time, console, orm - - To run an app or core command, type cake shell_name [args] - To run a plugin command, type cake Plugin.shell_name [args] - To get help on a specific command, type cake shell_name --help - -A primeira informação impressa refere-se a caminhos. Isso é útil se você estiver -executando o console a partir de diferentes partes do sistema de arquivos. - -Criando uma Shell -================= - -Vamos criar uma shell para utilizar no Console. Para este exemplo, -criaremos uma simples Hello World (Olá Mundo) shell. No diretório -**src/Shell** de sua aplicação crie **HelloShell.php**. Coloque o seguinte -código dentro do arquivo recem criado:: - - namespace App\Shell; - - use Cake\Console\Shell; - - class HelloShell extends Shell - { - public function main() - { - $this->out('Hello world.'); - } - } - -As convenções para as classes de shell são de que o nome da classe deve -corresponder ao nome do arquivo, com o sufixo de Shell. No nosso shell criamos -um método ``main()``. Este método é chamado quando um shell é chamado sem -comandos adicionais. Vamos adicionar alguns comandos daqui a pouco, mas por -agora vamos executar a nossa shell. A partir do diretório da aplicação, -execute:: - - bin/cake hello - -Você deve ver a seguinte saída:: - - Welcome to CakePHP Console - --------------------------------------------------------------- - App : app - Path: /Users/markstory/Sites/cake_dev/src/ - --------------------------------------------------------------- - Hello world. - -Como mencionado antes, o método ``main()`` em shells é um método especial -chamado sempre que não há outros comandos ou argumentos dados para uma shell. -Por nosso método principal não ser muito interessante, vamos adicionar outro -comando que faz algo:: - - 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); - } - } - -Depois de salvar o arquivo, você deve ser capaz de executar o seguinte comando e -ver o seu nome impresso:: - - bin/cake hello hey_there your-name - -Qualquer método público não prefixado por um ``_`` é permitido para ser chamado -a partir da linha de comando. Como você pode ver, os métodos invocados a partir -da linha de comando são transformados do argumento prefixado para a forma -correta do nome camel-cased (camelizada) -na classe. - -No nosso método ``heyThere()`` podemos ver que os argumentos posicionais são -providos para nossa função ``heyThere()``. Argumentos posicionais também estão -disponívels na propriedade ``args``. Você pode acessar switches ou opções em -aplicações shell, estando disponíveis em ``$this->params``, mas nós iremos -cobrir isso daqui a pouco. - -Quando utilizando um método ``main()`` você não estará liberado para utilizar -argumentos posicionais. Isso se deve ao primeiro argumento posicional ou opção -ser interpretado(a) como o nome do comando. Se você quer utilizar argumentos, -você deve usar métodos diferentes de ``main()``. - -Usando Models em suas shells ----------------------------- - -Você frequentemente precisará acessar a camada lógica de negócios em seus -utilitários shell; O CakePHP faz essa tarefa super fácil. Você pode carregar models em -shells assim como faz em um controller utilizando ``loadModel()``. Os models carregados -são definidos como propriedades anexas à sua shell:: - - 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])) { - return $this->error('Por favor, indique um nome de usuário.'); - } - $user = $this->Users->findByUsername($this->args[0])->first(); - $this->out(print_r($user, true)); - } - } - -A shell acima, irá preencher um user pelo seu username e exibir a informação -armazenada no banco de dados. - -Tasks de Shell -============== - -Haverão momentos construindo aplicações mais avançadas de console que você vai -querer compor funcionalidades em classes reutilizáveis que podem ser -compartilhadas através de muitas shells. Tasks permitem que você extraia -comandos em classes. Por exemplo, o ``bake`` é feito quase que completamente de -tasks. Você define tasks para uma shell usando a propriedade ``$tasks``:: - - class UserShell extends Shell - { - public $tasks = ['Template']; - } - -Você pode utilizar tasks de plugins utilizando o padrão :term:`sintaxe plugin`. -Tasks são armazenadas sob ``Shell/Task/`` em arquivos nomeados depois de suas -classes. Então se nós estivéssemos criando uma nova task 'FileGenerator', você -deveria criar **src/Shell/Task/FileGeneratorTask.php**. - -Cada task deve ao menos implementar um método ``main()``. O ShellDispatcher, -vai chamar esse método quando a task é invocada. Uma classe task se parece com:: - - namespace App\Shell\Task; - - use Cake\Console\Shell; - - class FileGeneratorTask extends Shell - { - public function main() - { - } - } - -Uma shell também pode prover acesso a suas tasks como propriedades, que fazem -tasks serem ótimas para criar punhados de funcionalidade reutilizáveis similares -a :doc:`/controllers/components`:: - - // Localizado em src/Shell/SeaShell.php - class SeaShell extends Shell - { - // Localizado em src/Shell/Task/SoundTask.php - public $tasks = ['Sound']; - - public function main() - { - $this->Sound->main(); - } - } - -Você também pode acessar tasks diretamente da linha de comando:: - - $ cake sea sound - -.. note:: - - Para acessar tasks diretamente através da linha de comando, a task - **deve** ser incluída na propriedade da classe shell ``$tasks``. - Portanto, esteja ciente que um método chamado "sound" na classe SeaShell - deve sobrescrever a habilidade de acessar a funcionalidade na task - Sound, especificada no array $tasks. - -Carregando Tasks em tempo-real com TaskRegistry ------------------------------------------------ - -Você pode carregar arquivos em tempo-real utilizando o Task registry object. -Você pode carregar tasks que não foram declaradas no $tasks dessa forma:: - - $project = $this->Tasks->load('Project'); - -Carregará e retornará uma instância ProjectTask. Você pode carregar tasks de -plugins usando:: - - $progressBar = $this->Tasks->load('ProgressBar.ProgressBar'); - -.. _invoking-other-shells-from-your-shell: - -Invocando outras Shells a partir da sua Shell -============================================= - -.. php:method:: dispatchShell($args) - -Existem ainda muitos casos onde você vai querer invocar uma shell a partir de -outra. ``Shell::dispatchShell()`` lhe dá a habilidade de chamar outras shells -ao providenciar o ``argv`` para a sub shell. Você pode providenciar argumentos -e opções tanto como variáveis ou como strings:: - - // Como uma string - $this->dispatchShell('schema create Blog --plugin Blog'); - - // Como um array - $this->dispatchShell('schema', 'create', 'Blog', '--plugin', 'Blog'); - -O conteúdo acima mostra como você pode chamar a shell schema para criar o schema -de um plugin de dentro da shell do próprio. - -Recenendo Input de usuários -=========================== - -.. php:method:: in($question, $choices = null, $defaut = null) - -Quando construir aplicações interativas pelo console você irá precisar receber -inputs dos usuários. CakePHP oferece uma forma fácil de fazer isso:: - - // Receber qualquer texto dos usuários. - $color = $this->in('What color do you like?'); - - // Receber uma escolha dos usuários. - $selection = $this->in('Red or Green?', ['R', 'G'], 'R'); - -A validação de seleção é insensitiva a maiúsculas / minúsculas. - -Criando Arquivos -================ - -.. php:method:: createFile($path, $contents) - -Muitas aplicações Shell auxiliam tarefas de desenvolvimento e implementação. -Criar arquivos é frequentemente importante nestes casos de uso. O CakePHP -oferece uma forma fácil de criar um arquivo em um determinado diretório:: - - $this->createFile('bower.json', $stuff); - -Se a Shell for interativa, um alerta vai ser gerado, e o usuário questionado se -ele quer sobreescrever o arquivo caso já exista. Se a propriedade de interação -da shell for ``false``, nenhuma questão será disparada e o arquivo será -simplesmente sobreescrito. - -Saída de dados do Console -========================= - -.. php:method:out($message, $newlines, $level) -.. php:method:err($message, $newlines) - -A classe ``Shell`` oferece alguns métodos para direcionar conteúdo:: - - // Escreve para stdout - $this->out('Normal message'); - - // Escreve para stderr - $this->err('Error message'); - - // Escreve para stderr e para o processo - $this->error('Fatal error'); - -A Shell também inclui métodos para limpar a saída de dados, criando linhas -em branco, ou desenhando uma linha de traços:: - - // Exibe 2 linhas novas - $this->out($this->nl(2)); - - // Limpa a tela do usuário - $this->clear(); - - // Desenha uma linha horizontal - $this->hr(); - -Por último, você pode atualizar a linha atual de texto na tela usando -``_io->overwrite()``:: - - $this->out('Counting down'); - $this->out('10', 0); - for ($i = 9; $i > 0; $i--) { - sleep(1); - $this->_io->overwrite($i, 0, 2); - } - -É importante lembrar, que você não pode sobreescrever texto -uma vez que uma nova linha tenha sido exibida. - -.. _shell-output-level: - -Console Output Levels ---------------------- - -Shells frequentemente precisam de diferentes níveis de verbosidade. Quando -executadas como cron jobs, muitas saídas são desnecessárias. E há ocasiões que -você não estará interessado em tudo que uma shell tenha a dizer. Você pode usar -os níveis de saída para sinalizar saídas apropriadamente. O usuário da shell, -pode então decidir qual nível de detalhe ele está interessado ao sinalizar o -chamado da shell. :php:meth:`Cake\\Console\\Shell::out()` suporta 3 tipos de -saída por padrão. - -* QUIET - Apenas informação absolutamente importante deve ser sinalizada. -* NORMAL - O nível padrão, e uso normal. -* VERBOSE - Sinalize mensagens que podem ser irritantes em demasia para uso - diário, mas informativas para depuração como VERBOSE. - -Você pode sinalizar a saíde da seguinte forma:: - - // Deve aparecer em todos os níveis. - $this->out('Quiet message', 1, Shell::QUIET); - $this->quiet('Quiet message'); - - // Não deve aparecer quando a saída quiet estiver alternado. - $this->out('normal message', 1, Shell::NORMAL); - $this->out('loud message', 1, Shell::VERBOSE); - $this->verbose('Verbose output'); - - // Deve aparecer somente quando a saíde verbose estiver habilitada. - $this->out('extra message', 1, Shell::VERBOSE); - $this->verbose('Verbose output'); - -Você pode controlar o nível de saída das shells, ao usar as opções ``--quiet`` -e ``--verbose``. Estas opções são adicionadas por padrão, e permitem a você -controlar consistentemente níveis de saída dentro das suas shells do CakePHP. - -Estilizando a saída de dados ----------------------------- - -Estilizar a saída de dados é feito ao incluir tags - como no HTML - em sua -saída. O ConsoleOutput irá substituir estas tags com a seqüência correta de -código ansi. Hão diversos estilos nativos, e você pode criar mais. Os nativos -são: - -* ``error`` Mensagens de erro. Texto sublinhado vermelho. -* ``warning`` Mensagens de alerta. Texto amarelo. -* ``info`` Mensagens informativas. Texto ciano. -* ``comment`` Texto adicional. Texto azul. -* ``question`` Texto que é uma questão, adicionado automaticamente pela shell. - -Você pode criar estilos adicionais usando ``$this->stdout->styles()``. Para -declarar um novo estilo de saíde você pode fazer:: - - $this->_io->styles('flashy', ['text' => 'magenta', 'blink' => true]); - -Isso deve então permití-lo usar uma ```` tag na saída de sua shell, e se -as cores ansi estiverem habilitadas, o seguinte pode ser renderizado como texto -magenta piscante ``$this->out('Whoooa Something went wrong');``. -Quando definir estilos você pode usar as seguintes cores para os atributos -``text`` e ``background``: - -* black -* red -* green -* yellow -* blue -* magenta -* cyan -* white - -Você também pode usar as seguintes opções através de valores boleanos, -defini-los com valor positivo os habilita. - -* bold -* underline -* blink -* reverse - -Adicionar um estilo o torna disponível para todas as instâncias do -ConsoleOutput, então você não tem que redeclarar estilos para os objetos stdout -e stderr respectivamente. - -Desabilitando a colorização ---------------------------- - -Mesmo que a colorização seja incrível, haverão ocasiões que você quererá -desabilitá-la, ou forçá-la:: - - $this->_io->outputAs(ConsoleOutput::RAW); - -O citado irá colocar o objeto de saída em modo raw. Em modo raw, -nenhum estilo é aplicado. Existem três modos que você pode usar. - -* ``ConsoleOutput::RAW`` - Saída raw, nenhum estilo ou formatação serão - aplicados. Este é um modo indicado se você estiver exibindo XML ou, quiser - depurar porquê seu estilo não está funcionando. -* ``ConsoleOutput::PLAIN`` - Saída de texto simples, tags conhecidas de estilo - serão removidas da saída. -* ``ConsoleOutput::COLOR`` - Saída onde a cor é removida. - -Por padrão em sistemas \*nix objetos ConsoleOutput padronizam-se a a saída de -cores. Em sistemas Windows, a saída simples é padrão a não ser que a variável de -ambiente ``ANSICON`` esteja presente. - -Opções de configuração e Geração de ajuda -========================================= - -.. php:class:: ConsoleOptionParser - -``ConsoleOptionParser`` oferece uma opção de CLI e analisador de argumentos. - -OptionParsers permitem a você completar dois objetivos ao mesmo tempo. Primeiro, -eles permitem definir opções e argumentos para os seus comandos. Isso permite -separar validação básica de dados e seus comandos do console. Segundo, -permite prover documentação, que é usada para gerar arquivos de ajuda bem -formatados. - -O console framework no CakePHP recebe as opções do seu interpetador shell ao -chamar ``$this->getOptionParser()``. Sobreescrever esse método permite -configurar o OptionParser para definir as entradas aguardadas da sua shell. -Você também pode configurar interpetadores de subcomandos, que permitem ter -diferentes interpretadores para subcomandos e tarefas. -O ConsoleOptionParser implementa uma interface fluida e inclui métodos para -facilmente definir múltiplas opções/argumentos de uma vez:: - - public function getOptionParser() - { - $parser = parent::getOptionParser(); - // Configure parser - return $parser; - } - -Configurando um interpretador de opção com a interface fluida -------------------------------------------------------------- - -Todos os métodos que configuram um interpretador de opções podem ser -encadeados, permitindo definir um interpretador de opções completo em uma -série de chamadas de métodos:: - - public function getOptionParser() - { - $parser = parent::getOptionParser(); - $parser->addArgument('type', [ - 'help' => 'Either a full path or type of class.' - ])->addArgument('className', [ - 'help' => 'A CakePHP core class name (e.g: Component, HtmlHelper).' - ])->addOption('method', [ - 'short' => 'm', - 'help' => __('The specific method you want help on.') - ])->description(__('Lookup doc block comments for classes in CakePHP.')); - - return $parser; - } - -Os métodos que permitem encadeamento são: - -- description() -- epilog() -- command() -- addArgument() -- addArguments() -- addOption() -- addOptions() -- addSubcommand() -- addSubcommands() - -.. php:method:: description($text = null) - -Recebe ou define a descrição para o interpretador de opções. A -descrição é exibida acima da informação do argumento e da opção. Ao -instanciar tanto em array como em string, você pode definir o valor -da descrição. Instanciar sem argumentos vai retornar o valor atual:: - - // Define múltiplas linhas de uma vez - $parser->description(['line one', 'line two']); - - // Lê o valor atual - $parser->description(); - -.. php:method:: epilog($text = null) - -Recebe ou define o epílogo para o interpretador de opções. O epílogo -é exibido depois da informação do argumento e da opção. Ao instanciar -tanto em array como em string, você pode definir o valor do epílogo. -Instanciar sem argumentos vai retornar o valor atual:: - - // Define múltiplas linhas de uma vez - $parser->epilog(['line one', 'line two']); - - // Lê o valor atual - $parser->epilog(); - -Adicionando argumentos ----------------------- - -.. php:method:: addArgument($name, $params = []) - -Argumentos posicionais são frequentemente usados em ferramentas -de linha de comando, e ``ConsoleOptionParser`` permite definir -argumentos bem como torná-los requiríveis. Você pode adicionar -argumentos um por vez com ``$parser->addArgument();`` ou múltiplos -de uma vez com ``$parser->addArguments();``:: - - $parser->addArgument('model', ['help' => 'The model to bake']); - -Você pode usar as seguintes opções ao criar um argumento: - -* ``help`` O texto de ajuda a ser exibido para este argumento. -* ``required`` Se esse parâmetro é requisito. -* ``index`` O índice do argumento, se deixado indefinido, o argumento será - colocado no final dos argumentos. Se você definir o mesmo índice duas vezes, - a primeira opção será sobreescrita. -* ``choices`` Um array de opções válidas para esse argumento. Se deixado vazio, - todos os valores são válidos. Uma exceção será lançada quando parse() - encontrar um valor inválido. - -Argumentos que forem definidos como requisito lançarão uma exceção quando -interpretarem o comando se eles forem omitidos. Então você não tem que lidar -com isso em sua shell. - -.. php:method:: addArguments(array $args) - -Se você tem um array com múltiplos argumentos você pode usar -``$parser->addArguments()`` para adicioná-los de uma vez.:: - - $parser->addArguments([ - 'node' => ['help' => 'The node to create', 'required' => true], - 'parent' => ['help' => 'The parent node', 'required' => true] - ]); - -Assim como todos os métodos de construção no ConsoleOptionParser, addArguments -pode ser usado como parte de um fluido método encadeado. - -Validando argumentos --------------------- - -Ao criar argumentos posicionais, você pode usar a marcação ``required`` para -indicar que um argumento deve estar presente quando uma shell é chamada. -Adicionalmente você pode usar o ``choices`` para forçar um argumento a -ser de uma lista de escolhas válidas:: - - $parser->addArgument('type', [ - 'help' => 'The type of node to interact with.', - 'required' => true, - 'choices' => ['aro', 'aco'] - ]); - -O código acima irá criar um argumento que é requisitado e tem validação -no input. Se o argumento está tanto indefinodo, ou possui um valor -incorreto, uma exceção será lançada e a shell parará. - -Adicionando opções ------------------- - -.. php:method:: addOption($name, $options = []) - -Opções são frequentemente usadas em ferramentas CLI. -``ConsoleOptionParser`` suporta a criação de opções com -verbose e aliases curtas, suprindo padrões e criando ativadores -boleanos. Opções são criadas tanto com -``$parser->addOption()`` ou ``$parser->addOptions()``.:: - - $parser->addOption('connection', [ - 'short' => 'c', - 'help' => 'connection', - 'default' => 'default', - ]); - -O código citado permite a você usar tanto ``cake myshell --connection=other``, -``cake myshell --connection other``, ou ``cake myshell -c other`` -quando invocando a shell. Você também criar ativadores boleanos. Estes -ativadores não consumem valores, e suas presenças apenas os habilitam nos -parâmetros interpretados.:: - - $parser->addOption('no-commit', ['boolean' => true]); - -Com essa opção, ao chamar uma shell como ``cake myshell --no-commit something`` -o parâmetro no-commit deve ter um valor de ``true``, e `something` -deve ser tratado como um argumento posicional. -As opções nativas ``--help``, ``--verbose``, e ``--quiet`` -usam essa funcionalidade. - -Ao criar opções você pode usar os seguintes argumentos para definir -o seu comportamento: - -* ``short`` - A variação de letra única para essa opção, deixe indefinido para - none. -* ``help`` - Texto de ajuda para essa opção. Usado ao gerar ajuda para a opção. -* ``default`` - O valor padrão para essa opção. Se não estiver definido o valor - padrão será ``true``. -* ``boolean`` - A opção não usa valor, é apenas um ativador boleano. Por padrão - ``false``. -* ``choices`` - Um array de escolhas válidas para essa opção. Se deixado vazio, - todos os valores são considerados válidos. Uma exceção será lançada quando - parse() encontrar um valor inválido. - -.. php:method:: addOptions(array $options) - -Se você tem um array com múltiplas opções, você pode usar -``$parser->addOptions()`` para adicioná-las de uma vez.:: - - $parser->addOptions([ - 'node' => ['short' => 'n', 'help' => 'The node to create'], - 'parent' => ['short' => 'p', 'help' => 'The parent node'] - ]); - -Assim como com todos os métodos construtores, no ConsoleOptionParser, addOptions -pode ser usado como parte de um método fluente encadeado. - -Validando opções ----------------- - -Opções podem ser fornecidas com um conjunto de escolhas bem como argumentos -posicionais podem ser. Quando uma opção define escolhas, essas são as únicas -opções válidas para uma opção. Todos os outros valores irão gerar um -``InvalidArgumentException``:: - - $parser->addOption('accept', [ - 'help' => 'What version to accept.', - 'choices' => ['working', 'theirs', 'mine'] - ]); - -Usando opções boleanas ----------------------- - -As opções podem ser definidas como opções boleanas, que são úteis quando você -precisa criar algumas opções de marcação. Como opções com padrões, opções -boleanas sempre irão incluir -se nos parâmetros analisados. Quando as marcações -estão presentes elas são definidas para ``true``, quando elas estão ausentes, -são definidas como ``false``:: - - $parser->addOption('verbose', [ - 'help' => 'Enable verbose output.', - 'boolean' => true - ]); - -A opção seguinte resultaria em ``$this->params['verbose']`` sempre -estando disponível. Isso permite a você omitir verificações ``empty()`` ou -``isset()`` em marcações boleanas:: - - if ($this->params['verbose']) { - // Do something. - } - -Desde que as opções boleanas estejam sempre definidas como ``true`` ou -``false``, você pode omitir métodos de verificação adicionais. - -Adicionando subcomandos ------------------------ - -.. php:method:: addSubcommand($name, $options = []) - -Aplicativos de console são muitas vezes feitas de subcomandos, e esses -subcomandos podem exigir a análise de opções especiais e terem a sua própria -ajuda. Um perfeito exemplo disso é ``bake``. Bake é feita de muitas tarefas -separadas e todas têm a sua própria ajuda e opções. ``ConsoleOptionParser`` -permite definir subcomandos e fornecer comandos analisadores de opção -específica, de modo que a shell sabe como analisar os comandos para as suas -funções:: - - $parser->addSubcommand('model', [ - 'help' => 'Bake a model', - 'parser' => $this->Model->getOptionParser() - ]); - -A descrição acima é um exemplo de como você poderia fornecer ajuda e um -especializado interpretador de opção para a tarefa de uma shell. Ao chamar a -tarefa de ``getOptionParser()`` não temos de duplicar a geração do interpretador -de opção, ou misturar preocupações no nosso shell. Adicionar subcomandos desta -forma tem duas vantagens. Primeiro, ele permite que o seu shell documente -facilmente seus subcomandos na ajuda gerada. Ele também dá fácil acesso ao -subcomando help. Com o subcomando acima criado você poderia chamar -``cake myshell --help`` e ver a lista de subcomandos, e -também executar o ``cake myshell model --help`` para exibir a ajuda -apenas o modelo de tarefa. - -.. note:: - - Uma vez que seu Shell define subcomandos, todos os subcomandos deve ser - explicitamente definidos. - -Ao definir um subcomando, você pode usar as seguintes opções: - -* ``help`` - Texto de ajuda para o subcomando. -* ``parser`` - Um ConsoleOptionParser para o subcomando. Isso permite que você - crie métodos analisadores de opção específios. Quando a ajuda é gerada por um - subcomando, se um analisador está presente ele vai ser usado. Você também - pode fornecer o analisador como uma matriz que seja compatível com - :php:meth:`Cake\\Console\\ConsoleOptionParser::buildFromArray()` - -Adicionar subcomandos pode ser feito como parte de uma cadeia de métodos -fluente. - -Construir uma ConsoleOptionParser de uma matriz ------------------------------------------------ - -.. php:method:: buildFromArray($spec) - -Como mencionado anteriormente, ao criar interpretadores de opção de subcomando, -você pode definir a especificação interpretadora como uma matriz para esse -método. Isso pode ajudar fazer analisadores mais facilmente, já que tudo é um -array:: - - $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 da especificação do interpretador, você pode definir as chaves para -``arguments``, ``options``, ``description`` e ``epilog``. Você não pode definir -``subcommands`` dentro de um construtor estilo array. Os valores para os -argumentos e opções, devem seguir o formato que -:php:func:`Cake\\Console\\ConsoleOptionParser::addArguments()` e -:php:func:`Cake\\Console\\ConsoleOptionParser::addOptions()` usam. Você também -pode usar buildFromArray por conta própria, para construir um interpretador de -opção:: - - 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')] - ] - ]); - } - -Recebendo ajuda das Shells --------------------------- - -Com a adição de ConsoleOptionParser receber ajuda de shells é feito -de uma forma consistente e uniforme. Ao usar a opção ``--help`` ou ``-h`` você -pode visualizar a ajuda para qualquer núcleo shell, e qualquer shell que -implementa um ConsoleOptionParser:: - - cake bake --help - cake bake -h - -Ambos devem gerar a ajuda para o bake. Se o shell suporta subcomandos -você pode obter ajuda para estes de uma forma semelhante:: - - cake bake model --help - cake bake model -h - -Isso deve fornecer a você a ajuda específica para a tarefa bake dos models. - -Recebendo ajuda como XML ------------------------- - -Quando a construção de ferramentas automatizadas ou ferramentas de -desenvolvimento que necessitam interagir com shells do CakePHP, é bom ter ajuda -disponível em uma máquina capaz interpretar formatos. O ConsoleOptionParser pode -fornecer ajuda em xml, definindo um argumento adicional:: - - cake bake --help xml - cake bake -h xml - -O trecho acima deve retornar um documento XML com a ajuda gerada, opções, -argumentos e subcomando para o shell selecionado. Um documento XML de amostra -seria algo como: - -.. 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. - - - - - - - - - - - - - - - - - - -Roteamento em Shells / CLI -========================== - -Na interface de linha de comando (CLI), especificamente suas shells e tarefas, -``env('HTTP_HOST')`` e outras variáveis de ambiente webbrowser específica, não -estão definidas. - -Se você gerar relatórios ou enviar e-mails que fazem uso de ``Router::url()``, -estes conterão a máquina padrão ``http://localhost/`` e resultando assim em -URLs inválidas. Neste caso, você precisa especificar o domínio manualmente. -Você pode fazer isso usando o valor de configuração ``App.fullBaseUrl`` no seu -bootstrap ou na sua configuração, por exemplo. - -Para enviar e-mails, você deve fornecer a classe CakeEmail com o host que você -deseja enviar o e-mail:: - - $Email = new CakeEmail(); - $Email->domain('www.example.org'); - -Iste afirma que os IDs de mensagens geradas são válidos e adequados para o -domínio a partir do qual os e-mails são enviados. - -Métodos enganchados -=================== - -.. php:method:: initialize() - - Inicializa a Shell para atua como construtor de subclasses e permite - configuração de tarefas antes de desenvolver a execução. - -.. php:method:: startup() - - Inicia-se a Shell e exibe a mensagem de boas-vindas. Permite a verificação - e configuração antes de comandar ou da execução principal. - - Substitua este método se você quiser remover as informações de boas-vindas, - ou outra forma modificar o fluxo de pré-comando. - -Mais tópicos -============ - -.. toctree:: - :maxdepth: 1 - - console-and-shells/helpers - console-and-shells/repl - console-and-shells/cron-jobs - console-and-shells/i18n-shell - console-and-shells/completion-shell - console-and-shells/plugin-shell - console-and-shells/routes-shell - console-and-shells/server-shell - console-and-shells/cache - -.. meta:: - :title lang=pt: Console e Shells - :keywords lang=pt: shell scripts,system shell,classes de aplicação,tarefas background,line script,cron job,request response,system path,acl,novos projetos,shells,parametros,i18n,cakephp,directory,manutenção,ideal,aplicações,mvc diff --git a/pt/console-and-shells/cache.rst b/pt/console-and-shells/cache.rst deleted file mode 100644 index fa0dbbfe56..0000000000 --- a/pt/console-and-shells/cache.rst +++ /dev/null @@ -1,11 +0,0 @@ -Cache Shell -=========== - -Para ajudá-lo a gerenciar melhor os dados armazenados em cache a partir de um ambiente CLI, um comando shell está disponível -para limpar os dados em cache que seu aplicativo possui:: - - // Limpar uma configuração de cache - bin/cake cache clear - - // Limpar todas as configurações de cache - bin/cake cache clear_all diff --git a/pt/console-and-shells/completion-shell.rst b/pt/console-and-shells/completion-shell.rst deleted file mode 100644 index dd2df27659..0000000000 --- a/pt/console-and-shells/completion-shell.rst +++ /dev/null @@ -1,12 +0,0 @@ -Completion Shell -################ - -.. note:: - Atualmente, a documentação desta página não é suportada em português. - - Por favor, sinta-se a vontade para nos enviar um *pull request* para o - `Github `_ ou use o botão - **IMPROVE THIS DOC** para propor suas mudanças diretamente. - - Você pode consultar a versão em inglês deste tópico através do seletor de - idiomas localizado ao lado direito do campo de buscas da documentação. \ No newline at end of file diff --git a/pt/console-and-shells/cron-jobs.rst b/pt/console-and-shells/cron-jobs.rst deleted file mode 100644 index 6bfddc7f86..0000000000 --- a/pt/console-and-shells/cron-jobs.rst +++ /dev/null @@ -1,27 +0,0 @@ -Executando Shells como Cron Jobs -################################ - -Uma coisa comum a fazer com um shell é torná-lo executado como um cronjob para limpar o banco de dados de vez em quando ou -enviar newsletters. Isso é trivial para configurar, por exemplo:: - - */5 * * * * cd /full/path/to/root && bin/cake myshell myparam - # * * * * * comando para executar - # │ │ │ │ │ - # │ │ │ │ │ - # │ │ │ │ \───── day of week (0 - 6) (0 a 6 são de domingo a sábado, ou use os nomes) -   # │   │   │   \────────── mês (1 - 12) -   # │   │   \─────────────── dia do mês (1 - 31) - # │ \──────────────────── hora (0 - 23) - # \───────────────────────── minuto (0 - 59) - -Você pode ver mais informações aqui: https://pt.wikipedia.org/wiki/Crontab - -.. tip:: - - Use ``-q`` (ou `--quiet`) para silenciar qualquer saída para cronjobs. - -.. meta:: - - :Title lang=pt: Executando Shells como cronjobs - :keywords lang=pt: crontab, script bash, crontab - diff --git a/pt/console-and-shells/helpers.rst b/pt/console-and-shells/helpers.rst deleted file mode 100644 index 57a41379ea..0000000000 --- a/pt/console-and-shells/helpers.rst +++ /dev/null @@ -1,12 +0,0 @@ -Shell Helpers -############# - -.. note:: - Atualmente, a documentação desta página não é suportada em português. - - Por favor, sinta-se a vontade para nos enviar um *pull request* para o - `Github `_ ou use o botão - **IMPROVE THIS DOC** para propor suas mudanças diretamente. - - Você pode consultar a versão em inglês deste tópico através do seletor de - idiomas localizado ao lado direito do campo de buscas da documentação. diff --git a/pt/console-and-shells/i18n-shell.rst b/pt/console-and-shells/i18n-shell.rst deleted file mode 100644 index 4b1b80b8fa..0000000000 --- a/pt/console-and-shells/i18n-shell.rst +++ /dev/null @@ -1,12 +0,0 @@ -I18N Shell -########## - -.. note:: - Atualmente, a documentação desta página não é suportada em português. - - Por favor, sinta-se a vontade para nos enviar um *pull request* para o - `Github `_ ou use o botão - **IMPROVE THIS DOC** para propor suas mudanças diretamente. - - Você pode consultar a versão em inglês deste tópico através do seletor de - idiomas localizado ao lado direito do campo de buscas da documentação. \ No newline at end of file diff --git a/pt/console-and-shells/orm-cache.rst b/pt/console-and-shells/orm-cache.rst deleted file mode 100644 index 04fb6b15e7..0000000000 --- a/pt/console-and-shells/orm-cache.rst +++ /dev/null @@ -1,22 +0,0 @@ -ORM Cache Shell -############### - -O OrmCacheShell fornece uma ferramenta CLI simples para gerenciar caches de metadados da sua aplicação. Em situações de -implantação, é útil reconstruir o cache de metadados no local sem limpar os dados de cache existentes. Você pode fazer isso -executando:: - - bin/cake orm_cache build --connection default - -Isso irá reconstruir o cache de metadados para todas as tabelas na conexão ``default``. Se você só precisa reconstruir uma -única tabela, você pode fazer isso fornecendo seu nome:: - - bin/cake orm_cache build --connection default <> - -Além de criar dados em cache, você pode usar o OrmCacheShell para remover metadados em cache também:: - - # Limpar todos os metadados - bin/cake orm_cache clear - - # Limpar uma única tabela de metadados - bin/cake orm_cache clear <> - diff --git a/pt/console-and-shells/plugin-shell.rst b/pt/console-and-shells/plugin-shell.rst deleted file mode 100644 index c180b8307b..0000000000 --- a/pt/console-and-shells/plugin-shell.rst +++ /dev/null @@ -1,18 +0,0 @@ -.. _plugin-shell: - -Plugin Shell -############ - -.. note:: - Atualmente, a documentação desta página não é suportada em português. - - Por favor, sinta-se a vontade para nos enviar um *pull request* para o - `Github `_ ou use o botão - **IMPROVE THIS DOC** para propor suas mudanças diretamente. - - Você pode consultar a versão em inglês deste tópico através do seletor de - idiomas localizado ao lado direito do campo de buscas da documentação. - -.. meta:: - :title lang=pt: Plugin Shell - :keywords lang=pt: api docs,shell,plugin,load,unload diff --git a/pt/console-and-shells/repl.rst b/pt/console-and-shells/repl.rst deleted file mode 100644 index 402bba12b0..0000000000 --- a/pt/console-and-shells/repl.rst +++ /dev/null @@ -1,12 +0,0 @@ -Console Interativo (REPL) -######################### - -.. note:: - Atualmente, a documentação desta página não é suportada em português. - - Por favor, sinta-se a vontade para nos enviar um *pull request* para o - `Github `_ ou use o botão - **IMPROVE THIS DOC** para propor suas mudanças diretamente. - - Você pode consultar a versão em inglês deste tópico através do seletor de - idiomas localizado ao lado direito do campo de buscas da documentação. \ No newline at end of file diff --git a/pt/console-and-shells/routes-shell.rst b/pt/console-and-shells/routes-shell.rst deleted file mode 100644 index 9ee380bc0e..0000000000 --- a/pt/console-and-shells/routes-shell.rst +++ /dev/null @@ -1,12 +0,0 @@ -Routes Shell -############ - -.. note:: - Atualmente, a documentação desta página não é suportada em português. - - Por favor, sinta-se a vontade para nos enviar um *pull request* para o - `Github `_ ou use o botão - **IMPROVE THIS DOC** para propor suas mudanças diretamente. - - Você pode consultar a versão em inglês deste tópico através do seletor de - idiomas localizado ao lado direito do campo de buscas da documentação. diff --git a/pt/console-and-shells/server-shell.rst b/pt/console-and-shells/server-shell.rst deleted file mode 100644 index 7d772f9318..0000000000 --- a/pt/console-and-shells/server-shell.rst +++ /dev/null @@ -1,12 +0,0 @@ -Server Shell -############ - -.. note:: - Atualmente, a documentação desta página não é suportada em português. - - Por favor, sinta-se a vontade para nos enviar um *pull request* para o - `Github `_ ou use o botão - **IMPROVE THIS DOC** para propor suas mudanças diretamente. - - Você pode consultar a versão em inglês deste tópico através do seletor de - idiomas localizado ao lado direito do campo de buscas da documentação. diff --git a/pt/console-commands.rst b/pt/console-commands.rst new file mode 100644 index 0000000000..c40f966c9c --- /dev/null +++ b/pt/console-commands.rst @@ -0,0 +1,183 @@ +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/pt/console-commands/cache.rst b/pt/console-commands/cache.rst new file mode 100644 index 0000000000..4bb1ea3761 --- /dev/null +++ b/pt/console-commands/cache.rst @@ -0,0 +1,14 @@ +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/pt/console-commands/commands.rst b/pt/console-commands/commands.rst new file mode 100644 index 0000000000..9493a24b5c --- /dev/null +++ b/pt/console-commands/commands.rst @@ -0,0 +1,574 @@ +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/pt/console-commands/completion.rst b/pt/console-commands/completion.rst new file mode 100644 index 0000000000..c197d5e4a2 --- /dev/null +++ b/pt/console-commands/completion.rst @@ -0,0 +1,187 @@ +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/pt/console-commands/counter-cache.rst b/pt/console-commands/counter-cache.rst new file mode 100644 index 0000000000..cc50932bcf --- /dev/null +++ b/pt/console-commands/counter-cache.rst @@ -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. + +.. 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/pt/console-commands/cron-jobs.rst b/pt/console-commands/cron-jobs.rst new file mode 100644 index 0000000000..6b29f9d01a --- /dev/null +++ b/pt/console-commands/cron-jobs.rst @@ -0,0 +1,43 @@ +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/pt/console-commands/i18n.rst b/pt/console-commands/i18n.rst new file mode 100644 index 0000000000..4cd828bfdb --- /dev/null +++ b/pt/console-commands/i18n.rst @@ -0,0 +1,92 @@ +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/pt/console-commands/input-output.rst b/pt/console-commands/input-output.rst new file mode 100644 index 0000000000..3d03c3cd03 --- /dev/null +++ b/pt/console-commands/input-output.rst @@ -0,0 +1,375 @@ +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/pt/console-commands/option-parsers.rst b/pt/console-commands/option-parsers.rst new file mode 100644 index 0000000000..9d5152becc --- /dev/null +++ b/pt/console-commands/option-parsers.rst @@ -0,0 +1,367 @@ +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/pt/console-commands/plugin.rst b/pt/console-commands/plugin.rst new file mode 100644 index 0000000000..4a9fe5a5b5 --- /dev/null +++ b/pt/console-commands/plugin.rst @@ -0,0 +1,65 @@ +.. _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/pt/console-commands/repl.rst b/pt/console-commands/repl.rst new file mode 100644 index 0000000000..48465ba3bd --- /dev/null +++ b/pt/console-commands/repl.rst @@ -0,0 +1,50 @@ +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/pt/console-commands/routes.rst b/pt/console-commands/routes.rst new file mode 100644 index 0000000000..d3c0986984 --- /dev/null +++ b/pt/console-commands/routes.rst @@ -0,0 +1,40 @@ +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/pt/console-commands/schema-cache.rst b/pt/console-commands/schema-cache.rst new file mode 100644 index 0000000000..df891ab6a5 --- /dev/null +++ b/pt/console-commands/schema-cache.rst @@ -0,0 +1,30 @@ +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/pt/console-commands/server.rst b/pt/console-commands/server.rst new file mode 100644 index 0000000000..7c3eb62f6e --- /dev/null +++ b/pt/console-commands/server.rst @@ -0,0 +1,30 @@ +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/pt/contents.rst b/pt/contents.rst index c2cbcf258f..c87f94badd 100644 --- a/pt/contents.rst +++ b/pt/contents.rst @@ -11,9 +11,10 @@ Conteúdo intro quickstart - appendices/4-0-migration-guide + appendices/migration-guides tutorials-and-examples contributing + release-policy .. toctree:: :caption: Começando @@ -21,6 +22,7 @@ Conteúdo installation development/configuration development/application + development/dependency-injection development/routing controllers/request-response controllers/middleware @@ -31,9 +33,8 @@ Conteúdo .. toctree:: :caption: Using CakePHP - controllers/components/authentication core-libraries/caching - console-and-shells + console-commands development/debugging deployment core-libraries/email @@ -42,7 +43,7 @@ Conteúdo core-libraries/internationalization-and-localization core-libraries/logging core-libraries/form - controllers/components/pagination + controllers/pagination plugins development/rest security @@ -55,24 +56,29 @@ Conteúdo core-libraries/app core-libraries/collections - core-libraries/file-folder 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 - - Bake - chronos - Debug Kit - Migrations - Elasticsearch + :caption: Plugins e Pacotes + + standalone-packages + Authentication + Authorization + Bake + Debug Kit + Migrations + Elasticsearch + Phinx + Chronos + Queue .. toctree:: :caption: Diversos @@ -85,15 +91,16 @@ Conteúdo topics chronos + debug-kit + elasticsearch bake bake/development bake/usage - debug-kit - elasticsearch migrations + phinx .. todolist:: .. meta:: :title lang=pt: Conteúdo - :keywords lang=pt: bibliotecas do core,busca,filtro,índice,shells,deployment,apêndices,glossário,models,lib + :keywords lang=pt: bibliotecas do core,busca,comandos,deployment,apêndices,glossário,models diff --git a/pt/controllers.rst b/pt/controllers.rst index d44cd86f29..7f3d361694 100644 --- a/pt/controllers.rst +++ b/pt/controllers.rst @@ -400,7 +400,7 @@ Paginando um model Este método é usado para fazer a paginação dos resultados retornados por seus models. Você pode especificar o tamanho da página (quantos resultados serão retornados), as condições de busca e outros parâmetros. Veja a seção -:doc:`pagination ` para mais detalhes +:doc:`pagination ` para mais detalhes sobre como usar o método ``paginate()`` O atributo paginate lhe oferece uma forma fácil de customizar como diff --git a/pt/controllers/components.rst b/pt/controllers/components.rst index 43609cb2ed..411fd7910f 100644 --- a/pt/controllers/components.rst +++ b/pt/controllers/components.rst @@ -17,7 +17,7 @@ capítulo para cada componente: /controllers/components/authentication /controllers/components/flash /controllers/components/security - /controllers/components/pagination + /controllers/pagination /controllers/components/request-handling .. _configuring-components: diff --git a/pt/controllers/components/pagination.rst b/pt/controllers/components/pagination.rst deleted file mode 100644 index 82e735c141..0000000000 --- a/pt/controllers/components/pagination.rst +++ /dev/null @@ -1,332 +0,0 @@ -Pagination -########## - -.. php:namespace:: Cake\Controller\Component - -.. php:class:: PaginatorComponent - -Um dos principais obstáculos da criação de aplicativos Web flexíveis e fáceis de usar -é o design de uma interface de usuário intuitiva. Muitos aplicativos tendem a crescer -em tamanho e complexidade rapidamente, e designers e programadores acham que não conseguem -lidar com a exibição de centenas ou milhares de registros. A refatoração leva tempo, e o -desempenho e a satisfação do usuário podem sofrer. - -A exibição de um número razoável de registros por página sempre foi uma parte crítica -de todos os aplicativos e usada para causar muitas dores de cabeça aos desenvolvedores. -O CakePHP facilita a carga para o desenvolvedor, fornecendo uma maneira rápida e fácil -de paginar os dados. - -A paginação no CakePHP é oferecida por um componente no controlador, para facilitar a -criação de consultas paginadas. A View :php:class:`~Cake\\View\\Helper\\PaginatorHelper` -é usada para simplificar a geração de links e botões de paginação. - -Usando Controller::paginate() -============================= - -No controlador, começamos definindo as condições de consulta padrão que a paginação usará -na variável do controlador ``$paginate``. Essas condições servem como base para suas -consultas de paginação. Eles são aumentados pelos parâmetros ``sort``, ``direction``, -``limit`` e ``page`` transmitidos a partir da URL. É importante notar que a chave ``order`` -deve ser definida em uma estrutura de matriz como abaixo:: - - class ArticlesController extends AppController - { - public $paginate = [ - 'limit' => 25, - 'order' => [ - 'Articles.title' => 'asc' - ] - ]; - - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('Paginator'); - } - } - -Você também pode incluir qualquer uma das opções suportadas -por :php:meth:`~Cake\\ORM\\Table::find()`, como ``fields``:: - - class ArticlesController extends AppController - { - public $paginate = [ - 'fields' => ['Articles.id', 'Articles.created'], - 'limit' => 25, - 'order' => [ - 'Articles.title' => 'asc' - ] - ]; - - public function initialize(): void - { - parent::initialize(); - $this->loadComponent('Paginator'); - } - } - -Enquanto você pode passar a maioria das opções de consulta da propriedade -paginate, geralmente é mais fácil e simples agrupar suas opções de paginação -em :ref:`custom-find-methods`. Você pode definir o uso da paginação do -localizador, definindo a opção ``finder``:: - - class ArticlesController extends AppController - { - public $paginate = [ - 'finder' => 'published', - ]; - } - -Como os métodos localizadores personalizados também podem receber opções, é assim -que você passa as opções para um método find personalizado dentro da propriedade -paginate:: - - class ArticlesController extends AppController - { - // encontrar artigos por tag - public function tags() - { - $tags = $this->request->getParam('pass'); - - $customFinderOptions = [ - 'tags' => $tags - ]; - // o método find personalizado é chamado findTagged dentro de ArticlesTable.php, - // e deve ter se parecer com: public function findTagged(Query $query, array $options) { - // portanto, você usa tags como a chave - $this->paginate = [ - 'finder' => [ - 'tagged' => $customFinderOptions - ] - ]; - $articles = $this->paginate($this->Articles); - $this->set(compact('articles', 'tags')); - } - } - -Além de definir valores gerais de paginação, você pode definir mais de um -conjunto de padrões de paginação no controlador, basta nomear as chaves da -matriz após o modelo que deseja configurar:: - - class ArticlesController extends AppController - { - public $paginate = [ - 'Articles' => [], - 'Authors' => [], - ]; - } - -Os valores das chaves ``Articles`` e ``Authors`` podem conter todas as propriedades -que um modelo/chave menos a matriz ``$paginate``. - -Depois de definida a propriedade ``$paginate``, podemos usar o método :php:meth:`~Cake\\Controller\\Controller::paginate()` -para criar os dados de paginação e adicionar o ``PaginatorHelper `` se ainda não foi adicionado. -O método paginado do controlador retornará o conjunto de resultados da consulta paginada e -definirá os metadados de paginação para a solicitação. Você pode acessar os metadados da -paginação em ``$this->request->getParam('paging')``. Um exemplo mais completo do uso de -``paginate()`` seria:: - - class ArticlesController extends AppController - { - public function index() - { - $this->set('articles', $this->paginate()); - } - } - -Por padrão, o método ``paginate()`` usará o modelo padrão para -um controlador. Você também pode passar a consulta resultante de um método find:: - - public function index() - { - $query = $this->Articles->find('popular')->where(['author_id' => 1]); - $this->set('articles', $this->paginate($query)); - } - -Se você quiser paginar um modelo diferente, poderá fornecer uma consulta para ele, -sendo o próprio objeto de tabela ou seu nome:: - - // Usando a query. - $comments = $this->paginate($commentsTable->find()); - - // Usando o nome do modelo. - $comments = $this->paginate('Comments'); - - // Usando um objeto de tabela. - $comments = $this->paginate($commentTable); - -Usando o Paginator Diretamente -============================== - -Se você precisar paginar os dados de outro componente, poderá usar o PaginatorComponent -diretamente. O PaginatorComponent possui uma API semelhante ao método do controlador:: - - $articles = $this->Paginator->paginate($articleTable->find(), $config); - - // Ou - $articles = $this->Paginator->paginate($articleTable, $config); - -O primeiro parâmetro deve ser o objeto de consulta de um objeto de localização na tabela -do qual você deseja paginar os resultados. Opcionalmente, você pode passar o objeto de -tabela e permitir que a consulta seja construída para você. O segundo parâmetro deve ser -a matriz de configurações a serem usadas para paginação. Essa matriz deve ter a mesma estrutura -que a propriedade ``$paginate`` em um controlador. Ao paginar um objeto ``Query``, a opção -``finder`` será ignorada. Supõe-se que você esteja passando a consulta que deseja paginar. - -.. _paginating-multiple-queries: - -Paginando Múltiplas Queries -=========================== - -Você pode paginar vários modelos em uma única ação do controlador, usando a opção -``scope`` na propriedade ``$paginate`` do controlador e na chamada para o -método ``paginate()``:: - - // Propriedade Paginar - public $paginate = [ - 'Articles' => ['scope' => 'article'], - 'Tags' => ['scope' => 'tag'] - ]; - - // Em um método do controlador - $articles = $this->paginate($this->Articles, ['scope' => 'article']); - $tags = $this->paginate($this->Tags, ['scope' => 'tag']); - $this->set(compact('articles', 'tags')); - -A opção ``scope`` resultará na ``PaginatorComponent`` procurando nos parâmetros da -string de consulta com escopo definido. Por exemplo, o URL a seguir pode ser usado -para paginar tags e artigos ao mesmo tempo:: - - /dashboard?article[page]=1&tag[page]=3 - -Veja a seção `paginator-helper-multiple` para saber como gerar elementos HTML -com escopo e URLs para paginação. - -Paginando o Mesmo Modelo Várias Vezes -------------------------------------- - -Para paginar o mesmo modelo várias vezes em uma única ação do controlador, é -necessário definir um alias para o modelo. Consulte `table-registry-usage` -para obter detalhes adicionais sobre como usar o registro da tabela:: - - // Em um método do controlador - $this->paginate = [ - 'ArticlesTable' => [ - 'scope' => 'published_articles', - 'limit' => 10, - 'order' => [ - 'id' => 'desc', - ], - ], - 'UnpublishedArticlesTable' => [ - 'scope' => 'unpublished_articles', - 'limit' => 10, - 'order' => [ - 'id' => 'desc', - ], - ], - ]; - - // Registre um objeto de tabela adicional para permitir a diferenciação no componente de paginação - TableRegistry::getTableLocator()->setConfig('UnpublishedArticles', [ - 'className' => 'App\Model\Table\ArticlesTable', - 'table' => 'articles', - 'entityClass' => 'App\Model\Entity\Article', - ]); - - $publishedArticles = $this->paginate( - $this->Articles->find('all', [ - 'scope' => 'published_articles' - ])->where(['published' => true]) - ); - - $unpublishedArticles = $this->paginate( - TableRegistry::getTableLocator()->get('UnpublishedArticles')->find('all', [ - 'scope' => 'unpublished_articles' - ])->where(['published' => false]) - ); - -.. _control-which-fields-used-for-ordering: - -Controlar Quais Campos Usados para Ordenamento -============================================== - -Por padrão, a classificação pode ser feita em qualquer coluna não virtual que uma -tabela tenha. Às vezes, isso é indesejável, pois permite que os usuários classifiquem -em colunas não indexadas que podem ser caras de solicitar. Você pode definir a lista de -permissões dos campos que podem ser classificados usando a opção ``sortableFields``. Essa -opção é necessária quando você deseja classificar os dados associados ou os campos computados -que podem fazer parte da sua consulta de paginação:: - - public $paginate = [ - 'sortableFields' => [ - 'id', 'title', 'Users.username', 'created' - ] - ]; - -Quaisquer solicitações que tentem classificar campos que não estão na lista de permissões serão ignoradas. - -Limitar o Número Máximo de Linhas por Página -============================================ - -O número de resultados que são buscados por página é exposto ao usuário como o -parâmetro ``limit``. Geralmente, é indesejável permitir que os usuários busquem -todas as linhas em um conjunto paginado. A opção ``maxLimit`` afirma que ninguém -pode definir esse limite muito alto do lado de fora. Por padrão, o CakePHP limita -o número máximo de linhas que podem ser buscadas para 100. Se esse padrão não for -apropriado para a sua aplicação, você poderá ajustá-lo como parte das opções de paginação, -por exemplo, reduzindo-o para ``10``:: - - public $paginate = [ - // Outras chaves aqui. - 'maxLimit' => 10 - ]; - -Se o parâmetro de limite da solicitação for maior que esse valor, -ele será reduzido ao valor ``maxLimit``. - -Juntando Associações Adicionais -=============================== - -Associações adicionais podem ser carregadas na tabela paginada usando o -parâmetro ``contains``:: - - public function index() - { - $this->paginate = [ - 'contain' => ['Authors', 'Comments'] - ]; - - $this->set('articles', $this->paginate($this->Articles)); - } - -Solicitações de Página Fora do Intervalo -======================================== - -O PaginatorComponent lançará uma ``NotFoundException`` ao tentar acessar uma página -inexistente, ou seja, o número da página solicitada é maior que a contagem total de páginas. - -Portanto, você pode permitir que a página de erro normal seja renderizada ou usar um -bloco try catch e executar a ação apropriada quando um ``NotFoundException`` for capturado:: - - use Cake\Http\Exception\NotFoundException; - - public function index() - { - try { - $this->paginate(); - } catch (NotFoundException $e) { - // Faça algo aqui como redirecionar para a primeira ou a última página. - // $this->request->getParam('paging') fornecerá as informações necessárias. - } - } - -Paginação na View -================= - -Verifique a documentação :php:class:`~Cake\\View\\Helper\\PaginatorHelper` -para saber como criar links para navegação de paginação. - -.. meta:: - :title lang=pt: Paginação - :keywords lang=pt: matriz de pedidos, condições de consulta, classe php, aplicativos web, dores de cabeça, obstáculos, complexidade, programadores, parâmetros, paginar, designers, cakephp, satisfação, desenvolvedores diff --git a/pt/controllers/pagination.rst b/pt/controllers/pagination.rst new file mode 100644 index 0000000000..474674a046 --- /dev/null +++ b/pt/controllers/pagination.rst @@ -0,0 +1,296 @@ +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/pt/core-libraries/plugin.rst b/pt/core-libraries/plugin.rst new file mode 100644 index 0000000000..1b0065a13e --- /dev/null +++ b/pt/core-libraries/plugin.rst @@ -0,0 +1,53 @@ +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/pt/development/dependency-injection.rst b/pt/development/dependency-injection.rst new file mode 100644 index 0000000000..1fcf43615b --- /dev/null +++ b/pt/development/dependency-injection.rst @@ -0,0 +1,352 @@ +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/pt/intro/cakephp-folder-structure.rst b/pt/intro/cakephp-folder-structure.rst index bee60c6a7a..3c4734f7d7 100644 --- a/pt/intro/cakephp-folder-structure.rst +++ b/pt/intro/cakephp-folder-structure.rst @@ -37,9 +37,9 @@ Você notará alguns diretórios principais: como os dados serão armazenados depende da configuração do CakePHP, mas esse diretório é comumente usado para armazenar descrições de modelos e algumas vezes informação de sessão. -- O diretório *vendor* será onde o CakePHP e outras dependências da aplicação - serão instalados. Faça uma nota pessoal para **não** editar arquivos deste - diretório. Nós não podemos ajudar se você tivé-lo feito. +- O diretório *vendor* é onde o CakePHP e outras dependências do aplicativo serão + instaladas pelo `Composer `_. Editar esses arquivos não é + aconselhável, pois o Composer substituirá suas alterações na próxima atualização. - O diretório *webroot* será a raíz pública de documentos da sua aplicação. Ele contem todos os arquivos que você gostaria que fossem públicos. @@ -53,20 +53,24 @@ O diretório src O diretório *src* do CakePHP é onde você fará a maior parte do desenvolvimento de sua aplicação. Vamos ver mais de perto a estrutura de pastas dentro de *src*. +Command + Contém os comandos de console para sua aplicação. Consulte + :doc:`/console-commands/commands` para saber mais. Console - Contém os comandos e tarefas de console para sua aplicação. - Para mais informações veja :doc:`/console-and-shells`. + Contém o script de instalação executado pelo Composer. Controller - Contém os controllers de sua aplicação e seus componentes. -Locale - Armazena arquivos textuais para internacionalização. + Contém os :doc:`/controllers` de sua aplicação e seus componentes. +Middleware + Armazena qualquer :doc:`/controllers/middleware` para seu aplicativo. Model Contém as tables, entities e behaviors de sua aplicação. View Classes de apresentação são alocadas aqui: cells, helpers, e arquivos view. -Template - Arquivos de apresentação são alocados aqui: elements, páginas de erro, - layouts, e templates view. + +.. note:: + + A pasta ``Command`` não está presente por padrão. + Você pode adicioná-la quando precisar. .. meta:: :title lang=pt: Estrutura de pastas do CakePHP diff --git a/pt/intro/conventions.rst b/pt/intro/conventions.rst index 1aed04f31c..4300dc66e6 100644 --- a/pt/intro/conventions.rst +++ b/pt/intro/conventions.rst @@ -76,6 +76,8 @@ nomes de classes e de seus arquivos: Cada arquivo deveria estar localizado no diretório/namespace apropriado de sua aplicação. +.. _model-and-database-conventions: + Convenções para Banco de Dados ============================== diff --git a/pt/orm.rst b/pt/orm.rst index 1a1008bc36..1f50eb16d8 100644 --- a/pt/orm.rst +++ b/pt/orm.rst @@ -1,81 +1,78 @@ -Models (Modelos) -################ - -Models (Modelos) são as classes que servem como camada de negócio na sua -aplicação. Isso significa que eles devem ser responsáveis pela gestão de quase -tudo o que acontece em relação a seus dados, sua validade, interações e evolução -do fluxo de trabalho de informação no domínio do trabalho. - -No CakePHP seu modelo de domínio da aplicação é dividido em 2 tipos de objetos -principais. Os primeiros são **repositories (repositórios)** ou **table objects -(objetos de tabela)**. Estes objetos fornecem acesso a coleções de dados. Eles -permitem a você salvar novos registros, modificar/deletar os que já existem, -definir relacionamentos, e executar operações em massa. O segundo tipo de -objetos são as **entities (entidades)**. Entities representam registros -individuais e permitem a você definir comportamento em nível de linha/registro -e funcionalidades. - -O ORM (MOR - Mapeamento Objeto-Relacional) nativo do CakePHP especializa-se em -banco de dados relacionais, mas pode ser extendido para suportar fontes de dados -alternativas. - -O ORM do Cakephp toma emprestadas ideias e conceitos dos padrões ActiveRecord e -Datamapper. Isso permite criar uma implementação híbrida que combina aspectos de -ambos padrões para criar uma ORM rápida e simples de utilizar. - -Antes de nós começarmos explorando o ORM, tenha certeza que você -:ref:`configure your database connections `. +Acesso a Banco de Dados e ORM +############################# -Exemplo rápido -============== +No CakePHP, o trabalho com dados por meio do banco de dados é feito com dois tipos principais de objetos: -Para começar você não precisa escrever código. Se você seguiu as convenções do -CakePHP para suas tabelas de banco de dados, você pode simplesmente começar a -usar o ORM. Por exemplo, se quiséssemos carregar alguns dados da nossa tabela -``articles`` poderíamos fazer:: +- **Repositórios** ou **objetos de tabela** fornecem acesso a coleções de dados. + Eles permitem salvar novos registros, modificar/excluir os existentes, definir + relações e executar operações em massa. +- **Entidades** representam registros individuais e permitem que você defina o comportamento e a funcionalidade + em nível de linha/registro. - use Cake\ORM\TableRegistry; +Essas duas classes geralmente são responsáveis ​​por gerenciar quase tudo +que acontece em relação aos seus dados, sua validade, interações e evolução +do fluxo de trabalho de informações em seu domínio de trabalho. - // Prior to 3.6 use TableRegistry::get('Articles') - $articles = TableRegistry::getTableLocator()->get('Articles'); - $query = $articles->find(); - foreach ($query as $row) { - echo $row->title; - } +O ORM integrado do CakePHP é especializado em bancos de dados relacionais, mas pode ser estendido +para oferecer suporte a fontes de dados alternativas. + +O ORM CakePHP utiliza ideias e conceitos dos padrões ActiveRecord e Datamapper. +O objetivo é criar uma implementação híbrida que combine aspectos de +ambos os padrões para criar um ORM rápido e fácil de usar. -Nota-se que nós não temos que criar qualquer código ou definir qualquer -configuração. As convenções do CakePHP nos permitem pular alguns códigos clichê, -e permitir que o framework insera classes básicas enquanto sua aplicação não -criou uma classe concreta. Se quiséssemos customizar nossa classe ArticlesTable -adicionando algumas associações ou definir alguns métodos adicionais, deveriamos -acrescentar o seguinte a **src/Model/Table/ArticlesTable.php** após a tag de -abertura ```. +Exemplo rápido +============== + +Para começar, você não precisa escrever nenhum código. Se você seguiu as +:ref:`Convenções para Banco de Dados +`, pode simplesmente começar a usar o ORM. Por exemplo, +se quiséssemos carregar alguns dados da nossa tabela ``articles``, começaríamos +criando nossa classe de tabela ``Articles``. Crie +**src/Model/Table/ArticlesTable.php** com o seguinte código:: + + fetchTable('Articles')->find()->all(); + foreach ($resultset as $row) { + echo $row->title; + } } -Classes de tabela usam a versão CamelCased do nome da tabela com o sufixo -``Table`` como o nome da classe. Uma vez que sua classe fora criada, você recebe -uma referência para esta utilizando o :php:class:`~Cake\\ORM\\TableRegistry` -como antes:: +Em outros contextos, você pode usar o ``LocatorAwareTrait`` que adiciona métodos de acesso para tabelas ORM:: - use Cake\ORM\TableRegistry; + use Cake\ORM\Locator\LocatorAwareTrait; + + public function someMethod() + { + $articles = $this->fetchTable('Articles'); + // mais código. + } + +Dentro de um método estático, você pode usar :php:class:`~Cake\\Datasource\\FactoryLocator` +para obter o localizador da tabela:: - // Agora $articles é uma instância de nossa classe ArticlesTable. - // Prior to 3.6 use TableRegistry::get('Articles') $articles = TableRegistry::getTableLocator()->get('Articles'); -Agora que temos uma classe de tabela concreta, nós provavelmente vamos querer -usar uma classe de entidade concreta. As classes de entidade permitem definir -métodos de acesso, métodos mutantes, definir lógica personalizada para os -registros individuais e muito mais. Vamos começar adicionando o seguinte para -**src/Model/Entity/Article.php** após a tag de abertura ``get('Articles'); - $query = $articles->find(); + $articles = $this->fetchTable('Articles'); + $resultset = $articles->find()->all(); - foreach ($query as $row) { - // Cada linha é agora uma instância de nossa classe Article. + foreach ($resultset as $row) { + // Each row is now an instance of our Article class. echo $row->title; } -CakePHP utiliza convenções de nomenclatura para ligar as classes de tabela e -entidade juntas. Se você precisar customizar qual entidade uma tabela utiliza, -você pode usar o método ``entityClass()`` para definir nomes de classe -específicos. +O CakePHP usa convenções de nomenclatura para vincular as classes Table e Entity. Se +você precisar personalizar qual entidade uma tabela usa, pode usar o método +``entityClass()`` para definir um nome de classe específico. -Veja os capítulos em :doc:`/orm/table-objects` e :doc:`/orm/entities` para mais -informações sobre como usar objetos de tabela e entidades em sua aplicação. +Consulte os capítulos em :doc:`/orm/table-objects` e :doc:`/orm/entities` para obter mais +informações sobre como usar objetos de tabela e entidades em seu aplicativo. -Mais informação -=============== +Mais informações +================ .. toctree:: :maxdepth: 2 @@ -121,11 +113,11 @@ Mais informação orm/query-builder orm/table-objects orm/entities + orm/associations orm/retrieving-data-and-resultsets orm/validation orm/saving-data orm/deleting-data - orm/associations orm/behaviors orm/schema-system - console-and-shells/orm-cache + console-commands/schema-cache diff --git a/pt/orm/database-basics.rst b/pt/orm/database-basics.rst index fc338e054b..946f1bda09 100644 --- a/pt/orm/database-basics.rst +++ b/pt/orm/database-basics.rst @@ -967,7 +967,7 @@ com o método ``cacheMetadata()``:: $connection->cacheMetadata('orm_metadata'); O CakePHP também inclui uma ferramenta CLI para gerenciar caches de metadados. -Confira o capítulo :doc:`/console-and-shells/orm-cache` para obter mais informações. +Confira o capítulo :doc:`/console-commands/schema-cache` para obter mais informações. Criando Banco de Dados ====================== diff --git a/pt/phinx.rst b/pt/phinx.rst new file mode 100644 index 0000000000..2f0093792f --- /dev/null +++ b/pt/phinx.rst @@ -0,0 +1,4 @@ +Migrações Phinx +############### + +Esta página foi `movida `__. diff --git a/pt/quickstart.rst b/pt/quickstart.rst index 4f38d0bf2d..d593ca20d6 100644 --- a/pt/quickstart.rst +++ b/pt/quickstart.rst @@ -1,11 +1,13 @@ Guia de Início Rápido ********************* -A melhor forma de viver experiências e aprender sobre CakePHP é sentar e -construir algo. Para começar nós iremos construir uma aplicação simples de blog. +A melhor maneira de experimentar e aprender CakePHP é sentar e construir algo. +Para começar, construiremos um aplicativo simples de gerenciamento de conteúdo. -.. include:: /tutorials-and-examples/bookmarks/intro.rst -.. include:: /tutorials-and-examples/bookmarks/part-two.rst +.. include:: /tutorials-and-examples/cms/installation.rst +.. include:: /tutorials-and-examples/cms/database.rst +.. include:: /tutorials-and-examples/cms/articles-model.rst +.. include:: /tutorials-and-examples/cms/articles-controller.rst .. meta:: :title lang=pt: Guia de Início Rápido diff --git a/pt/release-policy.rst b/pt/release-policy.rst new file mode 100644 index 0000000000..01dc68bc81 --- /dev/null +++ b/pt/release-policy.rst @@ -0,0 +1,69 @@ +Release Policy +############## + +CakePHP follows Semantic Versioning for all releases. This follows the versioning +convention of **major.minor.patch**. + +The development team tries to guarantee each release follow the restrictions and +and guarantees below. + +Major Releases +-------------- + +Major releases are generally not backwards compatible. Although CakePHP tries +to not change many large features in major releases, there are API changes. + +Changes in major release can include almost anything but are always used to +remove deprecated features and update interfaces. + +Any behavior changes that are not backwards compatible are made in major changes. + +Each major release typically comes with an upgrade guide and many automatic +code upgrades using rector. + +Minor Releases +-------------- + +Minor release are generally backwards compatible with the previous minor and patch +release. + +Features might be deprecated, but they are never removed in a minor release. + +Interfaces are not changed, but annotations might be added for new methods exposed +in implementations provided by CakePHP. + +New features are usually only added in minor releases so users can follow migration +notes. New features can also include new exceptions thrown when behavior is fixed +or bugs are reported. + +Behavior changes that require documentation are made in minor releases, but these are +still typically backwards compatible. Some exceptions can be made if the issue is severe. + +.. note: + Minor releases are also known as point releases. + +Patch Releases +---------------- + +Patch releases are always backwards compatible. Only changes that fix broken features +are made. + +Typically, users should be able to rely on patch releases not changing behavior except +to fix an issue. + +Issues that change long-standing behavior are typically not in patch releases. These are +considered behavior changes and will go into either minor or major releases so users can +migrate. + +.. note: + Patch releases are also known as bug fix releases. + +Experimental Features +--------------------- + +When a new feature is added where the API is still changing, it can be marked **experimental**. + +Experimental features should follow the same minor and bug fix release convention. However, +API changes can go into minor releases which might significantly change behavior. + +Users should always expect an API to change before experimental features are fully released. diff --git a/pt/standalone-packages.rst b/pt/standalone-packages.rst new file mode 100644 index 0000000000..ccc0ffb66d --- /dev/null +++ b/pt/standalone-packages.rst @@ -0,0 +1,77 @@ +Standalone Packages +################### + +The CakePHP core is split into various standalone packages which can +used independently. + +`ORM `_ +--------------------------------------- + +A flexible, lightweight and powerful Object-Relational Mapper for PHP, +implemented using the DataMapper pattern. + +`Database `_ +------------------------------------------------- + +Flexible and powerful Database abstraction library with a familiar PDO-like API. + +`Datasource `_ +----------------------------------------------------- + +Provides connection managing and traits for Entities and Queries that can be +reused for different datastores. + +`HTTP `_ +----------------------------------------- + +PSR-18, PSR-15 compliant HTTP client and server libraries. + +`Console `_ +----------------------------------------------- + +A library for building command line applications from a set of commands. + +`Collection `_ +----------------------------------------------------- + +A library providing a set of tools to manipulate arrays or Traversable objects. + +`I18n `_ +----------------------------------------- + +Provides support for message translation and localization for dates and numbers. + +`Cache `_ +------------------------------------------- + +PSR-16 compliant caching library with support for multiple caching backends. + +`Log `_ +--------------------------------------- + +PSR-3 compliant logging library with support for multiple different streams. + +`Event `_ +------------------------------------------- + +The event dispatcher library. + +`Utility `_ +----------------------------------------------- + +Utility classes such as Inflector, Text, Hash, Security and Xml. + +`Validation `_ +----------------------------------------------------- + +Validation library from CakePHP. + +`Form `_ +----------------------------------------- + +Form abstraction used to create forms not tied to ORM backed models, or to other +permanent datastores. + +.. meta:: + :title lang=en: Split Packages + :keywords lang=en: packages, cakephp, orm, database, http client, http server, utility, events, log, cache diff --git a/pt/topics.rst b/pt/topics.rst index 2db490545d..35216f055b 100644 --- a/pt/topics.rst +++ b/pt/topics.rst @@ -24,14 +24,14 @@ Introdução aos principais tópicos do CakePHP: * :doc:`/core-libraries/form` * :doc:`/development/sessions` * :doc:`/development/rest` -* :doc:`/controllers/components/authentication` -* :doc:`/controllers/components/pagination` +* :doc:`/controllers/pagination` +* :ref:`csrf-middleware` * :doc:`/core-libraries/email` * :doc:`/views/helpers/form` * :doc:`/views/helpers/html` * :doc:`/core-libraries/validation` * :doc:`/development/testing` * :doc:`/deployment` -* :doc:`/console-and-shells` +* :doc:`/console-commands` * :doc:`/contributing` -* :doc:`/tutorials-and-examples` +* :doc:`/tutorials-and-examples` \ No newline at end of file diff --git a/pt/tutorials-and-examples.rst b/pt/tutorials-and-examples.rst index 678097e49d..b61e652e17 100644 --- a/pt/tutorials-and-examples.rst +++ b/pt/tutorials-and-examples.rst @@ -14,18 +14,11 @@ componentes existentes. tutorials-and-examples/cms/installation tutorials-and-examples/cms/database + tutorials-and-examples/cms/articles-model tutorials-and-examples/cms/articles-controller + tutorials-and-examples/cms/tags-and-users tutorials-and-examples/cms/authentication - -.. toctree:: - :maxdepth: 1 - - 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 + tutorials-and-examples/cms/authorization .. meta:: :title lang=pt: Tutoriais & Examplos diff --git a/pt/tutorials-and-examples/blog-auth-example/auth.rst b/pt/tutorials-and-examples/blog-auth-example/auth.rst deleted file mode 100644 index 9f3cb94f4a..0000000000 --- a/pt/tutorials-and-examples/blog-auth-example/auth.rst +++ /dev/null @@ -1,435 +0,0 @@ -Tutorial - Criando um Blog - Autenticação e Autorização -####################################################### - -Continuando com o exemplo de :doc:`/tutorials-and-examples/blog/blog`, imagine -que queríamos garantir o acesso a certas URLs, com base no usuário logado. Temos -também uma outra exigência: permitir que o nosso blog para tenha vários autores -que podem criar, editar e excluir seus próprios artigos, e bloquear para que -outros autores não façam alterações nos artigos que não lhes pertencem. - -Criando todo o código relacionado ao Usuário -============================================ - -Primeiro, vamos criar uma nova tabela no banco de dados do blog para armazenar -dados de nossos usuários:: - - 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 - ); - -Respeitado as convenções do CakePHP para nomear tabelas, mas também aproveitando -de outras convenção: Usando as colunas ``email`` e ``password`` da tabela de -usuários, CakePHP será capaz de configurar automaticamente a maioria das coisas -para nós, na implementação do login do usuário. - -O próximo passo é criar a nossa classe UsersTable, responsável por encontrar, -salvar e validar os dados do usuário:: - - // 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) - { - return $validator - ->notEmpty('email', 'Email é necessário') - ->email('email') - ->notEmpty('password', 'Senha é necessária') - ->notEmpty('role', 'Função é necessária') - ->add('role', 'inList', [ - 'rule' => ['inList', ['admin', 'author']], - 'message' => 'Por favor informe uma função válida' - ]); - } - - } - -Vamos também criar o nosso UsersController. O conteúdo a seguir corresponde a -partes de uma classe UsersController básica gerado atráves do utilitário de -geração de código ``bake`` fornecido com CakePHP:: - - // src/Controller/UsersController.php - - namespace App\Controller; - - use App\Controller\AppController; - use Cake\Event\Event; - - class UsersController extends AppController - { - - public function beforeFilter(Event $event) - { - parent::beforeFilter($event); - $this->Auth->allow('add'); - } - - 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->newEntity(); - if ($this->request->is('post')) { - $user = $this->Users->patchEntity($user, $this->request->getData()); - if ($this->Users->save($user)) { - $this->Flash->success(__('O usuário foi salvo.')); - - return $this->redirect(['action' => 'add']); - } - $this->Flash->error(__('Não é possível adicionar o usuário.')); - } - $this->set('user', $user); - } - - } - -Da mesma maneira que criamos as ``views`` para os nossos artigos usando -a ferramenta de geração de código, podemos implementar as ``views`` do -usuário. Para o propósito deste tutorial, vamos mostrar apenas o add.php: - -.. code-block:: php - - - -
- Form->create($user) ?> -
- - Form->input('email') ?> - Form->input('password') ?> - Form->input('role', [ - 'options' => ['admin' => 'Admin', 'author' => 'Author'] - ]) ?> -
- Form->button(__('Submit')); ?> - Form->end() ?> -
- -Autenticação (Login e Logout) -============================= - -Agora estamos prontos para adicionar a nossa camada de autenticação. Em CakePHP -isso é tratado pelo :php:class:`Cake\\Controller\\Component\\AuthComponent`, uma -classe responsável por exigir o ``login`` para determinadas ações, a manipulação -de ``login`` e ``logout`` de usuário, e também permite as ações para que estão -autorizados. - -Para adicionar este componente em sua aplicação abra o arquivos -**src/Controller/AppController.php** e adicione as seguintes linha:: - - // src/Controller/AppController.php - - namespace App\Controller; - - use Cake\Controller\Controller; - use Cake\Event\Event; - - class AppController extends Controller - { - //... - - public function initialize() - { - $this->loadComponent('Flash'); - $this->loadComponent('Auth', [ - 'loginRedirect' => [ - 'controller' => 'Articles', - 'action' => 'index' - ], - 'logoutRedirect' => [ - 'controller' => 'Pages', - 'action' => 'display', - 'home' - ] - ]); - } - - public function beforeFilter(Event $event) - { - $this->Auth->allow(['index', 'view', 'display']); - } - //... - } - -Não há muito para ser configurado, como usamos as convenções para a tabela -de usuários. Nós apenas configuramos as URLs que serão carregados após o -``login`` e ``logout``, estás ações são realizadas no nosso caso para os -``/articles/`` e ``/`` respectivamente. - -O que fizemos na função ``beforeFilter()`` foi dizer ao ``AuthComponent`` para -não exigir ``login`` em todos ``index()`` e ``view()``, em cada controlador. -Queremos que os nossos visitantes sejam capaz de ler e listar as entradas sem -registrar-se no site. - -Agora, precisamos ser capaz de registrar novos usuários, salvar seu ``email`` -e ``password``, e mais importante, o hash da senha para que ele não seja -armazenado como texto simples no nosso banco de dados. Vamos dizer ao -``AuthComponet`` para permitir que usuários deslogados acessem a função add e -execute as ações de ``login`` e ``logout``:: - - // src/Controller/UsersController.php - - public function beforeFilter(Event $event) - { - parent::beforeFilter($event); - // Permitir aos usuários se registrarem e efetuar logout. - // Você não deve adicionar a ação de "login" a lista de permissões. - // Isto pode causar problemas com o funcionamento normal do AuthComponent. - $this->Auth->allow(['add', 'logout']); - } - - 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(__('Usuário ou senha ínvalido, tente novamente')); - } - } - - public function logout() - { - return $this->redirect($this->Auth->logout()); - } - -O hashing da senha ainda não está feito, precisamos de uma classe a fim de -manipular sua geração. Crie o arquivo **src/Model/Entity/User.php** -e adicione a seguinte trecho:: - - // src/Model/Entity/User.php - namespace App\Model\Entity; - - use Cake\Auth\DefaultPasswordHasher; - use Cake\ORM\Entity; - - class User extends Entity - { - - // Gera conjunto de todos os campos exceto o com a chave primária. - protected array $_accessible = [ - '*' => true, - 'id' => false - ]; - - // ... - - protected function _setPassword($password) - { - if (strlen($password) > 0) { - return (new DefaultPasswordHasher)->hash($password); - } - } - - // ... - } - -Agora, a senha criptografada usando a classe ``DefaultPasswordHasher``. -Está faltando apenas o arquivo para exibição da tela de login. -Abra o arquivo **templates/Users/login.php** e adicione as seguintes linhas: - -.. code-block:: php - - - -
- Flash->render('auth') ?> - Form->create() ?> -
- - Form->input('email') ?> - Form->input('password') ?> -
- Form->button(__('Login')); ?> - Form->end() ?> -
- -Agora você pode registrar um novo usuário, acessando a URL ``/users/add`` -e faça login com o usuário recém-criado, indo para a URL ``/users/login``. -Além disso, tente acessar qualquer outro URL que não tenha sido explicitamente -permitido, como ``/articles/add``, você vai ver que o aplicativo redireciona -automaticamente para a página de login. - -E é isso! Parece simples demais para ser verdade. Vamos voltar um pouco para -explicar o que aconteceu. A função ``beforeFilter()`` está falando para o -AuthComponent não solicitar um login para a ação ``add()`` em adição as ações -``index()`` e ``view()`` que foram prontamente autorizadas na função -``beforeFilter()`` do AppController. - -A ação ``login()`` chama a função ``$this->Auth->identify()`` da AuthComponent, -que funciona sem qualquer outra configuração porque estamos seguindo convenções, -como mencionado anteriormente. Ou seja, ter uma tabela de usuários com um -``email`` e uma coluna de ``password``, e usamos um form para postar os dados -do usuário para o controller. Esta função retorna se o login foi bem sucedido ou -não, e caso ela retorne sucesso, então nós redirecionamos o usuário para a URL -que configuramos quando adicionamos o AuthComponent em nossa aplicação. - -O logout funciona quando acessamos a URL ``/users/logout`` que irá redirecionar -o usuário para a url configurada em logoutUrl. Essa url é acionada quando a -função ``AuthComponent::logout()``. - -Autorização (quem tem permissão para acessar o que) -=================================================== - -Como afirmado anteriormente, nós estamos convertendo esse blog em uma ferramenta -multi usuário de autoria, e para fazer isso, precisamos modificar a tabela de -artigos um pouco para adicionar a referência à tabela de Usuários:: - - ALTER TABLE articles ADD COLUMN user_id INT(11); - -Além disso, uma pequena mudança no ArticlesController é necessário para -armazenar o usuário conectado no momento como uma referência para o artigo -criado:: - - // src/Controller/ArticlesController.php - - public function add() - { - $article = $this->Articles->newEmptyEntity(); - if ($this->request->is('post')) { - $article = $this->Articles->patchEntity($article, $this->request->getData()); - // Adicione esta linha - $article->user_id = $this->Auth->user('id'); - // Você também pode fazer o seguinte - //$newData = ['user_id' => $this->Auth->user('id')]; - //$article = $this->Articles->patchEntity($article, $newData); - if ($this->Articles->save($article)) { - $this->Flash->success(__('Seu artigo foi salvo.')); - - return $this->redirect(['action' => 'index']); - } - $this->Flash->error(__('Não foi possível adicionar seu artigo.')); - } - $this->set('article', $article); - } - -A função ``user()`` fornecida pelo componente retorna qualquer coluna do usuário -logado no momento. Nós usamos esse metódo para adicionar a informação dentro de -request data para que ela seja salva. - -Vamos garantir que nossa app evite que alguns autores editem ou apaguem posts de -outros. Uma regra básica para nossa aplicação é que usuários admin possam -acessar qualquer url, enquanto usuários normais (o papel author) podem somente -acessar as actions permitidas. Abra novamente a classe AppController e adicione -um pouco mais de opções para as configurações do Auth:: - - // src/Controller/AppController.php - - public function initialize() - { - $this->loadComponent('Flash'); - $this->loadComponent('Auth', [ - 'authorize' => ['Controller'], // Adicione está linha - 'loginRedirect' => [ - 'controller' => 'Articles', - 'action' => 'index' - ], - 'logoutRedirect' => [ - 'controller' => 'Pages', - 'action' => 'display', - 'home' - ] - ]); - } - - public function isAuthorized($user) - { - // Admin pode acessar todas as actions - if (isset($user['role']) && $user['role'] === 'admin') { - return true; - } - - // Bloqueia acesso por padrão - return false; - } - -Acabamos de criar um mecanismo de autorização muito simples. Nesse caso os -usuários com papel ``admin`` poderão acessar qualquer url no site quando -estiverem logados, mas o restante dos usuários (author) não podem acessar -qualquer coisa diferente dos usuários não logados. - -Isso não é exatamente o que nós queremos, por isso precisamos corrigir nosso -metódo ``isAuthorized()`` para fornecer mais regras. Mas ao invés de fazer -isso no AppController, vamos delegar a cada controller para suprir essas -regras extras. As regras que adicionaremos para o ``add`` de ArticlesController -deve permitir ao autores criarem os posts mas evitar a edição de posts que não -sejam deles. Abra o arquivo **src/Controller/ArticlesController.php** e adicione -o seguinte conteúdo:: - - // src/Controller/ArticlesController.php - - public function isAuthorized($user) - { - // Todos os usuários registrados podem adicionar artigos - if ($this->request->getParam('action') === 'add') { - return true; - } - - // Apenas o proprietário do artigo pode editar e excluí - if (in_array($this->request->getParam('action'), ['edit', 'delete'])) { - $articleId = (int)$this->request->getParam('pass.0'); - if ($this->Articles->isOwnedBy($articleId, $user['id'])) { - return true; - } - } - - return parent::isAuthorized($user); - } - -Estamos sobrescrevendo a chamada ``isAuthorized()``do AppController e -internamente verificando na classe pai se o usuário está autorizado. Caso não -esteja, então apenas permitem acessar a action ``add``, e condicionalmente -action ``edit`` e ``delete``. Uma última coisa não foi implementada. Para dizer -ou não se o usuário está autorizado a editar o artigo, nós estamos chamando uma -função ``isOwnedBy()`` na tabela artigos. Vamos, então, implementar essa -função:: - - // src/Model/Table/ArticlesTable.php - - public function isOwnedBy($articleId, $userId) - { - return $this->exists(['id' => $articleId, 'user_id' => $userId]); - } - -Isso conclui então nossa autorização simples e nosso tutorial de autorização. -Para garantir o UsersController você pode seguir as mesmas técnicas que usamos -para ArticlesController, você também pode ser mais criativo e codificar algumas -coisas mais gerais no AppController para suas próprias regras baseadas em -papéis. - -Se precisar de mais controle, nós sugerimos que leia o guia completo do Auth -:doc:`/controllers/components/authentication` seção onde você encontrará mais -sobre a configuração do componente, criação de classes de Autorização -customizadas, e muito mais. - -Sugerimos as seguintes leituras -------------------------------- - -1. :doc:`/bake/usage` Generating basic CRUD code -2. :doc:`/controllers/components/authentication`: User registration and login - -.. meta:: - :title lang=pt: Simple Authentication and Authorization Application - :keywords lang=pt: auto increment,authorization application,model user,array,conventions,authentication,urls,cakephp,delete,doc,columns diff --git a/pt/tutorials-and-examples/blog/blog.rst b/pt/tutorials-and-examples/blog/blog.rst deleted file mode 100755 index 5b3837f376..0000000000 --- a/pt/tutorials-and-examples/blog/blog.rst +++ /dev/null @@ -1,217 +0,0 @@ -Tutorial - Criando um Blog - Parte 1 -#################################### - -Este tutorial irá orientá-lo através da criação de um simples blog. -Faremos a instalação do CakePHP, criaremos um banco de dados e implementaremos a -lógica capaz de listar, adicionar, editar e apagar postagens do blog. - -Aqui está o que você vai precisar: - -#. Um servidor web em funcionamento. Nós iremos assumir que você esteja usando - o Apache, embora as instruções para outros servidores sejam bem similares. - Talvez seja preciso alterar um pouco a configuração do servidor, mas a - maioria das pessoas pode ter o CakePHP instalado e funcionando sem qualquer - trabalho extra. Certifique-se de que você tem o PHP |minphpversion| ou superior, - e que as extensões *mbstring* e *intl* estejam habilitadas no PHP. - Caso não saiba a versão do PHP que está instalada, utilize a função - ``phpinfo()`` ou digite ``php -v`` no seu terminal de comando. - -#. Um servidor de banco de dados. Nós vamos usar o servidor *MySQL* neste - tutorial. Você precisa saber o mínimo sobre SQL para então criar um banco de - dados, depois disso o CakePHP vai assumir as rédeas. Já que usaremos - o *MySQL*, também certifique-se que a extensão ``pdo_mysql`` está - habilitada no PHP. - -#. Conhecimento básico sobre PHP. - -Vamos começar! - -Instalação do CakePHP -===================== - -A maneira mais fácil de instalar o CakePHP é usando Composer, um gerenciador -de dependências para o PHP. Se trata de uma forma simples de instalar o -CakePHP a partir de seu terminal ou prompt de comando. Primeiro, você -precisa baixar e instalar o Composer, caso você já não o tenha. Se possuir -instalado o programa *cURL*, basta executar o seguinte comando:: - -.. code-block:: console - - curl -s https://getcomposer.org/installer | php - -Você também pode baixar o arquivo ``composer.phar`` do -`site `_ oficial do Composer. - -Em seguida, basta digitar a seguinte linha de comando no seu terminal a partir -do diretório onde se localiza o arquivo ``composer.phar`` para instalar o -esqueleto da aplicação do CakePHP no diretório [nome_do_app]. :: - - php composer.phar create-project --prefer-dist cakephp/app:4.* [nome_do_app] - -A vantagem de usar o Composer é que ele irá completar automaticamente um conjunto -importante de tarefas, como configurar corretamente as permissões de pastas -e criar o **config/app.php** para você. - -Há outras maneiras de instalar o CakePHP. Se você não puder ou não quiser usar -o Composer, confira a seção :doc:`/installation`. - -Independentemente de como você baixou o CakePHP, uma vez que sua instalação -for concluída, a estrutura dos diretórios deve ficar parecida com o seguinte:: - - /nome_do_app - /bin - /config - /logs - /plugins - /src - /tests - /tmp - /vendor - /webroot - .editorconfig - .gitignore - .htaccess - .travis.yml - composer.json - index.php - phpunit.xml.dist - README.md - -Agora pode ser um bom momento para aprender sobre como a estrutura de diretórios -do CakePHP funciona: Confira a seção :doc:`/intro/cakephp-folder-structure`. - -Permissões dos diretórios tmp e logs -==================================== - -Os diretórios **tmp** e **logs** precisam ter permissões adequadas para que -possam ser alterados pelo seu servidor web. Se você usou o Composer na -instalação, ele deve ter feito isso por você e confirmado com uma mensagem -"Permissions set on ". Se você ao invés disso, recebeu uma mensagem de -erro ou se quiser fazê-lo manualmente, a melhor forma seria descobrir por qual -usuário o seu servidor web é executado (````) e alterar o -proprietário desses dois diretórios para este usuário. -Os comandos finais a serem executados (em \*nix) podem ser algo como:: - - chown -R www-data tmp - chown -R www-data logs - -Se por alguma razão o CakePHP não puder escrever nesses diretórios, você será -informado por uma advertência enquanto não estiver em modo de produção. - -Embora não seja recomendado, se você é incapaz de redefinir as permissões -do seu servidor web, você pode simplesmente alterar as permissões de gravação -diretamente nos diretórios, executando os seguintes comandos:: - - chmod -R 777 tmp - chmod -R 777 logs - -Criando o banco de dados do Blog -================================ - -Em seguida, vamos configurar o banco de dados para o nosso blog. Se você -ainda não tiver feito isto, crie um banco de dados vazio para usar -neste tutorial, com um nome de sua escolha, por exemplo, ``cake_blog``. -Agora, vamos criar uma tabela para armazenar nossos artigos: - -.. code-block:: mysql - - -- Primeiro, criamos a tabela articles - CREATE TABLE articles ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - title VARCHAR(50), - body TEXT, - created DATETIME DEFAULT NULL, - modified DATETIME DEFAULT NULL - ); - -Nós vamos também inserir alguns artigos para usarmos em nossos testes. -Execute os seguintes comandos SQL em seu banco de dados: - -.. code-block:: mysql - - -- Então inserimos articles para testes - INSERT INTO articles (title,body,created) - VALUES ('The title', 'This is the article body.', NOW()); - INSERT INTO articles (title,body,created) - VALUES ('A title once again', 'And the article body follows.', NOW()); - INSERT INTO articles (title,body,created) - VALUES ('Title strikes back', 'This is really exciting! Not.', NOW()); - -Os nomes de tabelas e colunas que usamos não foram arbitrárias. Usando -:doc:`convenções de nomenclatura ` do CakePHP, podemos -alavancar o desenvolvimento e acelerar a configuração do framework. O CakePHP -é flexível o suficiente para acomodar até mesmo esquemas de banco de dados -legados inconsistentes, mas aderir às convenções vai lhe poupar tempo. - -Configurando o banco de dados do Blog -===================================== - -Em seguida, vamos dizer ao CakePHP onde nosso banco de dados está e como se -conectar a ele. Para muitos, esta será a primeira e última vez que será -necessário configurar algo. - -A configuração é bem simples e objetiva: basta alterar os valores no array -``Datasources.default`` localizado no arquivo **config/app.php**, pelos valores -que se aplicam à sua configuração. Um exemplo completo de configurações deve -se parecer como o seguinte:: - - return [ - // Mais configurações acima. - 'Datasources' => [ - 'default' => [ - 'className' => 'Cake\Database\Connection', - 'driver' => 'Cake\Database\Driver\Mysql', - 'persistent' => false, - 'host' => 'localhost', - 'username' => 'cakephp', - 'password' => 'AngelF00dC4k3~', - 'database' => 'cake_blog', - 'encoding' => 'utf8', - 'timezone' => 'UTC', - 'cacheMetadata' => true, - ], - ], - // Mais configurações abaixo. - ]; - -Depois de salvar o arquivo **config/app.php**, você deve notar a -mensagem *CakePHP is able to connect to the database* ao acessar o Blog pelo -seu navegador. - -.. note:: - Uma cópia do arquivo de configuração padrão do CakePHP pode ser encontrada - em **config/app.default.php**. - -Configurações opcionais -======================= - -Há alguns outros itens que podem ser configurados. Muitos desenvolvedores -completam esta lista de itens, mas os mesmos não são obrigatórios para este -tutorial. Um deles é definir uma sequência personalizada (ou "salt") para uso em -hashes de segurança. - -A sequência personalizada (ou salt) é utilizada para gerar hashes de segurança. -Se você utilizou o Composer, ele cuidou disso para você durante a instalação. -Apesar disso, você precisa alterar a sequência personalizada padrão editando -o arquivo **config/app.php**. Não importa qual será o novo valor, somente deverá ser -algo difícil de descobrir:: - - 'Security' => [ - 'salt' => 'algum valor longo contendo uma mistura aleatória de valores.', - ], - -Observação sobre o mod_rewrite -============================== - -Ocasionalmente, novos usuários irão se atrapalhar com problemas de mod_rewrite. -Por exemplo, se a página de boas vindas do CakePHP parecer estranha (sem -imagens ou estilos CSS). Isto provavelmente significa que o mod_rewrite não está -funcionando em seu servidor. Por favor, verifique a seção -:ref:`url-rewriting` para obter ajuda e resolver qualquer problema relacionado. - -Agora continue o tutorial em :doc:`/tutorials-and-examples/blog/part-two` e -inicie a construção do seu Blog com o CakePHP. - -.. meta:: - :title lang=pt: Tutorial - Criando um Blog - :keywords lang=pt: tutorial, guide, blog diff --git a/pt/tutorials-and-examples/blog/part-three.rst b/pt/tutorials-and-examples/blog/part-three.rst deleted file mode 100644 index f7276e1a5b..0000000000 --- a/pt/tutorials-and-examples/blog/part-three.rst +++ /dev/null @@ -1,395 +0,0 @@ -Tutorial - Criando um Blog - Parte 3 -#################################### - -Criar uma arvore de Categoria -============================= - -Vamos continuar o nosso aplicativo de blog e imaginar que queremos categorizar -os nossos artigos. Queremos que as categorias sejam ordenadas, e para isso, -vamos usar o comportamento de árvore para nos ajudar a organizar as categorias. - -Mas primeiro, precisamos modificar nossas tabelas. - -Migração de Plugin -================== - -Nós vamos usar o plugin de migrações para criar uma tabela em nosso banco de -dados. Se você tem a tabela articles no seu banco de dados, apague. Agora abra -o arquivo composer.json do seu aplicativo. Normalmente, você veria que o plugin -de migração já está requisitando. Se não, addicione atráves da execução:: - - composer require cakephp/migrations:~1.0 - -O plugin de migração agora está na pasta de sua aplicação. Também, adicionar -``Plugin::load('Migrations');`` para o arquivo bootstrap.php do seu aplicativo. - -Uma vez que o plugin está carregado, execute o seguinte comando para criar um -arquivo de migração:: - - bin/cake bake migration CreateArticles title:string body:text category_id:integer created modified - -Um arquivo de migração será gerado na pasta /config/Migrations com o seguinte: - -.. code-block:: php - - 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(); - } - } - -Executar outro comando para criar uma tabela de categorias. Se você precisar -especificar um comprimento de campo, você pode fazê-lo dentro de colchetes no -tipo de campo, ou seja:: - - bin/cake bake migration CreateCategories parent_id:integer lft:integer[10] rght:integer[10] name:string[100] description:string created modified - -Isso irá gerar o seguinte arquivo no config/Migrations: - -.. code-block:: php - - 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(); - } - } - -Agora que os arquivos de migração estão criadas, você pode editá-los antes de -criar suas tabelas. Precisamos mudar o 'null' => false para o campo parent_id -com ``'null' => true`` porque uma categoria de nível superior tem null no parent_id - -Execute o seguinte comando para criar suas tabelas:: - - bin/cake migrations migrate - -Modificando as Tabelas -====================== - -Com nossas tabelas configuradas, agora podemos nos concentrar em categorizar os -nossos artigos. - -Supomos que você já tem os arquivos (Tabelas, controladores e modelos dos -artigos) da parte 2. Então vamos adicionar as referências a categorias. - -Precisamos associar os artigos e categorias juntos nas tabelas. Abra o arquivo -src/Model/Table/ArticlesTable.php e adicione o seguinte: - -.. code-block:: php - - // src/Model/Table/ArticlesTable.php - namespace App\Model\Table; - - use Cake\ORM\Table; - - class ArticlesTable extends Table - { - public function initialize(array $config) - { - $this->addBehavior('Timestamp'); - // Just add the belongsTo relation with CategoriesTable - $this->belongsTo('Categories', [ - 'foreignKey' => 'category_id', - ]); - } - } - -Gerar código esqueleto por categorias -===================================== - -Crie todos os arquivos pelo comando bake:: - - bin/cake bake model Categories - bin/cake bake controller Categories - bin/cake bake template Categories - -A ferramenta bake criou todos os seus arquivos em um piscar de olhos. Você pode -fazer uma leitura rápida se quiser familiarizar como o CakePHP funciona. - -.. note:: - Se você estiver no Windows lembre-se de usar \\ em vez de /. - -Você vai precisar editar o seguinte em **templates/Categories/add.php** -e **templates/Categories/edit.php**:: - - echo $this->Form->input('parent_id', [ - 'options' => $parentCategories, - 'empty' => 'No parent category' - ]); - -Anexar árvore de compartamento para CategoriesTable -=================================================== - -O :doc:`TreeBehavior ` ajuda você a gerenciar as estruturas -de árvore hierárquica na tabela do banco de dados. Usa a lógica MPTT para -gerenciar os dados. Estruturas de árvore MPTT são otimizados para lê, o que -muitas vezes torna uma boa opção para aplicações pesadas, como ler blogs. - -Se você abrir o arquivo src/Model/Table/CategoriesTable.php, você verá que -o TreeBehavior foi anexado a sua CategoriesTable no método initialize(). Bake -acrescenta esse comportamento para todas as tabelas que contêm lft e colunas -rght:: - - $this->addBehavior('Tree'); - -Com o TreeBehavior anexado você vai ser capaz de acessar alguns recursos como -a reordenação das categorias. Vamos ver isso em um momento. - -Mas, por agora, você tem que remover as seguintes entradas em seus Categorias de -adicionar e editar arquivos de modelo:: - - echo $this->Form->input('lft'); - echo $this->Form->input('rght'); - -Além disso, você deve desabilitar ou remover o requirePresence do validador, -tanto para a ``lft`` e ``rght`` nas colunas em seu modelo CategoriesTable: - -.. code-block:: php - - public function validationDefault(Validator $validator) - { - $validator - ->add('id', 'valid', ['rule' => 'numeric']) - ->allowEmpty('id', 'create'); - - $validator - ->add('lft', 'valid', ['rule' => 'numeric']) - // ->requirePresence('lft', 'create') - ->notEmpty('lft'); - - $validator - ->add('rght', 'valid', ['rule' => 'numeric']) - // ->requirePresence('rght', 'create') - ->notEmpty('rght'); - } - -Esses campos são automaticamente gerenciados pelo TreeBehavior quando uma -categoria é salvo. - -Usando seu navegador, adicione algumas novas categorias usando os -``/yoursite/categories/add`` ação do controlador. - -Reordenar categorias com TreeBahavior -===================================== - -Em seu arquivo de modelo de índices de categorias, você pode listar as -categorias e reordená-los. - -Vamos modificar o método de índice em sua CategoriesController.php e adicionar -moveUp() e moveDown() para ser capaz de reordenar as categorias na árvore: - -.. code-block:: php - - class CategoriesController extends AppController - { - public function index() - { - $categories = $this->Categories->find() - ->order(['lft' => 'ASC']); - $this->set(compact('categories')); - $this->set('_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('The category has been moved Up.'); - } else { - $this->Flash->error('The category could not be moved up. Please, try again.'); - } - - 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('The category has been moved down.'); - } else { - $this->Flash->error('The category could not be moved down. Please, try again.'); - } - - return $this->redirect($this->referer(['action' => 'index'])); - } - } - -Em templates/Categories/index.php substituir o conteúdo existente com: - -.. code-block:: php - -
-

-
    -
  • Html->link(__('New Category'), ['action' => 'add']) ?>
  • -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
IdParent IdLftRghtNameDescriptionCreated
id ?>parent_id ?>lft ?>rght ?>name) ?>description) ?>created) ?> - Html->link(__('View'), ['action' => 'view', $category->id]) ?> - Html->link(__('Edit'), ['action' => 'edit', $category->id]) ?> - Form->postLink(__('Delete'), ['action' => 'delete', $category->id], ['confirm' => __('Are you sure you want to delete # {0}?', $category->id)]) ?> - Form->postLink(__('Move down'), ['action' => 'moveDown', $category->id], ['confirm' => __('Are you sure you want to move down # {0}?', $category->id)]) ?> - Form->postLink(__('Move up'), ['action' => 'moveUp', $category->id], ['confirm' => __('Are you sure you want to move up # {0}?', $category->id)]) ?> -
-
- -Modificando o ArticlesController -================================ - -Em nossa ArticlesController, vamos obter a lista de todas as categorias. Isto -irá permitir-nos para escolher uma categoria para um artigo ao criar ou editar -ele: - -.. code-block:: php - - // src/Controller/ArticlesController.php - namespace App\Controller; - - // Prior to 3.6 use Cake\Network\Exception\NotFoundException - 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(__('Your article has been saved.')); - - return $this->redirect(['action' => 'index']); - } - $this->Flash->error(__('Unable to add your article.')); - } - $this->set('article', $article); - - // Just added the categories list to be able to choose - // one category for an article - $categories = $this->Articles->Categories->find('treeList'); - $this->set(compact('categories')); - } - } - -Modificando os artigos Templates -================================ - -O artigo adicionado deveria se parecer como isto: - -.. code-block:: php - - - -

Add Article

- Form->create($article); - // just added the categories input - echo $this->Form->input('category_id'); - echo $this->Form->input('title'); - echo $this->Form->input('body', ['rows' => '3']); - echo $this->Form->button(__('Save Article')); - echo $this->Form->end(); - -Quando você vai para o endereço ``/yoursite/categories/add`` você deve ver uma lista -de categorias para escolher. diff --git a/pt/tutorials-and-examples/blog/part-two.rst b/pt/tutorials-and-examples/blog/part-two.rst deleted file mode 100644 index 5f30bc5d4a..0000000000 --- a/pt/tutorials-and-examples/blog/part-two.rst +++ /dev/null @@ -1,671 +0,0 @@ -Tutorial - Criando um Blog - Parte 2 -#################################### - -Criando o model -=============== - -Após criar um model (modelo) no CakePHP, nós teremos a base necessária para -interagirmos com o banco de dados e executar operações. - -Os arquivos de classes, correspondentes aos models, no CakePHP estão divididos -entre os objetos ``Table`` e ``Entity``. Objetos ``Table`` provêm acesso à -coleção de entidades armazenada em uma tabela e são alocados em -**src/Model/Table**. - -O arquivo que criaremos deverá ficar salvo em -**src/Model/Table/ArticlesTable.php**:: - - // src/Model/Table/ArticlesTable.php - - namespace App\Model\Table; - - use Cake\ORM\Table; - - class ArticlesTable extends Table - { - public function initialize(array $config) - { - $this->addBehavior('Timestamp'); - } - } - -Convenções de nomenclatura são muito importantes no CakePHP. Ao nomear nosso -objeto como ``ArticlesTable``, o CakePHP automaticamente deduz que o mesmo -utilize o ``ArticlesController`` e seja relacionado à tabela ``articles``. - -.. note:: - - O CakePHP criará automaticamente um objeto model se não puder encontrar um - arquivo correspondente em **src/Model/Table**. Se você nomear incorretamente - seu arquivo (isto é, artciclestable.php ou ArticleTable.php), o CakePHP não - reconhecerá suas definições e usará o model gerado como alternativa. - -Para mais informações sobre models, como callbacks e validação, visite o -capítulo :doc:`/orm` do manual. - -.. note:: - - Se você completou a - :doc:`primeira parte ` do tutorial e - criou a tabela ``articles``, você pode tomar proveito da capacidade de - geração de código do bake através do console do CakePHP para criar o model - ``ArticlesTable``:: - - bin/cake bake model Articles - -Para mais informações sobre o bake e suas características relacionadas a -geração de código, visite o capítulo :doc:`/bake/usage` do manual. - -Criando o controller -==================== - -A seguir, criaremos um controller (controlador) para nossos artigos. O -controller é responsável pela lógica de interação da aplicação. É o lugar onde -você utilizará as regras contidas nos models e executará tarefas relacionadas -aos artigos. Criaremos um arquivo chamado **ArticlesController.php** no -diretório **src/Controller**:: - - // src/Controller/ArticlesController.php - - namespace App\Controller; - - class ArticlesController extends AppController - { - } - -Agora, vamos adicionar uma action (ação) ao nosso controller. Actions -frequentemente, representam uma função ou interface em uma aplicação. -Por exemplo, quando os usuários requisitarem www.example.com/articles/index -(sendo o mesmo que www.example.com/articles/), eles esperam ver uma lista de -artigos:: - - // src/Controller/ArticlesController.php - - namespace App\Controller; - - class ArticlesController extends AppController - { - - public function index() - { - $articles = $this->Articles->find('all'); - $this->set(compact('articles')); - } - } - -Ao definir a função ``index()`` em nosso ``ArticlesController``, os usuários -podem acessá-la requisitando www.example.com/articles/index. Similarmente, se -definíssemos uma função chamada ``foobar()``, os usuários poderiam acessá-la em -www.example.com/articles/foobar. - -.. warning:: - - Vocês podem ser tentados a nomear seus controllers e actions para obter uma - certa URL. Resista a essa tentação. Siga as :doc:`/intro/conventions` e crie - nomes de action legíveis e compreensíveis. Você pode mapear URLs para o seu - código utilizando :doc:`/development/routing`. - -A instrução na action usa ``set()`` para passar dados do controller para a view. -A variável é definida como 'articles', sendo igual ao valor retornado do método -``find('all')`` do objeto ``ArticlesTable``. - -.. note:: - - Se você completou a - :doc:`primeira parte ` do tutorial e - criou a tabela ``articles``, você pode tomar proveito da capacidade de - geração de código do bake através do console do CakePHP para criar o - controller ``ArticlesController``:: - - bin/cake bake controller Articles - -Para mais informações sobre o bake e suas características sobre geração de -código, visite o capítulo :doc:`/bake/usage` do manual. - -Criando as views -================ - -Agora que nós temos os dados fluindo pelo nosso model, e nossa lógica da -aplicação definida em nosso controller, vamos criar uma view -(visualização) para a action ``index()``. - -As views do CakePHP são camadas de apresentação que se encaixam nos layouts -da aplicação. Para a maioria das aplicações, elas são uma mescla entre HTML e -PHP, mas também podem ser distribuídas como XML, CSV, ou ainda dados binários. - -Um layout é um conjunto de códigos encontrado ao redor das views. Múltiplos -layouts podem ser definidos, e você pode alterar entre eles, mas agora, vamos -usar o default, localizado em **templates/layout/default.php**. - -Lembra que na última sessão atribuímos a variável 'articles' à view usando o -método ``set()``? Isso levará a coleção de objetos gerada pela query a ser -invocada numa iteração ``foreach``. - -Arquivos de template do CakePHP são armazenados em **src/Template** dentro de -uma pasta com o nome do controller correspondente (nós teremos que criar a -pasta 'Articles' nesse caso). Para distribuir os dados de artigos em uma tabela, -precisamos criar uma view assim: - -.. code-block:: php - - - -

Blog articles

- - - - - - - - - - - - - - - - -
IdTitleCreated
id ?> - Html->link($article->title, ['action' => 'view', $article->id]) ?> - - created->format(DATE_RFC850) ?> -
- -Você deve ter notado o uso de um objeto chamado ``$this->Html``, uma -instância da classe :php:class:`Cake\\View\\Helper\\HtmlHelper` do CakePHP. -O CakePHP vem com um conjunto de view helpers que simplificam tarefas como gerar -links e formulários. Você pode aprender como usá-los em :doc:`/views/helpers`, -mas aqui é importante notar que o método ``link()`` irá gerar um link HTML -com o referido título (primeiro parâmetro) e URL (segundo parâmetro). - -Quando se especifíca URLs no CakePHP, é recomendado o uso do formato de array. -Isto será melhor explicado posteriormente na seção Rotas. Usando o formato de -array para URLs, você toma vantagem das capacidades de roteamento -reverso do CakePHP. Você também pode especificar URLs relativas a base da -aplicação com o formato ``/controller/action/param1/param2`` ou usar -:ref:`named routes `. - -Neste ponto, você pode visitar http://www.example.com/articles/index no seu -navegador. Você deve ver sua view corretamente formatada listando os artigos. - -Se você clicar no link do título de um artigo listado, provavelmente será -informado pelo CakePHP que a action ainda não foi definida, então vamos criá-la -no ``ArticlesController`` agora:: - - // 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')); - } - } - -O uso do ``set()`` deve parecer familiar. Repare que você está usando -``get()`` ao invés de ``find('all')`` porquê nós queremos a informação de apenas -um artigo. - -Repare que nossa action recebe um parâmetro: o ID do artigo que gostariamos de -visualizar. Esse parâmetro é entregue para a action através da URL solicitada. -Se o usuário requisitar ``/articles/view/3``, então o valor '3' é passado como -``$id`` para a action. - -Ao usar a função ``get()``, fazemos também algumas verificações para garantir -que o usuário realmente está acessando um registro existente , se não -ou se o ``$id`` for indefinido, a função irá lançar uma ``NotFoundException``. - -Agora vamos criar a view para nossa action em -**templates/Articles/view.php** - -.. code-block:: php - - - -

title) ?>

-

body) ?>

-

Criado: created->format(DATE_RFC850) ?>

- -Verifique se está tudo funcionando acessando os links em ``/articles/index`` ou -manualmente solicite a visualização de um artigo acessando ``articles/view/{id}``. -Lembre-se de substituir ``{id}`` por um 'id' de um artigo. - -Adicionando artigos -=================== - -Primeiro, comece criando a action ``add()`` no ``ArticlesController``:: - - // src/Controller/ArticlesController.php - - namespace App\Controller; - - use App\Controller\AppController; - - class ArticlesController extends AppController - { - - public function initialize() - { - parent::initialize(); - - $this->loadComponent('Flash'); // Inclui o 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(__('Seu artigo foi salvo.')); - - return $this->redirect(['action' => 'index']); - } - $this->Flash->error(__('Não é possível adicionar o seu artigo.')); - } - $this->set('article', $article); - } - } - -.. note:: - - Você precisa incluir o :doc:`/controllers/components/flash` component em qualquer - controller que vá usá-lo. Se necessário, inclua no ``AppController`` e - assim o ``FlashComponent`` estará disponível para todos os controllers da - aplicação. - -A action ``add()`` checa se o método HTTP da solicitação foi POST, e então tenta -salvar os dados utilizando o model Articles. Se por alguma razão ele não salvar, -apenas renderiza a view. Isto nos dá a chance de exibir erros de validação ou -outros alertas. - -Cada requisição do CakePHP instancia um objeto ``ServerRequest`` que é acessível -usando ``$this->request``. O objeto contém informações úteis sobre a requisição -que foi recebida e pode ser usado para controlar o fluxo de sua aplicação. Nesse -caso, nós usamos o método :php:meth:`Cake\\Network\\ServerRequest::is()` para checar -se a requisição é do tipo HTTP POST. - -Quando se usa um formulário para postar dados, essa informação fica disponível -em ``$this->request->getData()``. Você pode usar as funções :php:func:`pr()` ou -:php:func:`debug()` caso queira verificar esses dados. - -Usamos os métodos ``success()`` e ``error()`` do ``FlashComponent`` para definir -uma mensagem que será armazenada numa variável de sessão. Esses métodos são -gerados usando os `recursos de métodos mágicos -`_ do PHP. -Mensagens flash serão exibidas na página após um redirecionamento. No layout nós -temos ``Flash->render() ?>`` que exibe a mensagem e limpa a variável -de sessão. A função do controller -:php:meth:`Cake\\Controller\\Controller::redirect` redireciona para qualquer -outra URL. O parâmetro ``['action' => 'index']`` corresponde a URL /articles, -isto é, a action ``index()`` do ``ArticlesController``. Você pode consultar a -função -:php:func:`Cake\\Routing\\Router::url()` na `API `_ e -checar os formatos a partir dos quais você pode montar uma URL. - -Chamar o método ``save()`` vai checar erros de validação e abortar o processo -caso os encontre. Nós vamos abordar como esses erros são tratados nas sessões -a seguir. - -Validando artigos -================= - -O CakePHP torna mais prática e menos monótona a validação de dados de -formulário. - -Para tirar proveito dos recursos de validação, você vai precisar usar o -:doc:`/views/helpers/form` helper em suas views. O -:php:class:`Cake\\View\\Helper\\FormHelper` está disponível por padrão em todas -as views pelo uso do ``$this->Form``. - -Segue a view correspondente a action add: - -.. code-block:: php - - - -

Add Article

- Form->create($article); - echo $this->Form->input('title'); - echo $this->Form->input('body', ['rows' => '3']); - echo $this->Form->button(__('Salvar artigo')); - echo $this->Form->end(); - ?> - -Nós usamos o ``FormHelper`` para gerar a tag de abertura HTML de um formulário. -Segue o HTML gerado por ``$this->Form->create()``: - -.. code-block:: html - -
- -Se ``create()`` é chamado sem parâmetros fornecidos, assume-se a construção de -um formulário que submete dados via POST para a action ``add()`` (ou ``edit()`` -no caso de um ``id`` estar incluído nos dados do formulário). - -O método ``$this->Form->input()`` é usado para criar elementos do formulário do -mesmo nome. O primeiro parâmetro diz ao CakePHP qual é o campo correspondente, e -o segundo parâmetro permite que você especifique um vasto array de opções, -nesse, o número de linhas para o textarea. ``input()`` vai gerar diferentes -elementos de formulários baseados no tipo de campo especificado no model. - -O ``$this->Form->end()`` fecha o formulário, entregando também elementos ocultos -caso a prevenção contra CSRF/Form Tampering esteja habilitada. - -Agora vamos voltar e atualizar nossa view **templates/Articles/index.php** -para incluir um novo link. Antes do ````, adicione a seguinte linha: - -.. code-block:: php - - Html->link('Adicionar artigo', ['action' => 'add']) ?> - -Você deve estar se perguntando: como eu digo ao CakePHP meus critérios de -validação? Regras de validação são definidas no model. Vamos fazer alguns -ajustes no nosso model:: - - // 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) - { - $this->addBehavior('Timestamp'); - } - - public function validationDefault(Validator $validator) - { - $validator - ->notEmpty('title') - ->notEmpty('body'); - - return $validator; - } - } - -O método ``validationDefault()`` diz ao CakePHP como validar seus dados quando -o método ``save()`` for solicitado. Aqui, estamos especificando que tanto o -campo body quanto title não devem estar vazios. O CakePHP possui muitos recursos -de validação e um bom número de regras pré-determinadas (número de cartões, -endereços de email, etc), além de flexibilidade para adicionar regras de -validação customizadas. Para mais informações sobre configuração de validações, -visite a documentação em :doc:`/core-libraries/validation`. - -Agora que suas regras de validação estão definidas, tente adicionar um artigo -sem definir o campo title e body para ver como a validação funciona. Desde que -tenhamos usado o método :php:meth:`Cake\\View\\Helper\\FormHelper::input()` do -``FormHelper`` para criar nossos elementos, nossas mensagens de alerta da -validação serão exibidas automaticamente. - -Editando artigos -================ - -Edição, aí vamos nós! Você já é um profissional do CakePHP agora, então -possivelmente detectou um padrão... Cria-se a action e então a view. Aqui segue -a action ``edit()`` que deverá ser inserida no ``ArticlesController``:: - - // 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(__('Seu artigo foi atualizado.')); - - return $this->redirect(['action' => 'index']); - } - $this->Flash->error(__('Seu artigo não pôde ser atualizado.')); - } - - $this->set('article', $article); - } - -Essa action primeiramente certifica-se que o registro apontado existe. Se o -parâmetro ``$id`` não foi passado ou se o registro é inexistente, uma -``NotFoundException`` é lançada pelo ``ErrorHandler`` do CakePHP. - -Em seguida, a action verifica se a requisição é POST ou PUT e caso seja, os -dados são usados para atualizar a entidade de artigo em questão ao usar -o método ``patchEntity()``. Então finalmente usamos o ``ArticlesTable`` para -salvar a entidade. - -Segue a view correspondente a action edit: - -.. code-block:: php - - - -

Edit Article

- Form->create($article); - echo $this->Form->input('title'); - echo $this->Form->input('body', ['rows' => '3']); - echo $this->Form->button(__('Salvar artigo')); - echo $this->Form->end(); - ?> - -Essa view retorna o formulário de edição com os dados populados, juntamente -com qualquer mensagem de erro proveniente de validações. - -O CakePHP irá determinar se o ``save()`` vai inserir ou atualizar um registro -baseado nos dados da entidade. - -Você pode atualizar sua view index com os links para editar artigos: - -.. code-block:: php - - - -

Blog articles

-

Html->link("Adicionar artigo", ['action' => 'add']) ?>

-
- - - - - - - - - - - - - - - - - - -
IdTítuloCriadoAções
id ?> - Html->link($article->title, ['action' => 'view', $article->id]) ?> - - created->format(DATE_RFC850) ?> - - Html->link('Editar', ['action' => 'edit', $article->id]) ?> -
- -Deletando artigos -================= - -A seguir, vamos criar uma forma de deletar artigos. Comece com uma action -``delete()`` no ``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(__('O artigo com id: {0} foi deletado.', h($id))); - - return $this->redirect(['action' => 'index']); - } - } - -Essa lógica deleta o artigo especificado pelo ``$id`` e usa -``$this->Flash->success()`` para exibir uma mensagem de confirmação após -o redirecionamento para ``/articles``. Tentar excluir um registro usando uma -requisição GET, fará com que o ``allowMethod()`` lance uma exceção. Exceções -são capturadas pelo gerenciador de exceções do CakePHP e uma página de erro é -exibida. Existem muitos :doc:`Exceptions ` embutidos que -podem indicar variados erros HTTP que sua aplicação possa precisar. - -Por estarmos executando apenas lógica e redirecionando, essa action não -tem uma view. Vamos atualizar nossa view index com links para excluir artigos: - -.. code-block:: php - - - -

Blog articles

-

Html->link('Adicionar artigo', ['action' => 'add']) ?>

- - - - - - - - - - - - - - - - - - - -
IdTítuloCriadoAções
id ?> - Html->link($article->title, ['action' => 'view', $article->id]) ?> - - created->format(DATE_RFC850) ?> - - Form->postLink( - 'Deletar', - ['action' => 'delete', $article->id], - ['confirm' => 'Tem certeza?']) - ?> - Html->link('Edit', ['action' => 'edit', $article->id]) ?> -
- -Usar :php:meth:`~Cake\\View\\Helper\\FormHelper::postLink()` vai criar um link -que usa JavaScript para criar uma requisição POST afim de deletar um artigo. - -.. warning:: - - Permitir que registros sejam deletados usando requisições GET é perigoso, - pois rastreadores na web podem acidentalmente deletar todo o seu conteúdo. - -.. note:: - - Esse código da view também usa o ``FormHelper`` para confirmar a action - através de JavaScript. - -Rotas -===== - -Para muitos o roteamento padrão do CakePHP funciona bem o suficiente. -Desenvolvedores que consideram facilidade de uso e SEO irão apreciar a forma -como o CakePHP mapeia determinadas URLs para actions específicas. Vamos realizar -uma pequena mudança nas rotas neste tutorial. - -Para mais informações sobre técnicas avançadas de roteamento, visite -:ref:`routes-configuration`. - -Por padrão, o CakePHP responde a uma requisição pela raíz do seu site usando o -``PagesController``, ao renderizar uma view chamada **home.php**. -Alternativamente, nós vamos substituir esse comportamento pelo -``ArticlesController`` ao criar uma regra de roteamento. - -A configuração de rotas do CakePHP pode ser encontrada em **config/routes.php**. -Você deve comentar ou remover a linha que define o roteamento padrão: - -.. code-block:: php - - $routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']); - -Essa linha conecta a URL '/' com a página padrão do CakePHP. Nós queremos que -ela conecte-se ao nosso próprio controller, então a substitua por esta: - -.. code-block:: php - - $routes->connect('/', ['controller' => 'Articles', 'action' => 'index']); - -Isso irá conectar requisições por '/' a action ``index()`` do nosso -``ArticlesController`` - -.. note:: - - O CakePHP aproveita-se do uso de roteamento reverso. Se com a rota anterior - definida você gerar um link com a seguinte estrutura de array: - ``['controller' => 'Articles', 'action' => 'index']``, a URL resultante - será '/'. Portanto, é uma boa ideia sempre usar arrays para URLs, pois assim - suas rotas definem o endereço gerado e certificam-se que os links apontem - sempre para o mesmo lugar. - -Conclusão -========= - -Simples, não é? Tenha em mente que esse foi um tutorial básico. O CakePHP tem -*muito* mais recursos a oferecer. Não abordamos outros tópicos aqui para manter -a simplicidade. Use o restante do manual como um guia para criar aplicações -mais ricas. - -Agora que você criou uma aplicação básica no CakePHP, você pode continuar no -:doc:`/tutorials-and-examples/blog/part-three`, ou começar seu próprio projeto. -Você também pode folhear os :doc:`/topics` ou a -`API ` para aprender mais sobre o CakePHP. - -Se você precisar de ajuda, há muitas formas de conseguir, por favor, visite a -página :doc:`/intro/where-to-get-help` e bem-vindo(a) ao CakePHP! - -Leitura complementar --------------------- - -Existem tópicos comuns que as pessoas que estão estudando o CakePHP normalmente -visitam a seguir: - -1. :ref:`view-layouts`: Customizando o layout da aplicação -2. :ref:`view-elements`: Inclusão e reutilização de elementos na view -3. :doc:`/bake/usage`: Gerando código CRUD -4. :doc:`/tutorials-and-examples/blog-auth-example/auth`: Tutorial de - autorização e autenticação - -.. meta:: - :title lang=pt: Tutorial - Criando um Blog - Parte 2 - :keywords lang=pt: actions,view,add,edit,delete,validation,model,post,request,validate,error diff --git a/pt/tutorials-and-examples/bookmarks/intro.rst b/pt/tutorials-and-examples/bookmarks/intro.rst deleted file mode 100644 index 86fa41d627..0000000000 --- a/pt/tutorials-and-examples/bookmarks/intro.rst +++ /dev/null @@ -1,367 +0,0 @@ -Tutorial - Criando um Bookmarker - Parte 1 -########################################## - -Esse tutorial vai guiar você através da criação de uma simples aplicação de -marcação (bookmarker). Para começar, nós vamos instalar o CakePHP, criar -nosso banco de dados, e usar as ferramentas que o CakePHP fornece para subir -nossa aplicação de forma rápida. - -Aqui está o que você vai precisar: - -#. Um servidor de banco de dados. Nós vamos usar o servidor MySQL neste - tutorial. Você precisa saber o suficiente sobre SQL para criar um banco de - dados: O CakePHP vai tomar as rédeas a partir daí. Por nós estarmos - usando o MySQL, também certifique-se que você tem a extensão ``pdo_mysql`` - habilitada no PHP. - -#. Conhecimento básico sobre PHP. - -Vamos começar! - -Instalação do CakePHP -===================== - -A maneira mais fácil de instalar o CakePHP é usando Composer, um gerenciador -de dependências para o PHP. É uma forma simples de instalar o CakePHP a -partir de seu terminal ou prompt de comando. Primeiro, você precisa baixar e -instalar o Composer. Se você tiver instalada a extensão cURL do PHP, execute -o seguinte comando:: - - curl -s https://getcomposer.org/installer | php - -Ao invés disso, você também pode baixar o arquivo ``composer.phar`` do -`site `_ oficial. - -Em seguida, basta digitar a seguinte linha no seu terminal a partir do diretório -onde se localiza o arquivo ``composer.phar`` para instalar o esqueleto de -aplicações do CakePHP no diretório ``bookmarker``. :: - - php composer.phar create-project --prefer-dist cakephp/app:4.* bookmarker - -A vantagem de usar Composer é que ele irá completar automaticamente um conjunto -importante de tarefas, como configurar as permissões de arquivo e criar a sua -**config/app.php**. - -Há outras maneiras de instalar o CakePHP. Se você não puder ou não quiser usar -Composer, veja a seção :doc:`/installation`. - -Independentemente de como você baixou o CakePHP, uma vez que sua instalação -for concluída, a estrutura dos diretórios deve ficar parecida com o seguinte:: - - /bookmarker - /bin - /config - /logs - /plugins - /src - /tests - /tmp - /vendor - /webroot - .editorconfig - .gitignore - .htaccess - .travis.yml - composer.json - index.php - phpunit.xml.dist - README.md - -Agora pode ser um bom momento para aprender sobre como a estrutura de diretórios -do CakePHP funciona: Confira a seção :doc:`/intro/cakephp-folder-structure`. - -Verificando nossa instalação -============================ - -Podemos checar rapidamente que a nossa instalação está correta, verificando a -página inicial padrão. Antes que você possa fazer isso, você vai precisar -iniciar o servidor de desenvolvimento:: - - bin/cake server - -Isto irá iniciar o servidor embutido do PHP na porta 8765. Abra -``http://localhost:8765`` em seu navegador para ver a página de boas-vindas. -Todas as verificações devem estar checadas corretamente, a não ser a conexão com -banco de dados do CakePHP. Se não, você pode precisar instalar extensões do PHP -adicionais, ou definir permissões de diretório. - -Criando o banco de dados -======================== - -Em seguida, vamos criar o banco de dados para a nossa aplicação. Se você -ainda não tiver feito isso, crie um banco de dados vazio para uso -nesse tutorial, com um nome de sua escolha, por exemplo, ``cake_bookmarks``. -Você pode executar o seguinte SQL para criar as tabelas necessárias: - -.. 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(255), - 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), - INDEX tag_idx (tag_id, bookmark_id), - FOREIGN KEY tag_key(tag_id) REFERENCES tags(id), - FOREIGN KEY bookmark_key(bookmark_id) REFERENCES bookmarks(id) - ); - -Você deve ter notado que a tabela ``bookmarks_tags`` utilizada uma chave -primária composta. O CakePHP suporta chaves primárias compostas em quase todos os -lugares, tornando mais fácil construir aplicações multi-arrendados. - -Os nomes de tabelas e colunas que usamos não foram arbitrárias. Usando -:doc:`convenções de nomenclatura ` do CakePHP, podemos -alavancar o desenvolvimento e evitar ter de configurar o framework. O CakePHP -é flexível o suficiente para acomodar até mesmo esquemas de banco de dados -legados inconsistentes, mas aderir às convenções vai lhe poupar tempo. - -Configurando o banco de dados -============================= - -Em seguida, vamos dizer ao CakePHP onde o nosso banco de dados está e como se -conectar a ele. Para muitos, esta será a primeira e última vez que você vai -precisar configurar qualquer coisa. - -A configuração é bem simples: basta alterar os valores do array -``Datasources.default`` no arquivo **config/app.php** pelos que se -aplicam à sua configuração. A amostra completa da gama de configurações pode -ser algo como o seguinte:: - - return [ - // Mais configuração acima. - '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, - ], - ], - // Mais configuração abaixo. - ]; - -Depois de salvar o seu arquivo **config/app.php**, você deve notar que a -mensagem 'CakePHP is able to connect to the database' tem uma marca de -verificação. - -.. note:: - - Uma cópia do arquivo de configuração padrão do CakePHP é encontrado em - **config/app.default.php**. - -Gerando o código base -===================== - -Devido a nosso banco de dados seguir as convenções do CakePHP, podemos usar o -:doc:`bake console ` para gerar rapidamente uma aplicação básica -. Em sua linha de comando execute:: - - bin/cake bake all users - bin/cake bake all bookmarks - bin/cake bake all tags - -Isso irá gerar os controllers, models, views, seus casos de teste -correspondentes, e fixtures para os nossos users, bookmarks e tags. Se você -parou seu servidor, reinicie-o e vá para ``http://localhost:8765/bookmarks``. - -Você deverá ver uma aplicação que dá acesso básico, mas funcional a tabelas -de banco de dados. Adicione alguns users, bookmarks e tags. - -Adicionando criptografia de senha -================================= - -Quando você criou seus users, você deve ter notado que as senhas foram -armazenadas como texto simples. Isso é muito ruim do ponto de vista da -segurança, por isso vamos consertar isso. - -Este também é um bom momento para falar sobre a camada de modelo. No CakePHP, -separamos os métodos que operam em uma coleção de objetos, e um único objeto -em diferentes classes. Métodos que operam na recolha de entidades são -colocadas na classe *Table*, enquanto as características pertencentes a um -único registro são colocados na classe *Entity*. - -Por exemplo, a criptografia de senha é feita no registro individual, por -isso vamos implementar esse comportamento no objeto entidade. Dada a -circunstância de nós querermos criptografar a senha cada vez que é -definida, vamos usar um método modificador/definidor. O CakePHP vai chamar -métodos de definição baseados em convenções a qualquer momento que uma -propriedade é definida em uma de suas entidades. Vamos adicionar um definidor -para a senha. Em **src/Model/Entity/User.php** adicione o seguinte:: - - namespace App\Model\Entity; - - use Cake\ORM\Entity; - use Cake\Auth\DefaultPasswordHasher; - - class User extends Entity - { - - // Code from bake. - - protected function _setPassword($value) - { - $hasher = new DefaultPasswordHasher(); - - return $hasher->hash($value); - } - } - -Agora atualize um dos usuários que você criou anteriormente, se você alterar -sua senha, você deve ver um senha criptografada ao invés do valor original nas -páginas de lista ou visualização. O CakePHP criptografa senhas com -`bcrypt `_ por padrão. -Você também pode usar sha1 ou md5 caso venha a trabalhar com um -banco de dados existente. - -Recuperando bookmarks com uma tag específica -============================================ - -Agora que estamos armazenando senhas com segurança, podemos construir algumas -características mais interessantes em nossa aplicação. Uma vez que você -acumulou uma coleção de bookmarks, é útil ser capaz de pesquisar através -deles por tag. Em seguida, vamos implementar uma rota, a ação do controller, e -um método localizador para pesquisar através de bookmarks por tag. - -Idealmente, nós teríamos uma URL que se parece com -``http://localhost:8765/bookmarks/tagged/funny/cat/gifs``. Isso deveria nos -permitir a encontrar todos os bookmarks que têm as tags 'funny', 'cat' e -'gifs'. Antes de podermos implementar isso, vamos adicionar uma nova rota. Em -**config/routes.php**, adicione o seguinte na parte superior do arquivo:: - - Router::scope( - '/bookmarks', - ['controller' => 'Bookmarks'], - function ($routes) { - $routes->connect('/tagged/*', ['action' => 'tags']); - } - ); - -O trecho acima define uma nova "rota" que liga o caminho ``/bookmarks/tagged/*``, a -``BookmarksController::tags()``. Ao definir rotas, você pode isolar como -suas URLs parecerão, de como eles são implementadas. Se fôssemos visitar -``http://localhost:8765/bookmarks/tagged``, deveriamos ver uma página de erro -informativa do CakePHP. Vamos implementar esse método ausente agora. Em -**src/Controller/BookmarksController.php** adicione o seguinte trecho:: - - public function tags() - { - $tags = $this->request->getParam('pass'); - $bookmarks = $this->Bookmarks->find('tagged', [ - 'tags' => $tags - ]); - $this->set(compact('bookmarks', 'tags')); - } - -Criando o método localizador -============================ - -No CakePHP nós gostamos de manter as nossas ações do controller enxutas, e -colocar a maior parte da lógica de nossa aplicação nos modelos. Se você fosse -visitar a URL ``/bookmarks/tagged`` agora, você veria um erro sobre o -método ``findTagged`` não estar implementado ainda, então vamos fazer isso. Em -**src/Model/Table/BookmarksTable.php** adicione o seguinte:: - - 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']); - } - -Nós implementamos um método -:ref:`localizador customizado `. Este é um conceito -muito poderoso no CakePHP que lhe permite construir consultas reutilizáveis. -Em nossa pesquisa, nós alavancamos o método ``matching()`` que nos habilita -encontrar bookmarks que têm uma tag 'correspondente'. - -Criando a view -============== - -Agora, se você visitar a URL ``/bookmarks/tagged``, o CakePHP irá mostrar um -erro e deixá-lo saber que você ainda não fez um arquivo view. Em seguida, -vamos construir o arquivo view para a nossa ação ``tags``. Em -**templates/Bookmarks/tags.php** coloque o seguinte conteúdo:: - -

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

- -
- -
-

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

- url) ?> - Text->autoParagraph(h($bookmark->description)) ?> -
- -
- -O CakePHP espera que os nossos templates sigam a convenção de nomenclatura onde -o nome do template é a versão minúscula e grifada do nome da ação do -controller. - -Você pode perceber que fomos capazes de utilizar as variáveis ``$tags`` e -``bookmarks`` em nossa view. Quando usamos o método ``set()`` em nosso -controller, automaticamente definimos variáveis específicas que devem ser -enviadas para a view. A view vai tornar todas as variáveis passadas -disponíveis nos templates como variáveis locais. - -Em nossa view, usamos alguns dos :doc:`helpers ` nativos do -CakePHP. Helpers são usados para criar lógica re-utilizável para a -formatação de dados, a criação de HTML ou outra saída da view. - -Agora você deve ser capaz de visitar a URL ``/bookmarks/tagged/funny`` e ver -todas os bookmarks com a tag 'funny'. - -Até agora, nós criamos uma aplicação básica para gerenciar bookmarks, tags e -users. No entanto, todos podem ver as tags de todos os usuários. No próximo -capítulo, vamos implementar a autenticação e restringir os bookmarks visíveis -para somente aqueles que pertencem ao usuário atual. - -Agora vá a :doc:`/tutorials-and-examples/bookmarks/part-two` para continuar a -construir sua aplicação ou :doc:`mergulhe na documentação ` para -saber mais sobre o que CakePHP pode fazer por você. diff --git a/pt/tutorials-and-examples/bookmarks/part-two.rst b/pt/tutorials-and-examples/bookmarks/part-two.rst deleted file mode 100644 index 1db885160b..0000000000 --- a/pt/tutorials-and-examples/bookmarks/part-two.rst +++ /dev/null @@ -1,411 +0,0 @@ -Tutorial - Criando um Bookmarker - Parte 2 -########################################## - -Depois de terminar a :doc:`primeira parte deste tutorial -`, você deve ter uma -aplicação muito básica. Neste capítulo iremos adicionar autenticação e -restringir as bookmarks para que cada usuário possa ver/modificar somente -aquelas tags que possuam. - -Adicionando login -================= - -No CakePHP, a autenticação é feita por :doc:`/controllers/components`. Os -Components podem ser considerados como formas de criar pedaços reutilizáveis -de código relacionado a controllers com uma característica específica ou -conceito. Os components também podem se ligar ao evento do ciclo de vida do -controller e interagir com a sua aplicação. Para começar, vamos -adicionar o AuthComponent a nossa aplicação. É essencial que -cada método exija autenticação, por isso vamos acrescentar o -:doc:`AuthComponent` em nosso -AppController:: - - // Em src/Controller/AppController.php - namespace App\Controller; - - use Cake\Controller\Controller; - - class AppController extends Controller - { - public function initialize() - { - $this->loadComponent('Flash'); - $this->loadComponent('Auth', [ - 'authenticate' => [ - 'Form' => [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password' - ] - ] - ], - 'loginAction' => [ - 'controller' => 'Users', - 'action' => 'login' - ] - ]); - - // Permite a ação display, assim nosso pages controller - // continua a funcionar. - $this->Auth->allow(['display']); - } - } - -Acabamos de dizer ao CakePHP que queremos carregar os components ``Flash`` e -``Auth``. Além disso, temos a configuração personalizada do AuthComponent, -assim a nossa tabela users pode usar ``email`` como username. Agora, se você for -a qualquer URL, você vai ser chutado para ``/users/login``, que irá -mostrar uma página de erro já que não escrevemos o código ainda. -Então, vamos criar a ação de login:: - - // Em 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('Your username or password is incorrect.'); - } - } - -E em **templates/Users/login.php** adicione o seguinte trecho:: - -

Login

- Form->create() ?> - Form->input('email') ?> - Form->input('password') ?> - Form->button('Login') ?> - Form->end() ?> - -Agora que temos um formulário de login simples, devemos ser capazes de efetuar -login com um dos users que tenham senha criptografada. - -.. note:: - - Se nenhum de seus users tem senha criptografada, comente a linha - ``loadComponent('Auth')``. Então vá e edite o user, salvando uma nova - senha para ele. - -Agora você deve ser capaz de entrar. Se não, certifique-se que você está -usando um user que tenha senha criptografada. - -Adicionando logout -================== - -Agora que as pessoas podem efetuar o login, você provavelmente vai querer -fornecer uma maneira de encerrar a sessão também. Mais uma vez, no -``UsersController``, adicione o seguinte código:: - - public function logout() - { - $this->Flash->success('You are now logged out.'); - - return $this->redirect($this->Auth->logout()); - } - -Agora você pode visitar ``/users/logout`` para sair e ser enviado à -página de login. - -Ativando inscrições -=================== - -Se você não estiver logado e tentar visitar / usuários / adicionar você vai -ser expulso para a página de login. Devemos corrigir isso se -quisermos que as pessoas se inscrevam em nossa aplicação. No -UsersController adicione o seguinte trecho:: - - public function beforeFilter(\Cake\Event\Event $event) - { - $this->Auth->allow(['add']); - } - -O texto acima diz ao ``AuthComponent`` que a ação ``add`` não requer -autenticação ou autorização. Você pode querer dedicar algum tempo para limpar a -``/users/add`` e remover os links enganosos, ou continuar para a próxima -seção. Nós não estaremos construindo a edição do usuário, visualização ou -listagem neste tutorial, então eles não vão funcionar, já que o -``AuthComponent`` vai negar-lhe acesso a essas ações do controller. - -Restringindo acesso -=================== - -Agora que os usuários podem se conectar, nós vamos querer limitar os -bookmarks que podem ver para aqueles que fizeram. Nós vamos fazer isso -usando um adaptador de 'autorização'. Sendo os nossos requisitos -bastante simples, podemos escrever um código em nossa -``BookmarksController``. Mas antes de fazer isso, vamos querer dizer ao -AuthComponent como nossa aplicação vai autorizar ações. Em seu ``AppController`` -adicione o seguinte:: - - public function isAuthorized($user) - { - return false; - } - -Além disso, adicione o seguinte à configuração para ``Auth`` em seu -``AppController``:: - - 'authorize' => 'Controller', - -Seu método ``initialize`` agora deve parecer com:: - - public function initialize() - { - $this->loadComponent('Flash'); - $this->loadComponent('Auth', [ - 'authorize'=> 'Controller',//adicionado essa linha - 'authenticate' => [ - 'Form' => [ - 'fields' => [ - 'username' => 'email', - 'password' => 'password' - ] - ] - ], - 'loginAction' => [ - 'controller' => 'Users', - 'action' => 'login' - ], - 'unauthorizedRedirect' => $this->referer() - ]); - - // Permite a ação display, assim nosso pages controller - // continua a funcionar. - $this->Auth->allow(['display']); - } - -Vamos usar como padrão, negação do acesso, e de forma incremental conceder -acesso onde faça sentido. Primeiro, vamos adicionar a lógica de autorização -para os bookmarks. Em seu ``BookmarksController`` adicione o seguinte:: - - public function isAuthorized($user) - { - $action = $this->request->params['action']; - - // As ações add e index são permitidas sempre. - if (in_array($action, ['index', 'add', 'tags'])) { - return true; - } - // Todas as outras ações requerem um id. - if (!$this->request->getParam('pass.0')) { - return false; - } - - // Checa se o bookmark pertence ao user atual. - $id = $this->request->getParam('pass.0'); - $bookmark = $this->Bookmarks->get($id); - if ($bookmark->user_id == $user['id']) { - return true; - } - - return parent::isAuthorized($user); - } - -Agora, se você tentar visualizar, editar ou excluir um bookmark que não -pertença a você, você deve ser redirecionado para a página de onde veio. No -entanto, não há nenhuma mensagem de erro sendo exibida, então vamos -corrigir isso a seguir:: - - // In templates/layout/default.php - // Under the existing flash message. - Flash->render('auth') ?> - -Agora você deve ver as mensagens de erro de autorização. - -Corrigindo a view de listagem e formulários -=========================================== - -Enquanto view e delete estão trabalhando, edit, add e index tem -alguns problemas: - -#. Ao adicionar um bookmark, você pode escolher o user. -#. Ao editar um bookmark, você pode escolher o user. -#. A página de listagem mostra os bookmarks de outros users. - -Primeiramente, vamos refatorar o formulário de adição. Para começar -remova o ``input('user_id')`` a partir de **templates/Bookmarks/add.php**. -Com isso removido, nós também vamos atualizar o método add:: - - 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('The bookmark has been saved.'); - - return $this->redirect(['action' => 'index']); - } - $this->Flash->error('The bookmark could not be saved. Please, try again.'); - } - $tags = $this->Bookmarks->Tags->find('list'); - $this->set(compact('bookmark', 'tags')); - } - -Ao definir a propriedade da entidade com os dados da sessão, nós removemos -qualquer possibilidade do user modificar algo que não pertenca a ele. -Nós vamos fazer o mesmo para o formulário edit e action edit. Sua -ação edit deve ficar assim:: - - 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('The bookmark has been saved.'); - - return $this->redirect(['action' => 'index']); - } - $this->Flash->error('The bookmark could not be saved. Please, try again.'); - } - $tags = $this->Bookmarks->Tags->find('list'); - $this->set(compact('bookmark', 'tags')); - } - -View de listagem ----------------- - -Agora, nós precisamos apenas exibir bookmarks para o user logado. -Nós podemos fazer isso ao atualizar a chamada para ``paginate()``. Altere sua -ação index:: - - public function index() - { - $this->paginate = [ - 'conditions' => [ - 'Bookmarks.user_id' => $this->Auth->user('id'), - ] - ]; - $this->set('bookmarks', $this->paginate($this->Bookmarks)); - } - -Nós também devemos atualizar a action ``tags()`` e o método localizador relacionado, -mas vamos deixar isso como um exercício para que você conclua por sí. - -Melhorando a experiência com as tags -==================================== - -Agora, adicionar novas tags é um processo difícil, pois o ``TagsController`` -proíbe todos os acessos. Em vez de permitir o acesso, podemos melhorar a -interface do usuário para selecionar tags usando um campo de texto separado por -vírgulas. Isso permitirá dar uma melhor experiência para os nossos -usuários, e usar mais alguns grandes recursos no ORM. - -Adicionando um campo computado ------------------------------- - -Porque nós queremos uma maneira simples de acessar as tags formatados -para uma entidade, podemos adicionar um campo virtual/computado para a -entidade. Em **src/Model/Entity/Bookmark.php** adicione o seguinte:: - - 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, ', '); - } - -Isso vai nos deixar acessar a propriedade computada ``$bookmark->tag_string``. -Vamos usar essa propriedade em inputs mais tarde. Lembre-se de adicionar a -propriedade ``tag_string`` a lista ``_accessible`` em sua entidade. - -Em **src/Model/Entity/Bookmark.php** adicione o ``tag_string`` ao -``_accessible`` desta forma:: - - protected array $_accessible = [ - 'user_id' => true, - 'title' => true, - 'description' => true, - 'url' => true, - 'user' => true, - 'tags' => true, - 'tag_string' => true, - ]; - -Atualizando as views --------------------- - -Com a entidade atualizado, podemos adicionar uma nova entrada para as nossas -tags. Nas views add e edit, substitua ``tags._ids`` pelo seguinte:: - - Form->input('tag_string', ['type' => 'text']) ?> - -Persistindo a string tag ------------------------- - -Agora que podemos ver as tags como uma string existente, vamos querer salvar -os dados também. Por marcar o ``tag_string`` como acessível, o ORM irá -copiar os dados do pedido em nossa entidade. Podemos usar um método -``beforeSave`` para analisar a cadeia tag e encontrar/construir as -entidades relacionadas. Adicione o seguinte em -**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) - { - $new = array_unique(array_map('trim', explode(',', $tagString))); - $out = []; - $query = $this->Tags->find() - ->where(['Tags.title IN' => $new]); - - // Remove tags existentes da lista de novas tags. - foreach ($query->extract('title') as $existing) { - $index = array_search($existing, $new); - if ($index !== false) { - unset($new[$index]); - } - } - // Adiciona tags existentes. - foreach ($query as $tag) { - $out[] = $tag; - } - // Adiciona novas tags. - foreach ($new as $tag) { - $out[] = $this->Tags->newEntity(['title' => $tag]); - } - - return $out; - } - -Embora esse código seja um pouco mais complicado do que o que temos feito até -agora, ele ajuda a mostrar o quão poderosa a ORM do CakePHP é. Você pode -facilmente manipular resultados da consulta usando os métodos de -:doc:`/core-libraries/collections`, e lidar com situações em que você está -criando entidades sob demanda com facilidade. - -Terminando -========== - -Nós expandimos nossa aplicação bookmarker para lidar com situações de -autenticação e controle de autorização/acesso básico. Nós também adicionamos -algumas melhorias agradáveis à UX, aproveitando os recursos FormHelper e ORM. - -Obrigado por dispor do seu tempo para explorar o CakePHP. Em seguida, você pode -saber mais sobre o :doc:`/orm`, ou você pode ler os :doc:`/topics`. diff --git a/pt/tutorials-and-examples/cms/articles-controller.rst b/pt/tutorials-and-examples/cms/articles-controller.rst index 0a13500782..872668024a 100644 --- a/pt/tutorials-and-examples/cms/articles-controller.rst +++ b/pt/tutorials-and-examples/cms/articles-controller.rst @@ -1,4 +1,565 @@ CMS Tutorial - Creating the Articles Controller ############################################### -TODO +With our model created, we need a controller for our articles. Controllers in +CakePHP handle HTTP requests and execute business logic contained in model +methods, to prepare the response. We'll place this new controller in a file +called **ArticlesController.php** inside the **src/Controller** directory. +Here's what the basic controller should look like:: + + paginate($this->Articles); + $this->set(compact('articles')); + } + } + +By defining function ``index()`` in our ``ArticlesController``, users can now +access the logic there by requesting **www.example.com/articles/index**. +Similarly, if we were to define a function called ``foobar()``, users would be +able to access that at **www.example.com/articles/foobar**. You may be tempted +to name your controllers and actions in a way that allows you to obtain specific +URLs. Resist that temptation. Instead, follow the :doc:`/intro/conventions` +creating readable, meaningful action names. You can then use +:doc:`/development/routing` to connect the URLs you want to the actions you've +created. + +Our controller action is very simple. It fetches a paginated set of articles +from the database, using the Articles Model that is automatically loaded via naming +conventions. It then uses ``set()`` to pass the articles into the Template (which +we'll create soon). CakePHP will automatically render the template after our +controller action completes. + +Create the Article List Template +================================ + +Now that we have our controller pulling data from the model, and preparing our +view context, let's create a view template for our index action. + +CakePHP view templates are presentation-flavored PHP code that is inserted inside +the application's layout. While we'll be creating HTML here, Views can also +generate JSON, CSV or even binary files like PDFs. + +A layout is presentation code that is wrapped around a view. Layout files +contain common site elements like headers, footers and navigation elements. Your +application can have multiple layouts, and you can switch between them, but for +now, let's just use the default layout. + +CakePHP's template files are stored in **templates** inside a folder +named after the controller they correspond to. So we'll have to create +a folder named 'Articles' in this case. Add the following code to your +application: + +.. code-block:: php + + + +

Articles

+ + + + + + + + + + + + + + +
TitleCreated
+ Html->link($article->title, ['action' => 'view', $article->slug]) ?> + + created->format(DATE_RFC850) ?> +
+ +In the last section we assigned the 'articles' variable to the view using +``set()``. Variables passed into the view are available in the view templates as +local variables which we used in the above code. + +You might have noticed the use of an object called ``$this->Html``. This is an +instance of the CakePHP :doc:`HtmlHelper `. CakePHP comes +with a set of view helpers that make tasks like creating links, forms, and +pagination buttons. You can learn more about :doc:`/views/helpers` in their +chapter, but what's important to note here is that the ``link()`` method will +generate an HTML link with the given link text (the first parameter) and URL +(the second parameter). + +When specifying URLs in CakePHP, it is recommended that you use arrays or +:ref:`named routes `. These syntaxes allow you to +leverage the reverse routing features CakePHP offers. + +At this point, you should be able to point your browser to +**http://localhost:8765/articles/index**. You should see your list view, +correctly formatted with the title and table listing of the articles. + +Create the View Action +====================== + +If you were to click one of the 'view' links in our Articles list page, you'd +see an error page saying that action hasn't been implemented. Lets fix that now:: + + // Add to existing src/Controller/ArticlesController.php file + + public function view($slug = null) + { + $article = $this->Articles->findBySlug($slug)->firstOrFail(); + $this->set(compact('article')); + } + +While this is a simple action, we've used some powerful CakePHP features. We +start our action off by using ``findBySlug()`` which is +a :ref:`Dynamic Finder `. This method allows us to create a basic query that +finds articles by a given slug. We then use ``firstOrFail()`` to either fetch +the first record, or throw a ``\Cake\Datasource\Exception\RecordNotFoundException``. + +Our action takes a ``$slug`` parameter, but where does that parameter come from? +If a user requests ``/articles/view/first-post``, then the value 'first-post' is +passed as ``$slug`` by CakePHP's routing and dispatching layers. If we +reload our browser with our new action saved, we'd see another CakePHP error +page telling us we're missing a view template; let's fix that. + +Create the View Template +======================== + +Let's create the view for our new 'view' action and place it in +**templates/Articles/view.php** + +.. code-block:: php + + + +

title) ?>

+

body) ?>

+

Created: created->format(DATE_RFC850) ?>

+

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

+ +You can verify that this is working by trying the links at ``/articles/index`` or +manually requesting an article by accessing URLs like +``/articles/view/first-post``. + +Adding Articles +=============== + +With the basic read views created, we need to make it possible for new articles +to be created. Start by creating an ``add()`` action in the +``ArticlesController``. Our controller should now look like:: + + paginate($this->Articles); + $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()); + + // Hardcoding the user_id is temporary, and will be removed later + // when we build authentication out. + $article->user_id = 1; + + if ($this->Articles->save($article)) { + $this->Flash->success(__('Your article has been saved.')); + + return $this->redirect(['action' => 'index']); + } + $this->Flash->error(__('Unable to add your article.')); + } + $this->set('article', $article); + } + } + +.. note:: + + You need to include the :doc:`/controllers/components/flash` component in + any controller where you will use it. Often it makes sense to include it in + your ``AppController``, which is there already for this tutorial. + +Here's what the ``add()`` action does: + +* If the HTTP method of the request was POST, try to save the data using the Articles model. +* If for some reason it doesn't save, just render the view. This gives us a + chance to show the user validation errors or other warnings. + +Every CakePHP request includes a request object which is accessible using +``$this->request``. The request object contains information regarding the +request that was just received. We use the +:php:meth:`Cake\\Http\\ServerRequest::is()` method to check that the request +is a HTTP POST request. + +Our POST data is available in ``$this->request->getData()``. You can use the +:php:func:`pr()` or :php:func:`debug()` functions to print it out if you want to +see what it looks like. To save our data, we first 'marshal' the POST data into +an Article Entity. The Entity is then persisted using the ArticlesTable we +created earlier. + +After saving our new article we use FlashComponent's ``success()`` method to set +a message into the session. The ``success`` method is provided using PHP's +`magic method features +`_. Flash +messages will be displayed on the next page after redirecting. In our layout we have +``Flash->render() ?>`` which displays flash messages and clears the +corresponding session variable. Finally, after saving is complete, we use +:php:meth:`Cake\\Controller\\Controller::redirect` to send the user back to the +articles list. The param ``['action' => 'index']`` translates to URL +``/articles`` i.e the index action of the ``ArticlesController``. You can refer +to :php:func:`Cake\\Routing\\Router::url()` function on the `API +`_ to see the formats in which you can specify a URL +for various CakePHP functions. + +Create Add Template +=================== + +Here's our add view template: + +.. code-block:: php + + + +

Add 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(__('Save Article')); + echo $this->Form->end(); + ?> + +We use the FormHelper to generate the opening tag for an HTML +form. Here's the HTML that ``$this->Form->create()`` generates: + +.. code-block:: html + + + +Because we called ``create()`` without a URL option, ``FormHelper`` assumes we +want the form to submit back to the current action. + +The ``$this->Form->control()`` method is used to create form elements +of the same name. The first parameter tells CakePHP which field +they correspond to, and the second parameter allows you to specify +a wide array of options - in this case, the number of rows for the +textarea. There's a bit of introspection and conventions used here. The +``control()`` will output different form elements based on the model +field specified, and use inflection to generate the label text. You can +customize the label, the input or any other aspect of the form controls using +options. The ``$this->Form->end()`` call closes the form. + +Now let's go back and update our **templates/Articles/index.php** +view to include a new "Add Article" link. Before the ````, add +the following line:: + + Html->link('Add Article', ['action' => 'add']) ?> + +Adding Simple Slug Generation +============================= + +If we were to save an Article right now, saving would fail as we are not +creating a slug attribute, and the column is ``NOT NULL``. Slug values are +typically a URL-safe version of an article's title. We can use the +:ref:`beforeSave() callback ` of the ORM to populate our slug:: + + isNew() && !$entity->slug) { + $sluggedTitle = Text::slug($entity->title); + // trim slug to maximum length defined in schema + $entity->slug = substr($sluggedTitle, 0, 191); + } + } + +This code is simple, and doesn't take into account duplicate slugs. But we'll +fix that later on. + +Add Edit Action +=============== + +Our application can now save articles, but we can't edit them. Lets rectify that +now. Add the following action to your ``ArticlesController``:: + + // in src/Controller/ArticlesController.php + + // Add the following method. + + 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(__('Your article has been updated.')); + + return $this->redirect(['action' => 'index']); + } + $this->Flash->error(__('Unable to update your article.')); + } + + $this->set('article', $article); + } + +This action first ensures that the user has tried to access an existing record. +If they haven't passed in an ``$slug`` parameter, or the article does not exist, +a ``RecordNotFoundException`` will be thrown, and the CakePHP ErrorHandler will render +the appropriate error page. + +Next the action checks whether the request is either a POST or a PUT request. If +it is, then we use the POST/PUT data to update our article entity by using the +``patchEntity()`` method. Finally, we call ``save()``, set the appropriate flash +message, and either redirect or display validation errors. + +Create Edit Template +==================== + +The edit template should look like this: + +.. code-block:: php + + + +

Edit 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(__('Save Article')); + echo $this->Form->end(); + ?> + +This template outputs the edit form (with the values populated), along +with any necessary validation error messages. + +You can now update your index view with links to edit specific +articles: + +.. code-block:: php + + + +

Articles

+

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

+
+ + + + + + + + + + + + + + + + +
TitleCreatedAction
+ Html->link($article->title, ['action' => 'view', $article->slug]) ?> + + created->format(DATE_RFC850) ?> + + Html->link('Edit', ['action' => 'edit', $article->slug]) ?> +
+ +Update Validation Rules for Articles +==================================== + +Up until this point our Articles had no input validation done. Lets fix that by +using :ref:`a validator `:: + + // src/Model/Table/ArticlesTable.php + + // add this use statement right below the namespace declaration to import + // the Validator class + use Cake\Validation\Validator; + + // Add the following method. + public function validationDefault(Validator $validator): Validator + { + $validator + ->notEmptyString('title') + ->minLength('title', 10) + ->maxLength('title', 255) + + ->notEmptyString('body') + ->minLength('body', 10); + + return $validator; + } + +The ``validationDefault()`` method tells CakePHP how to validate your data when +the ``save()`` method is called. Here, we've specified that both the title, and +body fields must not be empty, and have certain length constraints. + +CakePHP's validation engine is powerful and flexible. It provides a suite of +frequently used rules for tasks like email addresses, IP addresses etc. and the +flexibility for adding your own validation rules. For more information on that +setup, check the :doc:`/core-libraries/validation` documentation. + +Now that your validation rules are in place, use the app to try to add +an article with an empty title or body to see how it works. Since we've used the +:php:meth:`Cake\\View\\Helper\\FormHelper::control()` method of the FormHelper to +create our form elements, our validation error messages will be shown +automatically. + +Add Delete Action +================= + +Next, let's make a way for users to delete articles. Start with a +``delete()`` action in the ``ArticlesController``:: + + // src/Controller/ArticlesController.php + + // Add the following method. + + public function delete($slug) + { + $this->request->allowMethod(['post', 'delete']); + + $article = $this->Articles->findBySlug($slug)->firstOrFail(); + if ($this->Articles->delete($article)) { + $this->Flash->success(__('The {0} article has been deleted.', $article->title)); + + return $this->redirect(['action' => 'index']); + } + } + +This logic deletes the article specified by ``$slug``, and uses +``$this->Flash->success()`` to show the user a confirmation +message after redirecting them to ``/articles``. If the user attempts to +delete an article using a GET request, ``allowMethod()`` will throw an exception. +Uncaught exceptions are captured by CakePHP's exception handler, and a nice +error page is displayed. There are many built-in +:doc:`Exceptions ` that can be used to indicate the various +HTTP errors your application might need to generate. + +.. warning:: + + Allowing content to be deleted using GET requests is *very* dangerous, as web + crawlers could accidentally delete all your content. That is why we used + ``allowMethod()`` in our controller. + +Because we're only executing logic and redirecting to another action, this +action has no template. You might want to update your index template with links +that allow users to delete articles: + +.. code-block:: php + + + +

Articles

+

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

+ + + + + + + + + + + + + + + + + +
TitleCreatedAction
+ Html->link($article->title, ['action' => 'view', $article->slug]) ?> + + created->format(DATE_RFC850) ?> + + Html->link('Edit', ['action' => 'edit', $article->slug]) ?> + Form->deleteLink( + 'Delete', + ['action' => 'delete', $article->slug], + ['confirm' => 'Are you sure?']) + ?> +
+ +Using :php:meth:`~Cake\\View\\Helper\\FormHelper::deleteLink()` will create a link +that uses JavaScript to do a DELETE request deleting our article. +Prior to CakePHP 5.2 you need to use ``postLink()`` instead. + +.. note:: + + This view code also uses the ``FormHelper`` to prompt the user with a + JavaScript confirmation dialog before they attempt to delete an + article. + +.. tip:: + + The ``ArticlesController`` can also be built with ``bake``: + + .. code-block:: console + + /bin/cake bake controller articles + + However, this does not build the **templates/Articles/*.php** files. + +With a basic articles management setup, we'll create the :doc:`basic actions +for our Tags and Users tables `. diff --git a/pt/tutorials-and-examples/cms/articles-model.rst b/pt/tutorials-and-examples/cms/articles-model.rst new file mode 100644 index 0000000000..a415595707 --- /dev/null +++ b/pt/tutorials-and-examples/cms/articles-model.rst @@ -0,0 +1,88 @@ +Tutorial CMS - Criando nosso primeiro Modelo +############################################ + +Os modelos são o coração das aplicações CakePHP. Eles nos permitem ler e +modificar nossos dados. Eles nos permitem construir relações entre nossos dados, validar +dados e aplicar regras de aplicação. Os modelos fornecem a base necessária para +criar nossas ações de controller e models. + +Os modelos do CakePHP são compostos por objetos ``Table`` e ``Entity``. Os objetos ``Table`` +fornecem acesso à coleção de entidades armazenadas em uma tabela específica. +Eles são armazenados em **src/Model/Table**. O arquivo que criaremos será salvo +em **src/Model/Table/ArticlesTable.php**. O arquivo completo deverá +ficar assim:: + + addBehavior('Timestamp'); + } + } + +Anexamos o comportamento :doc:`/orm/behaviors/timestamp`, que irá +preencher automaticamente as colunas ``created`` e ``modified`` da nossa tabela. +Ao nomear nosso objeto Table como ``ArticlesTable``, o CakePHP pode usar convenções de nomenclatura +para saber que nosso modelo usa a tabela ``articles``. O CakePHP também usa +convenções para saber que a coluna ``id`` é a chave primária da nossa tabela. + +.. note:: + + O CakePHP criará dinamicamente um objeto de modelo para você caso + não encontre um arquivo correspondente em **src/Model/Table**. Isso também significa + que, se você acidentalmente nomear seu arquivo incorretamente (por exemplo, articlestable.php ou + ArticleTable.php), o CakePHP não reconhecerá nenhuma de suas configurações e + usará o modelo gerado. + +Também criaremos uma classe Entity para nossos Artigos. Entidades representam um único +registro no banco de dados e fornecem comportamento em nível de linha para nossos dados. Nossa entidade +será salva em **src/Model/Entity/Article.php**. O arquivo completo deverá +ficar assim:: + + true, + 'title' => true, + 'slug' => true, + 'body' => true, + 'published' => true, + 'created' => true, + 'modified' => true, + 'user' => true, + 'tags' => true, + ]; + } + +No momento, nossa entidade é bem enxuta; configuramos apenas a propriedade ``_accessible``, +que controla como as propriedades podem ser modificadas por +:ref:`entities-mass-assignment`. + +.. tip:: + As classes de entidade ``ArticlesTable`` e ``Article`` podem ser geradas a partir de um + terminal: + + .. code-block:: console + + bin/cake bake model articles + +Ainda não podemos fazer muita coisa com este modelo. Em seguida, criaremos nosso primeiro +:doc:`Controller e Template ` +para nos permitir interagir com nosso modelo. diff --git a/pt/tutorials-and-examples/cms/authorization.rst b/pt/tutorials-and-examples/cms/authorization.rst new file mode 100644 index 0000000000..41a0194ec5 --- /dev/null +++ b/pt/tutorials-and-examples/cms/authorization.rst @@ -0,0 +1,255 @@ +CMS Tutorial - Authorization +############################ + +With users now able to login to our CMS, we want to apply authorization rules +to ensure that each user only edits the posts they own. We'll use the +`authorization plugin `__ to do this. + +Installing Authorization Plugin +================================ + +Use composer to install the Authorization Plugin: + +.. code-block:: console + + composer require "cakephp/authorization:^3.0" + +Load the plugin by adding the following statement to the ``bootstrap()`` method in **src/Application.php**:: + + $this->addPlugin('Authorization'); + +Enabling the Authorization Plugin +================================= + +The Authorization plugin integrates into your application as a middleware layer +and optionally a component to make checking authorization easier. First, lets +apply the middleware. In **src/Application.php** add the following to the class +imports:: + + use Authorization\AuthorizationService; + use Authorization\AuthorizationServiceInterface; + use Authorization\AuthorizationServiceProviderInterface; + use Authorization\Middleware\AuthorizationMiddleware; + use Authorization\Policy\OrmResolver; + +Add the ``AuthorizationServiceProviderInterface`` to the implemented interfaces on your application:: + + class Application extends BaseApplication + implements AuthenticationServiceProviderInterface, + AuthorizationServiceProviderInterface + +Then add the following to your ``middleware()`` method:: + + // Add authorization **after** authentication + $middlewareQueue->add(new AuthorizationMiddleware($this)); + +The ``AuthorizationMiddleware`` will call a hook method on your application when +it starts handling the request. This hook method allows your application to +define the ``AuthorizationService`` it wants to use. Add the following method your +**src/Application.php**:: + + public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface + { + $resolver = new OrmResolver(); + + return new AuthorizationService($resolver); + } + +The OrmResolver lets the authorization plugin find policy classes for ORM +entities and queries. Other resolvers can be used to find policies for other +resources types. + +Next, lets add the ``AuthorizationComponent`` to ``AppController``. In +**src/Controller/AppController.php** add the following to the ``initialize()`` +method:: + + $this->loadComponent('Authorization.Authorization'); + +Lastly we'll mark the add, login, and logout actions as not requiring +authorization by adding the following to +**src/Controller/UsersController.php**:: + + // In the add, login, and logout methods + $this->Authorization->skipAuthorization(); + +The ``skipAuthorization()`` method should be called in any controller action +that should be accessible to all users even those who have not logged in yet. + +Creating our First Policy +========================= + +The Authorization plugin models authorization and permissions as Policy classes. +These classes implement the logic to check whether or not a **identity** is +allowed to **perform an action** on a given **resource**. Our **identity** is +going to be our logged in user, and our **resources** are our ORM entities and +queries. Lets use bake to generate a basic policy: + +.. code-block:: console + + bin/cake bake policy --type entity Article + +This will generate an empty policy class for our ``Article`` entity. You can +find the generated policy in **src/Policy/ArticlePolicy.php**. Next update the +policy to look like the following:: + + isAuthor($user, $article); + } + + public function canDelete(IdentityInterface $user, Article $article) + { + // logged in users can delete their own articles. + return $this->isAuthor($user, $article); + } + + protected function isAuthor(IdentityInterface $user, Article $article) + { + return $article->user_id === $user->getIdentifier(); + } + } + +While we've defined some very simple rules, you can use as complex logic as your +application requires in your policies. + +Checking Authorization in the ArticlesController +================================================ + +With our policy created we can start checking authorization in each controller +action. If we forget to check or skip authorization in an controller action the +Authorization plugin will raise an exception letting us know we forgot to apply +authorization. In **src/Controller/ArticlesController.php** add the following to +the ``add``, ``edit`` and ``delete`` methods:: + + public function add() + { + $article = $this->Articles->newEmptyEntity(); + $this->Authorization->authorize($article); + // Rest of the method + } + + public function edit($slug) + { + $article = $this->Articles + ->findBySlug($slug) + ->contain('Tags') // load associated Tags + ->firstOrFail(); + $this->Authorization->authorize($article); + // Rest of the method. + } + + public function delete($slug) + { + $this->request->allowMethod(['post', 'delete']); + + $article = $this->Articles->findBySlug($slug)->firstOrFail(); + $this->Authorization->authorize($article); + // Rest of the method. + } + +The ``AuthorizationComponent::authorize()`` method will use the current +controller action name to generate the policy method to call. If you'd like to +call a different policy method you can call ``authorize`` with the operation +name:: + + $this->Authorization->authorize($article, 'update'); + +Lastly add the following to the ``tags``, ``view``, and ``index`` methods on the +``ArticlesController``:: + + // View, index and tags actions are public methods + // and don't require authorization checks. + $this->Authorization->skipAuthorization(); + +Fixing the Add & Edit Actions +============================= + +While we've blocked access to the edit action, we're still open to users +changing the ``user_id`` attribute of articles during edit. We +will solve these problems next. First up is the ``add`` action. + +When creating articles, we want to fix the ``user_id`` to be the currently +logged in user. Replace your add action with the following:: + + // in 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()); + + // Changed: Set the user_id from the current user. + $article->user_id = $this->request->getAttribute('identity')->getIdentifier(); + + if ($this->Articles->save($article)) { + $this->Flash->success(__('Your article has been saved.')); + + return $this->redirect(['action' => 'index']); + } + $this->Flash->error(__('Unable to add your article.')); + } + $tags = $this->Articles->Tags->find('list')->all(); + $this->set(compact('article', 'tags')); + } + +Next we'll update the ``edit`` action. Replace the edit method with the following:: + + // in src/Controller/ArticlesController.php + + public function edit($slug) + { + $article = $this->Articles + ->findBySlug($slug) + ->contain('Tags') // load associated Tags + ->firstOrFail(); + $this->Authorization->authorize($article); + + if ($this->request->is(['post', 'put'])) { + $this->Articles->patchEntity($article, $this->request->getData(), [ + // Added: Disable modification of user_id. + 'accessibleFields' => ['user_id' => false] + ]); + if ($this->Articles->save($article)) { + $this->Flash->success(__('Your article has been updated.')); + + return $this->redirect(['action' => 'index']); + } + $this->Flash->error(__('Unable to update your article.')); + } + $tags = $this->Articles->Tags->find('list')->all(); + $this->set(compact('article', 'tags')); + } + +Here we're modifying which properties can be mass-assigned, via the options +for ``patchEntity()``. See the :ref:`changing-accessible-fields` section for +more information. Remember to remove the ``user_id`` control from +**templates/Articles/edit.php** as we no longer need it. + +Wrapping Up +=========== + +We've built a simple CMS application that allows users to login, post articles, +tag them, explore posted articles by tag, and applied basic access control to +articles. We've also added some nice UX improvements by leveraging the +FormHelper and ORM capabilities. + +Thank you for taking the time to explore CakePHP. Next, you should learn more about +the :doc:`/orm`, or you peruse the :doc:`/topics`. diff --git a/pt/tutorials-and-examples/cms/database.rst b/pt/tutorials-and-examples/cms/database.rst index 925cb5b54b..5ba82fe955 100644 --- a/pt/tutorials-and-examples/cms/database.rst +++ b/pt/tutorials-and-examples/cms/database.rst @@ -10,6 +10,8 @@ as tabelas necessárias: .. code-block:: SQL + CREATE DATABASE cake_cms; + USE cake_cms; CREATE TABLE users ( @@ -134,22 +136,16 @@ pelos que se aplicam a sua instalação. Um exemplo completo de como deve ficar configuração segue abaixo:: [ 'default' => [ - 'className' => 'Cake\Database\Connection', - // Substitua Mysql por Postgres se você estiver usando PostgreSQL - 'driver' => 'Cake\Database\Driver\Mysql', - 'persistent' => false, 'host' => 'localhost', 'username' => 'cakephp', - 'password' => 'sua_senha', + 'password' => 'AngelF00dC4k3~', 'database' => 'cake_cms', - // Comente a linha abaixo se estiver usando PostgreSQL - 'encoding' => 'utf8mb4', - 'timezone' => 'UTC', - 'cacheMetadata' => true, + 'url' => env('DATABASE_URL', null), ], ], // Mais configurações abaixo. @@ -162,81 +158,61 @@ com o chapéu de chefe na cor verde. .. note:: - Se você não tiver o arquivo **config/app_local.php** na sua aplicação, - você deve configurar sua conexão no arquivo **config/app.php**. - + O arquivo **config/app_local.php** é uma substituição local do arquivo **config/app.php** + usado para configurar seu ambiente de desenvolvimento rapidamente. -Criando nosso Primeiro Modelo -============================= +Migrations +========== -Modelos são o coração de uma aplicação CakePHP. Ele permite a nós ler e -escrever nossos dados. Eles possibilitam a criação de relacionamentos -entre nossos dados, validar dados, e aplicar regras da aplicação. Modelos -formam a fundação necessária para construir nossas ações de controles e -templates. +As instruções SQL para criar as tabelas deste tutorial também podem ser geradas +usando o plugin Migrations. As migrações oferecem uma maneira independente de plataforma de +executar consultas, de modo que as diferenças sutis entre MySQL, PostgreSQL, SQLite, etc. +não se tornem obstáculos. -Modelos no CakePHP são compostos dos objetos ``Table`` (Tabela) e ``Entity`` -(Entidade). Objetos ``Table`` fornecem acesso a coleção de entidades armazenadas -em uma tabela específica. Elas ficam salvas em **src/Model/Table**. O arquivo -que iremos criar ficará salvo em **src/Model/Table/ArticlesTable.php**. O arquivo -completo deve se parecer com isso:: +.. code-block:: console - addBehavior('Timestamp'); - } - } - -Nós vinculamos o behavior :doc:`/orm/behaviors/timestamp` que irá preencher -automaticamente as colunas ``created`` (criado) e ``modified`` (modificado) -de nossa tabela. -Ao nomear nosso objeto Table ``ArticlesTable``, o CakePHP se baseia nas -convenções de nomes para saber que nosso modelo utiliza a tabela ``articles``. -O CakePHP também usa as convenções para saber que a coluna ``id`` é a chave -primária da tabela. + bin/cake bake migration CreateUsers email:string password:string created modified + bin/cake bake migration CreateArticles user_id:integer title:string slug:string[191]:unique body:text published:boolean created modified + bin/cake bake migration CreateTags title:string[191]:unique created modified + bin/cake bake migration CreateArticlesTags article_id:integer:primary tag_id:integer:primary created modified .. note:: + Alguns ajustes no código gerado podem ser necessários. Por exemplo, a + chave primária composta em ``articles_tags`` será definida para incrementar automaticamente + ambas as colunas:: - O CakePHP criará dinamicamente um objeto de modelo para você se ele - não conseguir encontrar o arquivo correspondente em **src/Model/Table**. - Isso significa que se você acidentalmente nomear errado o arquivo (ex. - articlestable.php ou ArticleTable.php), o CakePHP não reconhecerá nenhuma - de suas configurações e utilizará o modelo dinâmicamente gerado no lugar. + $table->addColumn('article_id', 'integer', [ + 'autoIncrement' => true, + 'default' => null, + 'limit' => 11, + 'null' => false, + ]); + $table->addColumn('tag_id', 'integer', [ + 'autoIncrement' => true, + 'default' => null, + 'limit' => 11, + 'null' => false, + ]); -Nós também vamos criar uma classe Entity para nossa Articles. Entidades -representam um único registro do nosso banco de dados, e implementam -comportamento a nível de linha para nossos dados. Nossa entidade será -salva em **src/Model/Entity/Article.php**. O arquivo complete deve parecer -com este:: + Remova essas linhas para evitar problemas com chaves estrangeiras. Assim que os ajustes estiverem + concluídos:: - true, - 'id' => false, - 'slug' => false, - ]; - } - -Nossa entidade está bem curta agora, e nós iremos configurar apenas a -propriedade ``_accessible`` que controla quais propriedades podem ser -modificadas com :ref:`entities-mass-assignment`. - -Nós não podemos fazer muito com nossos modelos agora, então a seguir -iremos criar nossos :doc:`Controller e Template -` que nos permitirá -interagir com nosso modelo. + bin/cake migrations migrate + +Da mesma forma, os registros de dados iniciais podem ser feitos com seeds. + +.. code-block:: console + + bin/cake bake seed Users + bin/cake bake seed Articles + +Preencha os dados iniciais acima nas novas classes ``UsersSeed`` e ``ArticlesSeed`` +e então:: + + bin/cake migrations seed + +Saiba mais sobre migrações de construção e semeadura de dados: `Migrations +`__ + +Com o banco de dados construído, agora podemos construir :doc:`Models +`. diff --git a/pt/tutorials-and-examples/cms/installation.rst b/pt/tutorials-and-examples/cms/installation.rst index a9e63cf316..35de1b3b23 100644 --- a/pt/tutorials-and-examples/cms/installation.rst +++ b/pt/tutorials-and-examples/cms/installation.rst @@ -33,7 +33,7 @@ A maneira mais fácil de instalar o CakePHP é usando Composer, um gerenciador de dependências para o PHP. Se trata de uma forma simples de instalar o CakePHP a partir de seu terminal ou prompt de comando. Primeiro, você precisa baixar e instalar o Composer, caso você já não o tenha. Se possuir -instalado o programa *cURL*, basta executar o seguinte comando:: +instalado o programa *cURL*, basta executar o seguinte comando: .. code-block:: console @@ -44,11 +44,11 @@ Você também pode baixar o arquivo ``composer.phar`` do Em seguida, basta digitar a seguinte linha de comando no seu terminal a partir do diretório onde se localiza o arquivo ``composer.phar`` para instalar o -esqueleto da aplicação do CakePHP no diretório **cms**. :: +esqueleto da aplicação do CakePHP no diretório **cms**. : .. code-block:: console - php composer.phar create-project --prefer-dist cakephp/app:5.* cms + php composer.phar create-project --prefer-dist cakephp/app:5 cms Caso você tenha feito o download e executado o `Instalador para Windows do Composer `_, então digite a linha @@ -57,7 +57,7 @@ C:\\wamp\\www\\dev): .. code-block:: console - composer self-update && composer create-project --prefer-dist cakephp/app:4.* cms + composer self-update && composer create-project --prefer-dist cakephp/app:5.* cms A vantagem de usar o Composer é que ele irá completar automaticamente um conjunto importante de tarefas, como configurar corretamente as permissões de pastas @@ -96,6 +96,13 @@ do CakePHP funciona: Confira a seção :doc:`/intro/cakephp-folder-structure`. Caso tenha dificuldades durante este tutorial, você pode ver o resultado final no `GitHub `_. +.. tip:: + + O utilitário de console ``bin/cake`` pode construir a maioria das classes e tabelas de dados + deste tutorial automaticamente. No entanto, recomendamos acompanhar + os exemplos de código manual para entender como as peças se encaixam e + como adicionar a lógica da sua aplicação. + Verificando sua Instalação ========================== @@ -119,4 +126,4 @@ tópicos devem ter chapéus de chef verdes, exceto diz sobre o CakePHP estar apt seu banco de dados. Caso contrário, voc%e pode precisar instalar alguma extensão PHP ou definir permissão de diretórios. -A seguir, nós iremos construir o :doc:`Banco de Dados e criar nosso primeiro modelo `. +A seguir, nós iremos construir o :doc:`Banco de Dados `. diff --git a/pt/tutorials-and-examples/cms/tags-and-users.rst b/pt/tutorials-and-examples/cms/tags-and-users.rst new file mode 100644 index 0000000000..fed181f406 --- /dev/null +++ b/pt/tutorials-and-examples/cms/tags-and-users.rst @@ -0,0 +1,505 @@ +CMS Tutorial - Tags and Users +############################# + +With the basic article creation functionality built, we need to enable multiple +authors to work in our CMS. Previously, we built all the models, views and +controllers by hand. This time around we're going to use +:doc:`/bake` to create our skeleton code. Bake is a powerful +code generation :abbr:`CLI (Command Line Interface)` tool that leverages the +conventions CakePHP uses to create skeleton :abbr:`CRUD (Create, Read, Update, +Delete)` applications very efficiently. We're going to use ``bake`` to build our +users code: + +.. code-block:: console + + cd /path/to/our/app + + # You can overwrite any existing files. + bin/cake bake model users + bin/cake bake controller users + bin/cake bake template users + +These 3 commands will generate: + +* The Table, Entity, Fixture files. +* The Controller +* The CRUD templates. +* Test cases for each generated class. + +Bake will also use the CakePHP conventions to infer the associations, and +validation your models have. + +Adding Tagging to Articles +========================== + +With multiple users able to access our small :abbr:`CMS` it would be nice to +have a way to categorize our content. We'll use tags and tagging to allow users +to create free-form categories and labels for their content. Again, we'll use +``bake`` to quickly generate some skeleton code for our application: + +.. code-block:: console + + # Generate all the code at once. + bin/cake bake all tags + +Once you have the scaffold code created, create a few sample tags by going to +**http://localhost:8765/tags/add**. + +Now that we have a Tags table, we can create an association between Articles and +Tags. We can do so by adding the following to the ``initialize`` method on the +``ArticlesTable``:: + + public function initialize(array $config): void + { + $this->addBehavior('Timestamp'); + $this->belongsToMany('Tags'); // Add this line + } + +This association will work with this simple definition because we followed +CakePHP conventions when creating our tables. For more information, read +:doc:`/orm/associations`. + +Updating Articles to Enable Tagging +=================================== + +Now that our application has tags, we need to enable users to tag their +articles. First, update the ``add`` action to look like:: + + Articles->newEmptyEntity(); + if ($this->request->is('post')) { + $article = $this->Articles->patchEntity($article, $this->request->getData()); + + // Hardcoding the user_id is temporary, and will be removed later + // when we build authentication out. + $article->user_id = 1; + + if ($this->Articles->save($article)) { + $this->Flash->success(__('Your article has been saved.')); + + return $this->redirect(['action' => 'index']); + } + $this->Flash->error(__('Unable to add your article.')); + } + // Get a list of tags. + $tags = $this->Articles->Tags->find('list')->all(); + + // Set tags to the view context + $this->set('tags', $tags); + + $this->set('article', $article); + } + + // Other actions + } + +The added lines load a list of tags as an associative array of ``id => title``. +This format will let us create a new tag input in our template. +Add the following to the PHP block of controls in **templates/Articles/add.php**:: + + echo $this->Form->control('tags._ids', ['options' => $tags]); + +This will render a multiple select element that uses the ``$tags`` variable to +generate the select box options. You should now create a couple new articles +that have tags, as in the following section we'll be adding the ability to find +articles by tags. + +You should also update the ``edit`` method to allow adding or editing tags. The +edit method should now look like:: + + public function edit($slug) + { + $article = $this->Articles + ->findBySlug($slug) + ->contain('Tags') // load associated Tags + ->firstOrFail(); + if ($this->request->is(['post', 'put'])) { + $this->Articles->patchEntity($article, $this->request->getData()); + if ($this->Articles->save($article)) { + $this->Flash->success(__('Your article has been updated.')); + + return $this->redirect(['action' => 'index']); + } + $this->Flash->error(__('Unable to update your article.')); + } + + // Get a list of tags. + $tags = $this->Articles->Tags->find('list')->all(); + + // Set tags to the view context + $this->set('tags', $tags); + + $this->set('article', $article); + } + +Remember to add the new tags multiple select control we added to the **add.php** +template to the **templates/Articles/edit.php** template as well. + +Finding Articles By Tags +======================== + +Once users have categorized their content, they will want to find that content +by the tags they used. For this feature we'll implement a route, controller +action, and finder method to search through articles by tag. + +Ideally, we'd have a URL that looks like +**http://localhost:8765/articles/tagged/funny/cat/gifs**. This would let us +find all the articles that have the 'funny', 'cat' or 'gifs' tags. Before we +can implement this, we'll add a new route. Your **config/routes.php** (with +the baked comments removed) should look like:: + + setRouteClass(DashedRoute::class); + + $routes->scope('/', function (RouteBuilder $builder) { + $builder->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']); + $builder->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']); + + // Add this + // New route we're adding for our tagged action. + // The trailing `*` tells CakePHP that this action has + // passed parameters. + $builder->scope('/articles', function (RouteBuilder $builder) { + $builder->connect('/tagged/*', ['controller' => 'Articles', 'action' => 'tags']); + }); + + $builder->fallbacks(); + }); + +The above defines a new 'route' which connects the **/articles/tagged/** path, +to ``ArticlesController::tags()``. By defining routes, you can isolate how your +URLs look, from how they are implemented. If we were to visit +**http://localhost:8765/articles/tagged**, we would see a helpful error page +from CakePHP informing you that the controller action does not exist. Let's +implement that missing method now. In **src/Controller/ArticlesController.php** +add the following:: + + public function tags() + { + // The 'pass' key is provided by CakePHP and contains all + // the passed URL path segments in the request. + $tags = $this->request->getParam('pass'); + + // Use the ArticlesTable to find tagged articles. + $articles = $this->Articles->find('tagged', tags: $tags) + ->all(); + + // Pass variables into the view template context. + $this->set([ + 'articles' => $articles, + 'tags' => $tags + ]); + } + +To access other parts of the request data, consult the :ref:`cake-request` +section. + +Since passed arguments are passed as method parameters, you could also write the +action using PHP's variadic argument:: + + public function tags(...$tags) + { + // Use the ArticlesTable to find tagged articles. + $articles = $this->Articles->find('tagged', tags: $tags) + ->all(); + + // Pass variables into the view template context. + $this->set([ + 'articles' => $articles, + 'tags' => $tags + ]); + } + +Creating the Finder Method +-------------------------- + +In CakePHP we like to keep our controller actions slim, and put most of our +application's logic in the model layer. If you were to visit the +**/articles/tagged** URL now you would see an error that the ``findTagged()`` +method has not been implemented yet, so let's do that. In +**src/Model/Table/ArticlesTable.php** add the following:: + + // add this use statement right below the namespace declaration to import + // the Query class + use Cake\ORM\Query\SelectQuery; + + // The $query argument is a query builder instance. + // The $options array will contain the 'tags' option we passed + // to find('tagged') in our controller action. + public function findTagged(SelectQuery $query, array $tags = []): SelectQuery + { + $columns = [ + 'Articles.id', 'Articles.user_id', 'Articles.title', + 'Articles.body', 'Articles.published', 'Articles.created', + 'Articles.slug', + ]; + + $query = $query + ->select($columns) + ->distinct($columns); + + if (empty($tags)) { + // If there are no tags provided, find articles that have no tags. + $query->leftJoinWith('Tags') + ->where(['Tags.title IS' => null]); + } else { + // Find articles that have one or more of the provided tags. + $query->innerJoinWith('Tags') + ->where(['Tags.title IN' => $tags]); + } + + return $query->groupBy(['Articles.id']); + } + +We just implemented a :ref:`custom finder method `. This is +a very powerful concept in CakePHP that allows you to package up re-usable +queries. Finder methods always get a :doc:`/orm/query-builder` object and an +array of options as parameters. Finders can manipulate the query and add any +required conditions or criteria. When complete, finder methods must return +a modified query object. In our finder we've leveraged the ``distinct()`` and +``leftJoin()`` methods which allow us to find distinct articles that have +a 'matching' tag. + +Creating the View +----------------- + +Now if you visit the **/articles/tagged** URL again, CakePHP will show a new error +letting you know that you have not made a view file. Next, let's build the +view file for our ``tags()`` action:: + + +

+ Articles tagged with + Text->toList(h($tags), 'or') ?> +

+ +
+ +
+ +

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

+ created) ?> +
+ +
+ +In the above code we use the :doc:`/views/helpers/html` and +:doc:`/views/helpers/text` helpers to assist in generating our view output. We +also use the :php:func:`h` shortcut function to HTML encode output. You should +remember to always use ``h()`` when outputting data to prevent HTML injection +issues. + +The **tags.php** file we just created follows the CakePHP conventions for view +template files. The convention is to have the template use the lower case and +underscored version of the controller action name. + +You may notice that we were able to use the ``$tags`` and ``$articles`` +variables in our view template. When we use the ``set()`` method in our +controller, we set specific variables to be sent to the view. The View will make +all passed variables available in the template scope as local variables. + +You should now be able to visit the **/articles/tagged/funny** URL and see all +the articles tagged with 'funny'. + +Improving the Tagging Experience +================================ + +Right now, adding new tags is a cumbersome process, as authors need to +pre-create all the tags they want to use. We can improve the tag selection UI by +using a comma separated text field. This will let us give a better experience to +our users, and use some more great features in the ORM. + +Adding a Computed Field +----------------------- + +Because we'll want a simple way to access the formatted tags for an entity, we +can add a virtual/computed field to the entity. In +**src/Model/Entity/Article.php** add the following:: + + // add this use statement right below the namespace declaration to import + // the Collection class + use Cake\Collection\Collection; + + // Update the accessible property to contain `tag_string` + protected array $_accessible = [ + //other fields... + '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, ', '); + } + +This will let us access the ``$article->tag_string`` computed property. We'll +use this property in controls later on. + +Updating the Views +------------------ + +With the entity updated we can add a new control for our tags. In +**templates/Articles/add.php** and **templates/Articles/edit.php**, +replace the existing ``tags._ids`` control with the following:: + + echo $this->Form->control('tag_string', ['type' => 'text']); + +We'll also need to update the article view template. In +**templates/Articles/view.php** add the line as shown:: + + + +

title) ?>

+

body) ?>

+ // Add the following line +

Tags: tag_string) ?>

+ +You should also update the view method to allow retrieving existing tags:: + + // src/Controller/ArticlesController.php file + + public function view($slug = null) + { + // Update retrieving tags with contain() + $article = $this->Articles + ->findBySlug($slug) + ->contain('Tags') + ->firstOrFail(); + $this->set(compact('article')); + } + +Persisting the Tag String +------------------------- + +Now that we can view existing tags as a string, we'll want to save that data as +well. Because we marked the ``tag_string`` as accessible, the ORM will copy that +data from the request into our entity. We can use a ``beforeSave()`` hook method +to parse the tag string and find/build the related entities. Add the following +to **src/Model/Table/ArticlesTable.php**:: + + public function beforeSave(EventInterface $event, $entity, $options): void + { + if ($entity->tag_string) { + $entity->tags = $this->_buildTags($entity->tag_string); + } + + // Other code + } + + protected function _buildTags($tagString) + { + // Trim tags + $newTags = array_map('trim', explode(',', $tagString)); + // Remove all empty tags + $newTags = array_filter($newTags); + // Reduce duplicated tags + $newTags = array_unique($newTags); + + $out = []; + $tags = $this->Tags->find() + ->where(['Tags.title IN' => $newTags]) + ->all(); + + // Remove existing tags from the list of new tags. + foreach ($tags->extract('title') as $existing) { + $index = array_search($existing, $newTags); + if ($index !== false) { + unset($newTags[$index]); + } + } + // Add existing tags. + foreach ($tags as $tag) { + $out[] = $tag; + } + // Add new tags. + foreach ($newTags as $tag) { + $out[] = $this->Tags->newEntity(['title' => $tag]); + } + + return $out; + } + +If you now create or edit articles, you should be able to save tags as a comma +separated list of tags, and have the tags and linking records automatically +created. + +While this code is a bit more complicated than what we've done so far, it helps +to showcase how powerful the ORM in CakePHP is. You can manipulate query +results using the :doc:`/core-libraries/collections` methods, and handle +scenarios where you are creating entities on the fly with ease. + +Auto-populating the Tag String +============================== + +Before we finish up, we'll need a mechanism that will load the associated tags +(if any) whenever we load an article. + +In your **src/Model/Table/ArticlesTable.php**, change:: + + public function initialize(array $config): void + { + $this->addBehavior('Timestamp'); + // Change this line + $this->belongsToMany('Tags', [ + 'joinTable' => 'articles_tags', + 'dependent' => true + ]); + } + +This will tell the Articles table model that there is a join table associated +with tags. The 'dependent' option tells the table to delete any associated +records from the join table if an article is deleted. + +Lastly, update the findBySlug() method calls in +**src/Controller/ArticlesController.php**:: + + public function edit($slug) + { + // Update this line + $article = $this->Articles + ->findBySlug($slug) + ->contain('Tags') + ->firstOrFail(); + ... + } + + public function view($slug = null) + { + // Update this line + $article = $this->Articles + ->findBySlug($slug) + ->contain('Tags') + ->firstOrFail(); + $this->set(compact('article')); + } + +The ``contain()`` method tells the ``ArticlesTable`` object to also populate the +Tags association when the article is loaded. Now when tag_string is called for +an Article entity, there will be data present to create the string! + +Next we'll be adding :doc:`authentication `.