diff --git a/source/_static/css/citheme.css b/source/_static/css/citheme.css index 642a98613..58de483c3 100644 --- a/source/_static/css/citheme.css +++ b/source/_static/css/citheme.css @@ -241,6 +241,10 @@ html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not( margin-top: 2rem; } +.highlight-console .highlight { + background-color: #fffff0; +} + /* Messages ----------------------------------------------------------------- */ .rst-content .success { diff --git a/source/_static/css/citheme_dark.css b/source/_static/css/citheme_dark.css index bb8442d21..649625a6b 100644 --- a/source/_static/css/citheme_dark.css +++ b/source/_static/css/citheme_dark.css @@ -343,4 +343,8 @@ .highlight .ni { color: #b780b7; } + + .highlight-console .highlight pre { + background: #434343; + } } diff --git a/source/changelogs/index.rst b/source/changelogs/index.rst index 117549c2a..5418ac165 100644 --- a/source/changelogs/index.rst +++ b/source/changelogs/index.rst @@ -12,6 +12,11 @@ .. toctree:: :titlesonly: + v4.4.2 + v4.4.1 + v4.4.0 + v4.3.8 + v4.3.7 v4.3.6 v4.3.5 v4.3.4 diff --git a/source/changelogs/v4.1.1.rst b/source/changelogs/v4.1.1.rst index d09e39eaa..b9d3bf4ab 100644 --- a/source/changelogs/v4.1.1.rst +++ b/source/changelogs/v4.1.1.rst @@ -14,7 +14,7 @@ - 修复了 ``.gitattributes`` 阻止框架下载的问题。 -请注意,此修复也被回溯应用于 **framework** 仓库中的 ``4.0.5``。 +请注意,此修复也被回溯应用于 **framework** 仓库中的 4.0.5 版。 有关已修复的错误列表,请参见仓库的 `CHANGELOG_4.1.md `_。 diff --git a/source/changelogs/v4.3.7.rst b/source/changelogs/v4.3.7.rst new file mode 100644 index 000000000..661392fe5 --- /dev/null +++ b/source/changelogs/v4.3.7.rst @@ -0,0 +1,30 @@ +版本 4.3.7 +############# + +发布日期:2023年7月30日 + +**CodeIgniter4 4.3.7 版本发布** + +.. contents:: + :local: + :depth: 3 + +重大变更 +******** + +- **路由集合 (RouteCollection):** 在 ``RouteCollection::getRoutes()`` 方法中添加了第二个参数 ``bool $includeWildcard = true``。 +- **AutoRouting Legacy:** ``AutoRouter::__construct()`` 的第一个参数从 ``$protectedControllers`` 更改为 ``$cliRoutes``。 +- **FeatureTestTrait:** 当使用 :ref:`withBodyFormat() ` 时,请求正文的优先级已更改。详情请参考 :ref:`升级指南 `。 +- **验证 (Validation):** ``Validation::loadRuleGroup()`` 的返回值从“**规则数组**”更改为“**规则数组** 和* *自定义错误数组** 的 **数组**” (``[rules, customErrors]``)。 + +变更 +******* + +- 数字辅助函数 :php:func:`number_to_amount()`,以前返回“1000”,现在在数字恰好为1000时已更正为返回“1千”。 + +已修复的错误 +************ + +- **自动路由遗留功能:** 修复了一个问题,当你使用 ``$routes->add()`` 添加路由时,控制器的其他方法在Web浏览器中无法访问。 + +请查看仓库的 `CHANGELOG.md `_ 以获取已修复错误的完整列表。 diff --git a/source/changelogs/v4.3.8.rst b/source/changelogs/v4.3.8.rst new file mode 100644 index 000000000..32b896cae --- /dev/null +++ b/source/changelogs/v4.3.8.rst @@ -0,0 +1,21 @@ +版本 4.3.8 +############# + +发布日期:2023年8月25日 + +**CodeIgniter4 4.3.8 版本发布** + +.. contents:: + :local: + :depth: 3 + +已修复的错误 +************ + +- **控制器过滤器 (Controller Filters):** 在以前的版本中,``['except' => []]`` 或 ``['except' => '']`` + 意味着“排除所有”。已修复此错误,现在 + + - ``['except' => []]`` 意味着不排除任何内容。 + - ``['except' => '']`` 意味着只排除基础URL。 + +请查看仓库的 `CHANGELOG.md `_ 以获取已修复错误的完整列表。 diff --git a/source/changelogs/v4.4.0.rst b/source/changelogs/v4.4.0.rst new file mode 100644 index 000000000..77a2b8672 --- /dev/null +++ b/source/changelogs/v4.4.0.rst @@ -0,0 +1,304 @@ +版本 4.4.0 +############# + +发布日期:2023年8月25日 + +CodeIgniter4 的 4.4.0 版本发布 + +.. contents:: + :local: + :depth: 3 + +亮点 +********** + +- 调试工具栏现在具有新的“热重载”功能 + (*由* `lonnieezell `_ 贡献)。 + 请参阅 `测试`_。 + +重大变更 +******** + +行为变更 +================ + +URI::setSegment() 和不存在的段 +------------------------------------------ + +当你设置最后一个 ``+2`` 段时,现在会抛出异常。 +在之前的版本中,只有当指定了最后一个段的 ``+3`` 或更多时才会抛出异常。请参阅 :ref:`upgrade-440-uri-setsegment`。 + +当前最后一个段的下一个段(``+1``)可以像以前一样设置。 + +.. _v440-factories: + +工厂 +--------- + +使用命名空间传递类名 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +现在,只有在请求 :ref:`不带命名空间的类名 ` 时,``preferApp`` 才起作用。 + +例如,当你调用 ``model(\Myth\Auth\Models\UserModel::class)`` 或 +``model('Myth\Auth\Models\UserModel')`` 时: + + - 之前: + + - 如果存在 ``App\Models\UserModel`` 并且 ``preferApp`` 为 true(默认值),则返回该类 + - 如果存在 ``Myth\Auth\Models\UserModel`` 并且 ``preferApp`` 为 false,则返回该类 + + - 现在: + + - 无论 ``preferApp`` 是否为 true(默认值),都返回 ``Myth\Auth\Models\UserModel`` + - 如果在调用 ``model()`` 之前定义了 ``Factories::define('models', 'Myth\Auth\Models\UserModel', 'App\Models\UserModel')``,则返回 ``App\Models\UserModel`` + +如果你错误地传递了一个不存在的类名,之前的版本会返回 ``App`` 或 ``Config`` 命名空间中的类实例,因为存在 ``preferApp`` 功能。 + +例如,在控制器(``namespace App\Controllers``)中,如果你错误地调用了 ``config(Config\App::class)`` (注意类名缺少前导的 ``\``),实际上传递的是 ``App\Controllers\Config\App``。 +但是该类不存在,因此 Factories 现在将返回 ``null``。 + +属性名称 +^^^^^^^^^^^^^ + +属性 ``Factories::$basenames`` 已更名为 ``$aliases``。 + +自动加载器 +---------- + +以前,CodeIgniter 的自动加载器允许加载以 `.php` 扩展名结尾的类名。这意味着可以实例化类似 `new Foo.php()` 的对象,并将其实例化为 `new Foo()`。由于 `Foo.php` 是无效的类名,自动加载器的行为已更改。现在,实例化这样的类将失败。 + +.. _v440-codeigniter-and-exit: + +CodeIgniter 和 exit() +---------------------- + +``CodeIgniter::run()`` 方法不再调用 ``exit(EXIT_SUCCESS)``。退出调用已移至 **public/index.php**。 + +.. _v440-site-uri-changes: + +站点 URI 更改 +---------------- + +添加了一个扩展了 ``URI`` 类并表示站点 URI 的新 ``SiteURI`` 类,并且现在在许多需要当前 URI 的地方使用它。 + +控制器中的 ``$this->request->getUri()`` 返回 ``SiteURI`` 实例。 +此外,:php:func:`site_url()` 、:php:func:`base_url()` 和 :php:func:`current_url()` +在内部使用 SiteURI。 + +getPath() +^^^^^^^^^ + +``getPath()`` 方法现在始终返回带有前导 ``/`` 的完整 URI 路径。 +因此,当你的 baseURL 具有子目录并且你想获取相对于 baseURL 的路径时,必须使用新的 ``getRoutePath()`` 方法。 + +例如:: + + baseURL: http://localhost:8888/CodeIgniter4/ + 当前 URI: http://localhost:8888/CodeIgniter4/foo/bar + getPath(): /CodeIgniter4/foo/bar + getRoutePath(): foo/bar + +站点 URI 值 +^^^^^^^^^^^^^^^ + +SiteURI 类现在比以前更严格地规范化站点 URI,并修复了一些错误。 + +因此,与之前的版本相比,框架可能会以稍微不同的方式返回站点 URI 或 URI 路径。 +例如,在 ``index.php`` 之后会添加 ``/``:: + + http://example.com/test/index.php?page=1 + ↓ + http://example.com/test/index.php/?page=1 + +.. _v440-interface-changes: + +接口更改 +================= + +.. note:: 只要你没有扩展相关的 CodeIgniter 核心类或实现这些接口,所有这些更改都是向后兼容的,无需干预。 + +- **Validation:** 在 ``ValidationInterface`` 中添加了 ``getValidated()`` 方法。 + +.. _v440-method-signature-changes: + +方法签名更改 +======================== + +.. _v440-parameter-type-changes: + +参数类型更改 +---------------------- + +- **Services:** + - ``Services::security()`` 的第一个参数已从 ``Config\App`` 更改为 ``Config\Security``。 + - ``Services::session()`` 的第一个参数已从 ``Config\App`` 更改为 ``Config\Session``。 +- **Session:** + - ``Session::__construct()`` 的第二个参数已从 ``Config\App`` 更改为 ``Config\Session``。 + - 数据库的 ``BaseHandler``、``DatabaseHandler``、``FileHandler``、``MemcachedHandler`` 和 ``RedisHandler`` 中的 ``__construct()`` 的第一个参数已从 ``Config\App`` 更改为 ``Config\Session``。 +- **Security:** ``Security::__construct()`` 的第一个参数已从 ``Config\App`` 更改为 ``Config\Security``。 +- **Validation:** ``Validation::check()`` 的方法签名已更改。``$rule`` 参数上的 ``string`` 类型提示已被删除。 +- **CodeIgniter:** ``CodeIgniter::setRequest()`` 的方法签名已更改。``$request`` 参数上的 ``Request`` 类型提示已被删除。 +- **FeatureTestCase:** + - ``FeatureTestCase::populateGlobals()`` 的方法签名已更改。``$request`` 参数上的 ``Request`` 类型提示已被删除。 + - ``FeatureTestCase::setRequestBody()`` 的方法签名已更改。``$request`` 参数上的 ``Request`` 类型提示和返回类型 ``Request`` 已被删除。 + +添加的参数 +---------------- + +- **Routing:** 在 ``RouteCollection::__construct()`` 中添加了第三个参数 ``Routing $routing``。 + +删除的参数 +------------------ + +- **Services:** 在 ``Services::exceptions()`` 中删除了第二个参数 ``$request`` 和第三个参数 ``$response``。 +- **错误处理:** 在 ``CodeIgniter\Debug\Exceptions::__construct()`` 中删除了第二个参数 ``$request`` 和第三个参数 ``$response``。 + +返回类型更改 +------------------- + +- **自动加载器:** ``loadClass`` 和 ``loadClassmap`` 方法的返回签名都改为 ``void``,以便与 ``spl_autoload_register`` 和 ``spl_autoload_unregister`` 函数中的回调兼容。 + +增强功能 +************ + +命令 +======== + +- **spark routes:** + - 现在你可以在请求 URL 中指定主机。 + 请参阅 :ref:`routing-spark-routes-specify-host`。 + - 它在 *Handler* 中显示 :ref:`view-routes` 的视图文件,如下所示: + + +---------+-------------+------+------------------------------+----------------+---------------+ + | Method | Route | Name | Handler | Before Filters | After Filters | + +---------+-------------+------+------------------------------+----------------+---------------+ + | GET | about | » | (View) pages/about | | toolbar | + +---------+-------------+------+------------------------------+----------------+---------------+ + + +测试 +======= + +- **调试工具栏:** + - 调试工具栏现在具有新的“热重载”功能,可以在文件更改时自动重新加载页面。 + 请参阅 :ref:`debug-toolbar-hot-reload`。 + - 现在,在 *Routes* 选项卡的 *DEFINED ROUTES* 中显示 :ref:`view-routes`。 + +数据库 +======== + +- **MySQLi:** 在数据库配置中添加了 ``numberNative`` 属性,以保持 SQL 查询后获取的变量类型与数据库中设置的类型一致。 + 请参阅 :ref:`Database Configuration `。 +- **SQLSRV:** 字段元数据现在包括 ``nullable``。请参阅 :ref:`db-metadata-getfielddata`。 + +模型 +===== + +- 为实体添加了特殊的 getter/setter,以避免方法名称冲突。 + 请参阅 :ref:`entities-special-getter-setter`。 + +库 +========= + +- **Validation:** 添加了 ``Validation::getValidated()`` 方法,用于获取实际验证的数据。请参阅 :ref:`validation-getting-validated-data` 了解详细信息。 +- **Images:** 现在可以使用选项 ``$quality`` 压缩 WebP 图像。 +- **Uploaded Files:** 添加了 ``UploadedFiles::getClientPath()`` 方法,如果通过目录上传方式上传文件,则返回文件的 `full_path` 索引的值。 +- **CURLRequest:** 添加了请求选项 ``proxy``。请参阅 :ref:`CURLRequest Class `。 +- **URI:** 添加了一个扩展了 ``URI`` 并表示站点 URI 的新 ``SiteURI`` 类。 + +辅助函数和方法 +===================== + +- **Array:** 添加了 :php:func:`array_group_by()` 辅助函数,用于将数据值分组在一起。支持点符号语法。 +- **Common:** :php:func:`force_https()` 不再终止应用程序,而是抛出 ``RedirectException``。 + +其他 +====== + +- **DownloadResponse:** 添加了 ``DownloadResponse::inline()`` 方法,将 ``Content-Disposition: inline`` 标头设置为在浏览器中显示文件。 + 请参阅 :ref:`open-file-in-browser` 了解详细信息。 +- **View:** 在 ``renderSection()`` 上添加了可选的第二个参数 ``$saveData``,以防止在显示后自动清除数据。请参阅 :ref:`View Layouts ` 了解详细信息。 +- **自动路由(改进):** + - 现在你可以路由到模块。请参阅 :ref:`auto-routing-improved-module-routing` 了解详细信息。 + - 如果找到与 URI 段对应的控制器,并且该控制器没有为该 URI 段定义的方法,则将执行默认方法。这样可以更灵活地处理自动路由中的 URI。请参阅 :ref:`controller-default-method-fallback` 了解详细信息。 +- **过滤器:** 现在可以在 :ref:`$filters 属性 ` 中使用过滤器参数。 +- **请求:** 添加了 ``IncomingRequest::setValidLocales()`` 方法,用于设置有效的区域设置。 +- **Table:** 添加了 ``Table::setSyncRowsWithHeading()`` 方法,用于将行列与标题同步。请参阅 :ref:`table-sync-rows-with-headings` 了解详细信息。 +- **错误处理:** 现在可以使用 :ref:`custom-exception-handlers`。 +- **RedirectException:** + - 它还可以接受实现 ``ResponseInterface`` 的对象作为第一个参数。 + - 它实现了 ``ResponsableInterface``。 +- **Factories:** + - 现在可以定义实际加载的类名。请参阅 :ref:`factories-defining-classname-to-be-loaded`。 + - 实现了配置缓存。请参阅 :ref:`factories-config-caching` 了解详细信息。 + +消息更改 +*************** + +- 添加了 ``Core.invalidDirectory`` 错误消息。 +- 改进了 ``HTTP.invalidHTTPProtocol`` 错误消息。 + +变更 +******* + +- **Images:** 在 ``GDHandler`` 中,WebP 的默认质量从 80 改为 90。 +- **Config:** + - 删除了 **app/Config/App.php** 中已弃用的 Cookie 项。 + - 删除了 **app/Config/App.php** 中已弃用的 Session 项。 + - 删除了 **app/Config/App.php** 中已弃用的 CSRF 项。 + - 将路由设置移至 **app/Config/Routing.php** 配置文件。 + 请参阅 :ref:`Upgrading Guide `。 +- **DownloadResponse:** 在生成响应标头时,如果之前已指定了 ``Content-Disposition`` 标头,则不替换它。 +- **自动加载器:** + - 在 v4.4.0 之前,CodeIgniter 的自动加载器不允许在某些操作系统上的文件名中使用特殊字符。 + 可以使用的符号是 ``/``、``_``、``.``、``:``、``\`` 和空格。 + 因此,如果你将 CodeIgniter 安装在包含特殊字符(如 ``(``、``)`` 等)的文件夹中,CodeIgniter 将无法工作。 + 从 v4.4.0 开始,此限制已被移除。 + - ``Autoloader::loadClass()`` 和 ``Autoloader::loadClassmap()`` 方法现在都标记为 ``@internal``。 +- **RouteCollection:** 受保护属性 ``$routes`` 的数组结构已进行了修改以提高性能。 +- **HSTS:** 现在,无论是通过 :php:func:`force_https()` 还是 ``Config\App::$forceGlobalSecureRequests = true``,都会设置 HTTP 状态码 307,允许在重定向后保留 HTTP 请求方法。 + 在之前的版本中,它是 302。 + +弃用 +************ + +- **Entity:** 弃用了 ``Entity::setAttributes()`` 方法。请改用 ``Entity::injectRawData()``。 +- **错误处理:** 弃用了 ``CodeIgniter\Debug\Exceptions`` 中的许多方法和属性。因为这些方法已移至 ``BaseExceptionHandler`` 或 ``ExceptionHandler``。 +- **自动加载器:** 弃用了 ``Autoloader::sanitizeFilename()``。 +- **CodeIgniter:** + - 弃用了 ``CodeIgniter::$returnResponse`` 属性。不再使用。 + - 弃用了 ``CodeIgniter::$cacheTTL`` 属性。不再使用。请改用 ``ResponseCache``。 + - 弃用了 ``CodeIgniter::cache()`` 方法。不再使用。请改用 ``ResponseCache``。 + - 弃用了 ``CodeIgniter::cachePage()`` 方法。不再使用。请改用 ``ResponseCache``。 + - 弃用了 ``CodeIgniter::generateCacheName()`` 方法。不再使用。请改用 ``ResponseCache``。 + - 弃用了 ``CodeIgniter::callExit()`` 方法。不再使用。 +- **RedirectException:** 弃用了 ``\CodeIgniter\Router\Exceptions\RedirectException``。请改用 ``\CodeIgniter\HTTP\Exceptions\RedirectException``。 +- **Session:** 弃用了 ``Session`` 中的属性 ``$sessionDriverName``、``$sessionCookieName``、 + ``$sessionExpiration``、``$sessionSavePath``、``$sessionMatchIP``、 + ``$sessionTimeToUpdate`` 和 ``$sessionRegenerateDestroy``,不再使用。请改用 ``$config``。 +- **Security:** 弃用了 ``Security`` 中的属性 ``$csrfProtection``、``$tokenRandomize``、 + ``$tokenName``、``$headerName``、``$expires``、``$regenerate`` 和 + ``$redirect``,不再使用。请改用 ``$config``。 +- **URI:** + - 弃用了 ``URI::$uriString``。 + - 弃用了 ``URI::$baseURL``。请改用 ``SiteURI``。 + - 弃用了 ``URI::setSilent()``。 + - 弃用了 ``URI::setScheme()``。请改用 ``withScheme()``。 + - 弃用了 ``URI::setURI()``。 + +- **IncomingRequest:** + - 弃用了 ``IncomingRequest::detectURI()``,不再使用。 + - 弃用了 ``IncomingRequest::detectPath()``,不再使用。已移至 ``SiteURIFactory``。 + - 弃用了 ``IncomingRequest::parseRequestURI()``,不再使用。已移至 ``SiteURIFactory``。 + - 弃用了 ``IncomingRequest::parseQueryString()``,不再使用。已移至 ``SiteURIFactory``。 + - 弃用了 ``IncomingRequest::setPath()``。 + +已修复的错误 +************ + +- **自动路由(改进):** 在之前的版本中,当 ``$translateURIDashes`` 为 true 时,两个 URI 对应于单个控制器方法,一个 URI 用于破折号(例如 **foo-bar**),另一个 URI 用于下划线(例如 **foo_bar**)。修复了此错误。现在,下划线的 URI(**foo_bar**)无法访问。 +- **输出缓冲:** 修复了输出缓冲的错误。 +- **ControllerTestTrait:** ``ControllerTestTrait::withUri()`` 使用 URI 创建一个新的 Request 实例。因为 Request 实例应该具有 URI 实例。此外,如果 URI 字符串中的主机名与 ``Config\App`` 中的有效主机名不匹配,则将设置有效的主机名。 + +有关修复的所有错误的完整列表,请参阅存储库的 `CHANGELOG.md `_。 diff --git a/source/changelogs/v4.4.1.rst b/source/changelogs/v4.4.1.rst new file mode 100644 index 000000000..12b65f178 --- /dev/null +++ b/source/changelogs/v4.4.1.rst @@ -0,0 +1,22 @@ +版本 4.4.1 +############# + +发布日期:2023年9月5日 + +**CodeIgniter4 的 4.4.1 版本** + +.. contents:: + :local: + :depth: 3 + +修复的问题 +********** + +- **AutoRouting Legacy:** 修复了 Legacy 自动路由无法正常工作的 bug。 +- **FeatureTest:** + - 修复了 FeatureTest 可能导致风险测试的 bug。 + - 修复了 FeatureTest 在 forceGlobalSecureRequests 为 true 时失败的 bug。 + +请参阅仓库的 +`CHANGELOG.md `_ +获取完整的修复 bug 列表。 diff --git a/source/changelogs/v4.4.2.rst b/source/changelogs/v4.4.2.rst new file mode 100644 index 000000000..b0cec36d2 --- /dev/null +++ b/source/changelogs/v4.4.2.rst @@ -0,0 +1,40 @@ +版本 4.4.2 +############# + +发布日期:2023年10月19日 + +**CodeIgniter4 的 4.4.2 版本** + +.. contents:: + :local: + :depth: 3 + +消息变更 +*************** + +- 添加了 ``Language.invalidMessageFormat`` 错误消息。 + +变更 +******* + +- **数据库迁移:** 移除了 ``spark migrate:rollback`` 命令的 ``-g`` 选项。该选项从一开始就无效。此外,回滚命令将数据库状态恢复到指定的批次号,并且无法仅指定特定的数据库组。 +- **安全性:** 现在还会检查原始请求体(非 JSON 格式)中的 CSRF 令牌,用于 PUT、PATCH 和 DELETE 类型的请求。 + +弃用 +************ + +- **过滤器:** 过滤器的自动发现和 ``Filters::discoverFilters()`` 方法已弃用。请改用 :ref:`registrars`。有关详细信息,请参阅 :ref:`modules-filters`。 +- **CLI:** 弃用了公共属性 ``CLI::$readline_support`` 和 ``CLI::$wait_msg``。这些方法将被保护。 +- **CodeIgniter:** ``displayCache()`` 方法的 ``$config`` 参数已弃用。未使用该参数。 + +修复的问题 +********** + +- **CodeIgniter:** 修复了在页面未找到时返回 "200 OK" 响应状态码的 bug。 +- **Spark:** 修复了在生产模式下 spark 无法显示异常或在发生异常时以 JSON 格式显示回溯的 bug。 +- **Forge:** 修复了在给现有表添加主键时,如果没有添加其他键,则会被忽略的 bug。 +- **路由:** 修复了 ``spark routes`` 可能显示不正确的路由名称的 bug。 + +请参阅仓库的 +`CHANGELOG.md `_ +获取完整的修复 bug 列表。 diff --git a/source/cli/cli_commands.rst b/source/cli/cli_commands.rst index 8b3627c5f..0a01a2d26 100644 --- a/source/cli/cli_commands.rst +++ b/source/cli/cli_commands.rst @@ -51,7 +51,9 @@ run() ``run()`` 方法是在运行命令时调用的方法。``$params`` 数组是命令名称后面的任何 CLI 参数列表,供你使用。如果 CLI 字符串是: - > php spark foo bar baz +.. code-block:: console + + php spark foo bar baz 那么 **foo** 是命令名称, ``$params`` 数组将是: diff --git a/source/cli/cli_generators.rst b/source/cli/cli_generators.rst index bd718391d..18aa5e2c0 100644 --- a/source/cli/cli_generators.rst +++ b/source/cli/cli_generators.rst @@ -13,9 +13,11 @@ CodeIgniter4 现在配备了生成器,以简化常规控制器、模型、实体 ************ 所有内置生成器在使用 ``php spark list`` 列出时都位于 ``Generators`` 组下。 -要查看特定生成器的完整描述和使用信息,请使用命令:: +要查看特定生成器的完整描述和使用信息,请使用命令: - > php spark help <生成器命令> +.. code-block:: console + + php spark help <生成器命令> 其中 ``<生成器命令>`` 将替换为要检查的命令。 @@ -265,9 +267,11 @@ make:validation 不要担心!CodeIgniter4 还配备了专用的 ``make:scaffold`` 命令,它基本上是控制器、模型、实体、迁移和种子生成器命令的包装器。你只需要输入用于命名所有生成类的类名。另外, **每个生成器命令支持的单独选项** 也会被脚手架命令识别。 -在终端中运行此命令:: +在终端中运行此命令: + +.. code-block:: console - > php spark make:scaffold user + php spark make:scaffold user 将创建以下文件: diff --git a/source/cli/cli_library/001.php b/source/cli/cli_library/001.php index e42c9a923..45f025b1c 100644 --- a/source/cli/cli_library/001.php +++ b/source/cli/cli_library/001.php @@ -1,11 +1,16 @@ php spark +.. code-block:: console + + php spark 有关详细信息,请参阅 :doc:`spark_commands` 页面。 diff --git a/source/cli/spark_commands.rst b/source/cli/spark_commands.rst index 1041d9684..bfcfc1870 100644 --- a/source/cli/spark_commands.rst +++ b/source/cli/spark_commands.rst @@ -6,7 +6,7 @@ CodeIgniter 提供了官方命令 **spark** 和内置命令。 .. contents:: :local: - :depth: 2 + :depth: 3 **************** 运行命令 @@ -15,64 +15,99 @@ CodeIgniter 提供了官方命令 **spark** 和内置命令。 通过 CLI 运行 =============== -这些命令是从项目根目录的命令行运行的。 -提供了命令文件 **spark** 用于运行任何 CLI 命令:: +命令是从命令行中在项目根目录下运行的。 +提供了一个名为 **spark** 的命令文件,用于运行任何 CLI 命令。 - > php spark +显示命令列表 +------------------------ -如果不指定命令直接调用,将显示一个简单的帮助页面,其中也提供了可用命令列表。 +当不指定命令调用 **spark** 时,会显示一个简单的帮助页面,其中还提供了按类别排序的可用命令列表及其描述: -你应该将命令名作为第一个参数传递来运行该命令:: +.. code-block:: console - > php spark migrate + php spark -某些命令需要附加参数,应在命令后直接提供这些参数,使用空格分隔:: +spark list +^^^^^^^^^^ - > php spark db:seed DevUserSeeder +``php spark`` 与 ``list`` 命令完全相同: -你始终可以传递 ``--no-header`` 来隐藏标题输出,有助于解析结果:: +.. code-block:: console - > php spark cache:clear --no-header + php spark list -对于 CodeIgniter 提供的所有命令,如果你没有提供所需的参数,它会提示你它需要正确运行的信息:: +你还可以使用 ``--simple`` 选项获取按字母顺序排序的所有可用命令的原始列表: - > php spark make::controller +.. code-block:: console - Controller class name: + php spark list --simple -调用命令 -================ +显示帮助 +------------ -命令也可以在代码中调用。这通常在控制器的 cron 任务中完成,但可以在任何时候使用。你可以使用 ``command()`` 函数来实现。该函数一直可用。 +你可以使用 ``help`` 命令获取有关任何 CLI 命令的帮助,如下所示: -.. literalinclude:: cli_commands/001.php +.. code-block:: console -唯一的参数是一个字符串,即调用的命令及任何参数。这看起来与从命令行调用完全相同。 + php spark help db:seed -从命令行外运行时,命令的所有输出都会被捕获。它从命令返回,以便你可以选择显示或不显示。 +自 v4.3.0 起,你还可以使用 ``--help`` 选项代替 ``help`` 命令: -****************** -使用帮助命令 -****************** +.. code-block:: console -spark help -========== + php spark db:seed --help -你可以使用 ``help`` 命令获取有关任何 CLI 命令的帮助:: +运行命令 +----------------- - > php spark help db:seed +你应该将命令的名称作为第一个参数传递以运行该命令: -从 v4.3.0 开始,你也可以使用 ``--help`` 选项代替 ``help`` 命令:: +.. code-block:: console - > php spark db:seed --help + php spark migrate -spark list -========== +某些命令接受附加参数,这些参数应该直接在命令之后用空格分隔提供: + +.. code-block:: console + + php spark db:seed DevUserSeeder + +对于 CodeIgniter 提供的所有命令,如果你没有提供所需的参数,系统将提示你提供运行所需的信息: + +.. code-block:: console + + php spark make:controller + + Controller 类名: + +抑制头部输出 +------------------------- -使用 ``list`` 命令获取可用命令列表及其描述,这些命令已按类别排序:: +运行命令时,会输出包含 CodeIgniter 版本和当前时间的头部信息: - > php spark list +.. code-block:: console + + php spark env + + CodeIgniter v4.3.5 Command Line Tool - Server Time: 2023-06-16 12:45:31 UTC+00:00 + + Your environment is currently set as development. + +你可以始终传递 ``--no-header`` 以抑制头部输出,这对于解析结果很有帮助: + +.. code-block:: console + + php spark env --no-header + + Your environment is currently set as development. + +调用命令 +================ + +命令也可以从你自己的代码中运行。这通常在控制器中用于 cron 任务,但可以随时使用。你可以使用 ``command()`` 函数来实现。该函数始终可用。 + +.. literalinclude:: cli_commands/001.php -你也可以使用 ``--simple`` 选项获取所有可用命令的原始列表,这些命令已按字母顺序排序:: +唯一的参数是字符串,即所调用的命令和任何参数。它的使用方式与从命令行调用完全相同。 - > php spark list --simple +当不从命令行运行时,所有运行的命令的输出都会被捕获。它会从命令中返回,以便你可以选择是否显示它。 diff --git a/source/concepts/autoloader.rst b/source/concepts/autoloader.rst index ee6cc1fff..afdb930fa 100755 --- a/source/concepts/autoloader.rst +++ b/source/concepts/autoloader.rst @@ -39,9 +39,11 @@ CodeIgniter 提供了一个非常灵活的自动加载器,可以通过非常少 每行的键是命名空间本身。这个不需要尾部反斜杠。 值是可以找到类的目录位置。 -.. note:: 你可以使用 ``spark namespaces`` 命令检查命名空间配置:: +.. note:: 你可以使用 ``spark namespaces`` 命令检查命名空间配置: - > php spark namespaces + .. code-block:: console + + php spark namespaces 默认情况下,应用程序目录被映射到 ``App`` 命名空间。你必须为应用程序目录中的控制器、库或模型添加命名空间,它们将在 ``App`` 命名空间下被找到。 diff --git a/source/concepts/factories.rst b/source/concepts/factories.rst index f98fce195..8e69a0fe2 100644 --- a/source/concepts/factories.rst +++ b/source/concepts/factories.rst @@ -32,65 +32,74 @@ 另一方面,服务具有创建实例的代码,所以它可以创建一个需要其他服务或类实例的复杂实例。获取服务时,服务需要一个服务名称,而不是一个类名,所以可以在不更改客户端代码的情况下更改返回的实例。 +.. _factories-loading-class: + 加载类 *************** 加载一个类 =============== -.. _factories-example: +以 **模型** 为例。你可以通过使用 Factories 类的魔术静态方法 ``Factories::models()`` 访问特定于模型的工厂。 + +静态方法名称称为 *component*。 -模型示例 -------------- +.. _factories-passing-classname-without-namespace: -以 **模型** 为例。你可以通过使用 Factories 类的魔术静态方法 ``Factories::models()`` 访问特定于模型的工厂。 +不带命名空间的类名 +----------------------------------- -静态方法名称称为 *组件*。 +如果你传递一个不带命名空间的类名,Factories 首先会在 ``App`` 命名空间中搜索与魔术静态方法名对应的路径。``Factories::models()`` 会搜索 **app/Models** 目录。 -默认情况下,工厂首先在 ``App`` 命名空间中搜索与魔术静态方法名称对应的路径。``Factories::models()`` 搜索 **app/Models** 目录。 +传递短类名 +^^^^^^^^^^^^^^^^^^^^^^^ -在以下代码中,如果你有 ``App\Models\UserModel``,将返回实例: +在下面的代码中,如果你有 ``App\Models\UserModel``,将返回该实例: .. literalinclude:: factories/001.php -或者你也可以请求一个特定的类: +如果没有 ``App\Models\UserModel``,它会在所有命名空间中搜索 ``Models\UserModel``。 -.. literalinclude:: factories/002.php - :lines: 2- +下次你在代码中的任何地方请求相同的类时,Factories 将确保你获得之前的实例: -如果你只有 ``Blog\Models\UserModel``,将返回实例。 -但如果你同时有 ``App\Models\UserModel`` 和 ``Blog\Models\UserModel``, -将返回 ``App\Models\UserModel`` 的实例。 +.. literalinclude:: factories/003.php -如果你想获取 ``Blog\Models\UserModel``,你需要禁用 ``preferApp`` 选项: +传递带有子目录的短类名 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. literalinclude:: factories/010.php +如果要加载子目录中的类,可以使用 ``/`` 作为分隔符。如果存在,以下代码将加载 **app/Libraries/Sub/SubLib.php**: + +.. literalinclude:: factories/013.php :lines: 2- -参见 :ref:`factories-options` 了解详细信息。 +传递完全限定类名 +-------------------------------- -下次你在代码中的任何地方请求相同的类,工厂都会确保像以前一样返回该实例: +你还可以请求一个完全限定的类名: -.. literalinclude:: factories/003.php +.. literalinclude:: factories/002.php + :lines: 2- -在子目录中加载类 -================================== +如果存在,它将返回 ``Blog\Models\UserModel`` 的实例。 -如果你想在子目录中加载类,可以使用 ``/`` 作为分隔符。 -以下代码加载 **app/Libraries/Sub/SubLib.php**: +.. note:: 在 v4.4.0 之前,当你请求一个完全限定的类名时,如果只有 ``Blog\Models\UserModel``,将返回该实例。但是,如果同时存在 ``App\Models\UserModel`` 和 ``Blog\Models\UserModel``,将返回 ``App\Models\UserModel`` 的实例。 -.. literalinclude:: factories/013.php - :lines: 2- + 如果你想获取 ``Blog\Models\UserModel``,你需要禁用选项 ``preferApp``: + + .. literalinclude:: factories/010.php + :lines: 2- 便利函数 ********************* 为工厂提供了两个快捷函数。这些函数始终可用。 +.. _factories-config: + config() ======== -第一个是 ``config()``,它返回一个新的 Config 类实例。唯一必需的参数是类名称: +第一个是 :php:func:`config()`,它返回一个新的 Config 类实例。唯一必需的参数是类名称: .. literalinclude:: factories/008.php @@ -101,6 +110,25 @@ model() .. literalinclude:: factories/009.php +.. _factories-defining-classname-to-be-loaded: + +定义要加载的类名 +******************************* + +.. versionadded:: 4.4.0 + +你可以使用 ``Factories::define()`` 方法定义在加载类之前要加载的类名: + +.. literalinclude:: factories/014.php + :lines: 2- + +第一个参数是组件。第二个参数是类别名(Factories 魔术静态方法的第一个参数),第三个参数是要加载的真实完全限定类名。 + +之后,如果使用 Factories 加载 ``Myth\Auth\Models\UserModel``,将返回 ``App\Models\UserModel`` 的实例: + +.. literalinclude:: factories/015.php + :lines: 2- + 工厂参数 ****************** @@ -131,6 +159,8 @@ getShared boolean 是否返回类的共享实例或者加载一个新实 preferApp boolean 是否优先使用 App 命名空间中具有相同基本名称的类而不是其他明确的类请求。 ``true`` ========== ============== ======================================================================= =================================================== +.. note:: 自 v4.4.0 起,``preferApp`` 仅在你请求 :ref:`不带命名空间的类名 ` 时起作用。 + 工厂行为 ****************** @@ -191,3 +221,71 @@ setOptions 方法 .. literalinclude:: factories/007.php :lines: 2- + +.. _factories-config-caching: + +配置缓存 +************** + +.. versionadded:: 4.4.0 + +为了提高性能,实现了配置缓存。 + +先决条件 +============ + +.. important:: 当不满足先决条件时使用此功能将阻止 CodeIgniter 正常运行。在这种情况下不要使用此功能。 + +- 要使用此功能,Factories 中实例化的所有 Config 对象的属性在实例化后不能被修改。换句话说,Config 类必须是不可变或只读的类。 +- 默认情况下,每个被缓存的 Config 类必须实现 ``__set_state()`` 方法。 + +工作原理 +============ + +- 如果 Factories 中的 Config 实例的状态发生变化,则在关闭之前将所有 Config 实例保存到缓存文件中。 +- 如果有缓存可用,则在 CodeIgniter 初始化之前恢复缓存的 Config 实例。 + +简而言之,Factories 持有的所有 Config 实例在关闭之前都会被缓存,并且缓存的实例将永久使用。 + +如何更新配置值 +=========================== + +一旦存储,缓存的版本将永不过期。更改现有的 Config 文件(或更改其环境变量)不会更新缓存或 Config 值。 + +因此,如果要更新 Config 值,请更新 Config 文件或其环境变量,并且必须手动删除缓存文件。 + +你可以使用 ``spark cache:clear`` 命令: + +.. code-block:: console + + php spark cache:clear + +或者直接删除 **writable/cache/FactoriesCache_config** 文件。 + +如何启用配置缓存 +============================ + +取消 **public/index.php** 中以下代码的注释:: + + --- a/public/index.php + +++ b/public/index.php + @@ -49,8 +49,8 @@ if (! defined('ENVIRONMENT')) { + } + + // Load Config Cache + -// $factoriesCache = new \CodeIgniter\Cache\FactoriesCache(); + -// $factoriesCache->load('config'); + +$factoriesCache = new \CodeIgniter\Cache\FactoriesCache(); + +$factoriesCache->load('config'); + // ^^^ Uncomment these lines if you want to use Config Caching. + + /* + @@ -79,7 +79,7 @@ $app->setContext($context); + $app->run(); + + // Save Config Cache + -// $factoriesCache->save('config'); + +$factoriesCache->save('config'); + // ^^^ Uncomment this line if you want to use Config Caching. + + // Exits the application, setting the exit code for CLI-based applications diff --git a/source/concepts/factories/002.php b/source/concepts/factories/002.php index 6a56bbe7a..6de5996e7 100644 --- a/source/concepts/factories/002.php +++ b/source/concepts/factories/002.php @@ -1,3 +1,5 @@ ` 提供输入字段过滤和内容元数据 -- 验证库 +- :ref:`invalidchars` 过滤器 +- :doc:`../libraries/validation` 库 +- :doc:`HTTP 库 <../incoming/incomingrequest>` 提供了 :ref:`输入字段过滤 ` 和内容元数据的功能 ********************************************* A2 弱认证和会话管理 @@ -57,6 +58,7 @@ CodeIgniter 对应措施 - :doc:`会话 <../libraries/sessions>` 库 - :doc:`安全 ` 库提供 CSRF 验证 +- 一个官方的身份验证和授权框架 :ref:`CodeIgniter Shield ` - 易于添加第三方认证 ***************************** @@ -75,8 +77,9 @@ OWASP 建议 CodeIgniter 对应措施 ====================== -- esc 函数 -- 验证库 +- :php:func:`esc()` 函数 +- :doc:`../libraries/validation` 库 +- 支持 :ref:`content-security-policy` *********************************** A4 不安全的直接对象引用 @@ -95,7 +98,8 @@ OWASP 建议 CodeIgniter 对应措施 ====================== -- 验证库 +- :doc:`../libraries/validation` 库 +- 一个官方的身份验证和授权框架 :ref:`CodeIgniter Shield ` - 易于添加第三方认证 **************************** @@ -132,7 +136,10 @@ OWASP 建议 CodeIgniter 对应措施 ====================== -- 会话密钥以加密形式存储 +- 全局安全访问的配置(``Config\App::$forceGlobalSecureRequests``) +- :php:func:`force_https()` 函数 +- :doc:`../libraries/encryption` +- :ref:`数据库配置 ` (``encrypt``) **************************************** A7 缺少功能级访问控制 @@ -150,8 +157,8 @@ OWASP 建议 CodeIgniter 对应措施 ====================== -- 公共文件夹,应用程序和系统在外 -- :doc:`安全库 ` 提供 CSRF 验证 +- :ref:`Public ` 文件夹,其中应用程序和系统位于外部 +- :doc:`安全库 ` 提供了 :ref:`CSRF 验证 ` 的功能 ************************************ A8 跨站请求伪造(CSRF) @@ -169,7 +176,7 @@ OWASP 建议 CodeIgniter 对应措施 ====================== -- :doc:`安全库 ` 提供 CSRF 验证 +- :doc:`安全库 ` 提供了 :ref:`CSRF 验证 ` 的功能 ********************************************** A9 使用已知漏洞的组件 @@ -204,4 +211,4 @@ CodeIgniter 对应措施 ====================== - :doc:`HTTP 库 <../incoming/incomingrequest>` 提供... -- :doc:`Session 库 <../libraries/sessions>` 提供临时数据 +- :doc:`Session 库 <../libraries/sessions>` 提供 :ref:`sessions-flashdata` diff --git a/source/concepts/structure.rst b/source/concepts/structure.rst index 221699c65..3d6abef24 100755 --- a/source/concepts/structure.rst +++ b/source/concepts/structure.rst @@ -48,6 +48,8 @@ system 此目录下的所有文件位于 ``CodeIgniter`` 命名空间下。 +.. _application-structure-public: + public ====== diff --git a/source/conf.py b/source/conf.py index 640a4a944..1bdc0ec7b 100755 --- a/source/conf.py +++ b/source/conf.py @@ -23,10 +23,10 @@ copyright = '2019-' + str(year_now) + ' CodeIgniter 基金会' # The short X.Y version. -version = '4.3' +version = '4.4' # The full version, including alpha/beta/rc tags. -release = '4.3.6' +release = '4.4.2' # -- General configuration --------------------------------------------------- diff --git a/source/database/configuration.rst b/source/database/configuration.rst index c9e2d1e75..dcdd9237a 100755 --- a/source/database/configuration.rst +++ b/source/database/configuration.rst @@ -83,6 +83,8 @@ DSN .. literalinclude:: configuration/008.php +.. _database-config-with-env-file: + ************************** 使用 .env 文件配置 ************************** @@ -105,48 +107,51 @@ DSN .. literalinclude:: configuration/009.php +.. _database-config-explanation-of-values: + ********************** 值的解释: ********************** -=============== =========================================================================================================== +================ =========================================================================================================== 名称 描述 -=============== =========================================================================================================== -**DSN** DSN 连接字符串(一体化配置序列)。 -**hostname** 数据库服务器的主机名。通常是 'localhost'。 -**username** 用于连接数据库的用户名。(``SQLite3`` 不使用此用户名) -**password** 用于连接数据库的密码。(``SQLite3`` 不使用此密码) -**database** 要连接的数据库名称。 - - .. note:: CodeIgniter 不支持数据库、表格和列名称中的点(``.``)。 -**DBDriver** 数据库驱动名称。驱动名称区分大小写。 - 你可以设置完全限定的类名以使用自定义驱动。 - 支持的驱动:``MySQLi``、``Postgre``、``SQLite3``、``SQLSRV`` 和 ``OCI8``。 -**DBPrefix** 可选的表前缀,在运行时会添加到表名中:doc:`查询构造器 ` 查询。这允许多个 CodeIgniter 安装共享一个数据库。 -**pConnect** true/false (布尔值)- 是否使用持久连接。 -**DBDebug** true/false (布尔值)- 数据库错误发生时是否抛出异常。 -**charset** 与数据库通信使用的字符集。 -**DBCollat** 与数据库通信使用的字符整理(``MySQLi`` 仅)。 -**swapPre** 一个默认的表前缀,应该与 ``DBPrefix`` 互换。这对于分布式应用程序很有用,在那里你可能运行手动编写的查询,并需要最终用户仍可自定义前缀。 -**schema** 数据库模式,默认值因驱动而异。(``Postgre`` 和 ``SQLSRV`` 使用。) -**encrypt** 是否使用加密连接。 - ``SQLSRV`` 驱动程序接受 true/false - ``MySQLi`` 驱动程序接受带有以下选项的数组: - * ``ssl_key`` - 私钥文件的路径 - * ``ssl_cert`` - 公钥证书文件的路径 - * ``ssl_ca`` - 证书颁发机构文件的路径 - * ``ssl_capath`` - 包含 PEM 格式可信 CA 证书的目录的路径 - * ``ssl_cipher`` - 用冒号 (``:``) 分隔的允许使用的加密的列表 - * ``ssl_verify`` - true/false; 是否验证服务器证书 (``MySQLi`` 仅) -**compress** 是否使用客户端压缩(``MySQLi`` 仅)。 -**strictOn** true/false (布尔值)- 是否强制“严格模式”连接,有助于开发应用程序时确保严格的 SQL (``MySQLi`` 仅)。 -**port** 数据库端口号。 -**foreignKeys** true/false (布尔值)- 是否启用外键约束(``SQLite3`` 仅)。 - - .. important:: SQLite3 外键约束默认关闭。 - 请参阅 `SQLite 文档 `_。 - 要实施外键约束,请将此配置项设置为 true。 -**busyTimeout** 毫秒(int) - 表锁定时休眠指定时间(``SQLite3`` 仅)。 -=============== =========================================================================================================== +================ =========================================================================================================== +**DSN** DSN 连接字符串(一体化配置序列)。 +**hostname** 数据库服务器的主机名。通常是 'localhost'。 +**username** 用于连接数据库的用户名。(``SQLite3`` 不使用此用户名) +**password** 用于连接数据库的密码。(``SQLite3`` 不使用此密码) +**database** 要连接的数据库名称。 + + .. note:: CodeIgniter 不支持数据库、表格和列名称中的点(``.``)。 +**DBDriver** 数据库驱动名称。驱动名称区分大小写。 + 你可以设置完全限定的类名以使用自定义驱动。 + 支持的驱动:``MySQLi``、``Postgre``、``SQLite3``、``SQLSRV`` 和 ``OCI8``。 +**DBPrefix** 可选的表前缀,在运行时会添加到表名中:doc:`查询构造器 ` 查询。这允许多个 CodeIgniter 安装共享一个数据库。 +**pConnect** true/false (布尔值)- 是否使用持久连接。 +**DBDebug** true/false (布尔值)- 数据库错误发生时是否抛出异常。 +**charset** 与数据库通信使用的字符集。 +**DBCollat** 与数据库通信使用的字符整理(``MySQLi`` 仅)。 +**swapPre** 一个默认的表前缀,应该与 ``DBPrefix`` 互换。这对于分布式应用程序很有用,在那里你可能运行手动编写的查询,并需要最终用户仍可自定义前缀。 +**schema** 数据库模式,默认值因驱动而异。(``Postgre`` 和 ``SQLSRV`` 使用。) +**encrypt** 是否使用加密连接。 + ``SQLSRV`` 驱动程序接受 true/false + ``MySQLi`` 驱动程序接受带有以下选项的数组: + * ``ssl_key`` - 私钥文件的路径 + * ``ssl_cert`` - 公钥证书文件的路径 + * ``ssl_ca`` - 证书颁发机构文件的路径 + * ``ssl_capath`` - 包含 PEM 格式可信 CA 证书的目录的路径 + * ``ssl_cipher`` - 用冒号 (``:``) 分隔的允许使用的加密的列表 + * ``ssl_verify`` - true/false; 是否验证服务器证书 (``MySQLi`` 仅) +**compress** 是否使用客户端压缩(``MySQLi`` 仅)。 +**strictOn** true/false (布尔值)- 是否强制“严格模式”连接,有助于开发应用程序时确保严格的 SQL (``MySQLi`` 仅)。 +**port** 数据库端口号 - 默认端口为空字符串 ``''`` (或使用 ``SQLSRV`` 动态端口)。 +**foreignKeys** true/false (布尔值)- 是否启用外键约束(``SQLite3`` 仅)。 + + .. important:: SQLite3 外键约束默认关闭。 + 请参阅 `SQLite 文档 `_。 + 要实施外键约束,请将此配置项设置为 true。 +**busyTimeout** 毫秒(int) - 表锁定时休眠指定时间(``SQLite3`` 仅)。 +**numberNative** true/false (布尔值) - 是否启用 MYSQLI_OPT_INT_AND_FLOAT_NATIVE(仅适用于 ``MySQLi``)。 +================ =========================================================================================================== .. note:: 根据你使用的数据库驱动程序(``MySQLi``、``Postgre`` 等),并非所有的值都是必需的。例如,在使用 ``SQLite3`` 时,你不需要提供用户名或密码,数据库名称将是数据库文件的路径。 diff --git a/source/database/configuration/008.php b/source/database/configuration/008.php index 5639c484d..9073ee71f 100644 --- a/source/database/configuration/008.php +++ b/source/database/configuration/008.php @@ -4,14 +4,20 @@ use CodeIgniter\Database\Config; +/** + * Database Configuration + */ class Database extends Config { + // ... public $development = [/* ... */]; public $test = [/* ... */]; public $production = [/* ... */]; public function __construct() { + // ... + $this->defaultGroup = ENVIRONMENT; } } diff --git a/source/database/metadata.rst b/source/database/metadata.rst index bdd9d6e62..6453aadad 100755 --- a/source/database/metadata.rst +++ b/source/database/metadata.rst @@ -71,6 +71,8 @@ $db->fieldExists() 检索字段元数据 ======================= +.. _db-metadata-getfielddata: + $db->getFieldData() ------------------- @@ -94,7 +96,7 @@ $db->getFieldData() - type - 列的类型 - max_length - 列的最大长度 - primary_key - 如果列是主键,则为整数 ``1`` (即使有多个主键,也全部为整数 ``1``),否则为整数 ``0`` (此字段当前仅适用于 MySQL 和 SQLite3) -- nullable - 如果列可为空,则为布尔值 ``true``,否则为布尔值 ``false`` (此字段当前在 SQL Server 中不可用) +- nullable - 如果列可为空,则为布尔值 ``true``,否则为布尔值 ``false`` - default - 默认值 列出表中的索引 diff --git a/source/database/queries.rst b/source/database/queries.rst index 579cbd7b4..125efec37 100755 --- a/source/database/queries.rst +++ b/source/database/queries.rst @@ -15,11 +15,16 @@ 常规查询 =============== -要提交查询,请使用 **query** 函数: +.. _db-query: + +$db->query() +------------ + +要提交查询,请使用 ``query()`` 方法: .. literalinclude:: queries/001.php -当运行“读取”类型查询时, ``query()`` 函数会返回一个可以用来 :doc:`显示结果 ` 的数据库结果 **对象**。当运行“写入”类型查询时,它根据成功或失败简单地返回 true 或 false。检索数据时,你通常会将查询分配给自己的变量,如下所示: +当运行“读取”类型查询时, ``query()`` 方法会返回一个可以用来 :doc:`显示结果 ` 的数据库结果 **对象**。当运行“写入”类型查询时,它根据成功或失败简单地返回 true 或 false。检索数据时,你通常会将查询分配给自己的变量,如下所示: .. literalinclude:: queries/002.php @@ -28,6 +33,11 @@ 简化的查询 ================== +.. _db-simplequery: + +$db->simpleQuery() +------------------ + ``simpleQuery()`` 方法是 ``$db->query()`` 方法的简化版本。 它不会返回数据库结果集,也不会设置查询计时器或编译绑定数据或存储调试查询。它只是让你提交一个查询。大多数用户很少使用此功能。 diff --git a/source/dbmgmt/db_commands.rst b/source/dbmgmt/db_commands.rst index 14d96b244..92de5b7fb 100644 --- a/source/dbmgmt/db_commands.rst +++ b/source/dbmgmt/db_commands.rst @@ -18,9 +18,11 @@ CodeIgniter 提供了一些简单的数据库管理命令。 db:table --show --------------- -要直接从喜欢的终端列出数据库中的所有表,可以使用 ``db:table --show`` 命令:: +要直接从喜欢的终端列出数据库中的所有表,可以使用 ``db:table --show`` 命令: - > php spark db:table --show +.. code-block:: console + + php spark db:table --show 使用此命令时,假定表存在。否则,CodeIgniter 将抱怨数据库没有表。 @@ -30,21 +32,27 @@ db:table --show db:table -------- -当你有一个名为 ``my_table`` 的表时,你可以看到表的字段名和记录:: +当你有一个名为 ``my_table`` 的表时,你可以看到表的字段名和记录: + +.. code-block:: console - > php spark db:table my_table + php spark db:table my_table 如果数据库中没有表 ``my_table``,CodeIgniter 会显示可用表列表以供选择。 -你也可以不使用表名使用以下命令:: +你也可以不使用表名使用以下命令: + +.. code-block:: console - > php spark db:table + php spark db:table 在这种情况下,将询问表名。 -你还可以传递一些选项:: +你还可以传递一些选项: - > php spark db:table my_table --limit-rows 50 --limit-field-value 20 --desc +.. code-block:: console + + php spark db:table my_table --limit-rows 50 --limit-field-value 20 --desc 选项 ``--limit-rows 50`` 将行数限制为 50 行。 @@ -58,9 +66,11 @@ db:table db:table --metadata ------------------- -当你有一个名为 ``my_table`` 的表时,你可以使用 ``--metadata`` 选项查看元数据,如列类型、表的最大长度:: +当你有一个名为 ``my_table`` 的表时,你可以使用 ``--metadata`` 选项查看元数据,如列类型、表的最大长度: + +.. code-block:: console - > php spark db:table my_table --metadata + php spark db:table my_table --metadata 使用此命令时,假定表存在。否则,CodeIgniter 将显示表列表以供选择。 此外,你可以将此命令用作 ``db:table --metadata``。 diff --git a/source/dbmgmt/forge.rst b/source/dbmgmt/forge.rst index 582152509..b8f992fac 100644 --- a/source/dbmgmt/forge.rst +++ b/source/dbmgmt/forge.rst @@ -51,18 +51,23 @@ $forge->dropDatabase('db_name') CodeIgniter 支持直接从喜欢的终端使用专用的 ``db:create`` 命令创建数据库。通过使用此命令,假定数据库还不存在。否则,CodeIgniter 将抱怨数据库创建失败。 -首先,只需键入命令和数据库名称(例如 ``foo``):: +首先,只需键入命令和数据库名称(例如 ``foo``): - > php spark db:create foo +.. code-block:: console + + php spark db:create foo 如果一切顺利,你应该会看到显示的 ``Database "foo" successfully created.`` 消息。 如果你在测试环境中或正在使用 SQLite3 驱动程序,可以使用 ``--ext`` 选项 为将创建数据库的文件传递文件扩展名。有效值为 ``db`` 和 ``sqlite``,默认为 ``db``。请记住,这些前面不应有句点。 -:: +: + +.. code-block:: console + + php spark db:create foo --ext sqlite - > php spark db:create foo --ext sqlite - // 将在 WRITEPATH/foo.sqlite 中创建 db 文件 +上述命令将创建名为 **WRITEPATH/foo.sqlite** 的数据库文件。 .. note:: 当使用特殊的 SQLite3 数据库名称 ``:memory:`` 时,请注意命令仍会生成成功消息,但不会创建数据库文件。这是因为 SQLite3 将只使用内存中的数据库。 diff --git a/source/dbmgmt/migration.rst b/source/dbmgmt/migration.rst index a3a255ec0..76d928d58 100644 --- a/source/dbmgmt/migration.rst +++ b/source/dbmgmt/migration.rst @@ -4,7 +4,7 @@ 迁移是一种以结构化和有序的方式修改数据库的便捷方法。你可以手工编辑 SQL 片段,但这样你就需要告知其他开发者他们需要运行这些片段。你也需要在下次部署到生产环境时跟踪哪些更改需要运行。 -数据库表 **migrations** 记录了哪些迁移已经运行,所以你只需要确保你的迁移就位,然后调用 ``$migration->latest()`` 就可以将数据库状态更新到最新。你也可以使用 ``$migration->setNamespace(null)->latest()`` 来包含所有命名空间的迁移。 +数据库表 **migrations** 用于跟踪已经运行的迁移,因此你只需确保你的迁移文件已经准备好,并运行 ``spark migrate`` 命令将数据库更新到最新状态。你还可以使用 ``spark migrate --all`` 命令来包括所有命名空间的迁移。 .. contents:: :local: @@ -46,7 +46,11 @@ 数据库组 =============== -迁移只会针对单个数据库组运行。如果你在 **app/Config/Database.php** 中定义了多个组,那么它将针对那里指定的 ``$defaultGroup`` 运行。有时你可能需要为不同的数据库组使用不同的模式。也许你有一个数据库用于所有常规站点信息,而另一个数据库用于业务关键的数据。你可以通过在迁移上设置 ``$DBGroup`` 属性来确保迁移只针对适当的组运行。此名称必须与数据库组的名称完全匹配: +迁移只会针对单个数据库组运行。如果你在 **app/Config/Database.php** 中定义了多个组,那么它将针对那里指定的 ``$defaultGroup`` 运行。 + +有时你可能需要为不同的数据库组使用不同的模式。也许你有一个数据库用于所有常规站点信息,而另一个数据库用于业务关键的数据。 + +你可以通过在迁移上设置 ``$DBGroup`` 属性来确保迁移只针对适当的组运行。此名称必须与数据库组的名称完全匹配: .. literalinclude:: migration/003.php @@ -63,78 +67,86 @@ 这将查找 **APPPATH/Database/Migrations** 和 **ROOTPATH/MyCompany/Database/Migrations** 中的任何迁移。这使得在你的可重用、模块化代码套件中包含迁移变得很简单。 -************* -使用示例 -************* - -在这个示例中,一些简单的代码被放置在 **app/Controllers/Migrate.php** 中以更新 schema: - -.. literalinclude:: migration/005.php - .. _command-line-tools: ******************* 命令行工具 ******************* -CodeIgniter 自带了几个 :doc:`commands `,可通过命令行访问,以帮助你使用迁移。这些工具不是使用迁移的必需品,但对于那些希望使用它们的人来说可能会更方便。这些工具主要提供了 MigrationRunner 类中可用的相同方法的访问。 +CodeIgniter 自带了几个 :doc:`commands `,可通过命令行访问,以帮助你使用迁移。这些工具使得使用迁移更加方便。这些工具主要提供了 MigrationRunner 类中可用的相同方法的访问。 migrate ======= -使用所有可用的迁移迁移一个数据库组:: +使用所有可用的迁移迁移一个数据库组: - > php spark migrate +.. code-block:: console + + php spark migrate 你可以对 (migrate) 使用以下选项: -- ``-g`` - 选择数据库组,否则将使用默认数据库组。 -- ``-n`` - 选择命名空间,否则将使用 (App) 命名空间。 +- ``-g`` - 用于指定数据库组。如果指定了该选项,只会运行指定数据库组的迁移。如果未指定,则会运行所有迁移。 +- ``-n`` - 用于选择命名空间,否则将使用 ``App`` 命名空间。 - ``--all`` - 迁移所有命名空间到最新的迁移。 -这个例子将在 test 数据库组上使用任何新的迁移迁移 ``Acme\Blog`` 命名空间:: +这个例子将在 test 数据库组上使用任何新的迁移迁移 ``Acme\Blog`` 命名空间: + +For Unix: + +.. code-block:: console + + php spark migrate -g test -n Acme\\Blog + +For Windows: - For Unix: - > php spark migrate -g test -n Acme\\Blog +.. code-block:: console - For Windows: - > php spark migrate -g test -n Acme\Blog + php spark migrate -g test -n Acme\Blog 当使用 ``--all`` 选项时,它将扫描所有命名空间,尝试找到任何未运行的迁移。这些迁移将一起收集,然后按创建日期排序为一组。这应该有助于最大限度地减少主应用程序和任何模块之间的潜在冲突。 rollback ======== -回滚所有迁移,将数据库组还原到空白状态,有效迁移到 0:: +回滚所有迁移到空白状态,有效迁移到 0: - > php spark migrate:rollback +.. code-block:: console + + php spark migrate:rollback 你可以对 (rollback) 使用以下选项: -- ``-g`` - 选择数据库组,否则将使用默认数据库组。 - ``-b`` - 选择批次:自然数指定批次。 - ``-f`` - 强制绕过确认问题,它仅在生产环境中询问。 refresh ======= -首先回滚所有迁移,然后迁移所有来刷新数据库状态:: +首先回滚所有迁移,然后迁移所有来刷新数据库状态: + +.. code-block:: console - > php spark migrate:refresh + php spark migrate:refresh 你可以对 (refresh) 使用以下选项: -- ``-g`` - 选择数据库组,否则将使用默认数据库组。 -- ``-n`` - 选择命名空间,否则将使用 (App) 命名空间。 +- ``-g`` - 用于指定数据库组。如果指定了该选项,只会运行指定数据库组的迁移。如果未指定,则会运行所有迁移。 +- ``-n`` - 用于选择命名空间,否则将使用 ``App`` 命名空间。 - ``--all`` - 刷新所有命名空间。 - ``-f`` - 强制绕过确认问题,它仅在生产环境中询问。 status ====== -显示所有迁移的列表以及它们运行的日期和时间,如果未运行则显示 '--':: +显示所有迁移的列表以及它们运行的日期和时间,如果未运行则显示 '--': + +.. code-block:: console + + php spark migrate:status + + ... - > php spark migrate:status +----------------------+-------------------+-----------------------+---------+---------------------+-------+ | Namespace | Version | Filename | Group | Migrated On | Batch | +----------------------+-------------------+-----------------------+---------+---------------------+-------+ @@ -145,38 +157,41 @@ status 你可以对 (status) 使用以下选项: -- ``-g`` - 选择数据库组,否则将使用默认数据库组。 +- ``-g`` - 用于指定数据库组。如果指定了该选项,只会检查指定数据库组的迁移。如果未指定,则会检查所有迁移。 make:migration ============== -在 **app/Database/Migrations** 中创建一个骨架迁移文件。它会自动在文件名前加上当前时间戳。它创建的类名是文件名的帕斯卡大小写版本。 +在 **app/Database/Migrations** 中创建一个骨架迁移文件。它会自动在文件名前加上当前时间戳。它创建的类名是文件名的大驼峰版本。 -:: +.. code-block:: console - > php spark make:migration [options] + php spark make:migration [options] -你可以对 (make:migration) 使用以下选项: +你可以对 (``make:migration``) 使用以下选项: -- ``--session`` - 为数据库 sessions 生成迁移文件。 -- ``--table`` - 数据库 sessions 使用的表名。默认:``ci_sessions``。 -- ``--dbgroup`` - 数据库 sessions 使用的数据库组。默认:``default``。 -- ``--namespace`` - 设置根命名空间。默认:``APP_NAMESPACE``。 +- ``--namespace`` - 设置根命名空间。默认: ``APP_NAMESPACE``。 - ``--suffix`` - 在类名后追加组件标题。 +以下选项也可用于为数据库 Sessions 生成迁移文件: + +- ``--session`` - 为数据库 sessions 生成迁移文件。 +- ``--table`` - 数据库 sessions 使用的表名。默认: ``ci_sessions``。 +- ``--dbgroup`` - 数据库 sessions 使用的数据库组。默认: ``default``。 + ********************* 迁移配置 ********************* 下表列出了所有迁移的配置选项,在 **app/Config/Migrations.php** 中可用。 -========================== ====================== ========================== ============================================================= +========================== ====================== ========================== ================================================================================= 首选项 默认值 可选值 描述 -========================== ====================== ========================== ============================================================= +========================== ====================== ========================== ================================================================================= **enabled** true true / false 启用或禁用迁移。 -**table** migrations None 用于存储 schema 版本号的表名。 +**table** migrations None 用于存储 schema 版本号的表名。该表始终在默认数据库组(``$defaultGroup``)中创建。 **timestampFormat** Y-m-d-His\_ 创建迁移时使用的时间戳格式。 -========================== ====================== ========================== ============================================================= +========================== ====================== ========================== ================================================================================= *************** 类参考 @@ -191,7 +206,7 @@ make:migration :returns: 迁移文件数组 :rtype: array - 返回在 **path** 属性中找到的迁移文件名数组。 + 返回在 ``path`` 属性中找到的迁移文件名数组。 .. php:method:: latest($group) @@ -220,7 +235,7 @@ make:migration :returns: 成功则为 ``true``,失败则为 ``false`` :rtype: bool - 该方法强制单文件迁移,不考虑顺序或批次。基于它是否已经迁移来检测“up”或“down”方法。 + 该方法强制单文件迁移,不考虑顺序或批次。基于它是否已经迁移来检测 ``up()`` 或 ``down()`` 方法。 .. note:: 该方法仅建议用于测试,可能会导致数据一致性问题。 diff --git a/source/dbmgmt/migration/005.php b/source/dbmgmt/migration/005.php deleted file mode 100644 index e9b4c2ad3..000000000 --- a/source/dbmgmt/migration/005.php +++ /dev/null @@ -1,20 +0,0 @@ -latest(); - } catch (Throwable $e) { - // Do something with the error here... - } - } -} diff --git a/source/dbmgmt/seeds.rst b/source/dbmgmt/seeds.rst index 12be4fbe3..3cc63b33a 100755 --- a/source/dbmgmt/seeds.rst +++ b/source/dbmgmt/seeds.rst @@ -43,9 +43,11 @@ 命令行填充 ==================== -你也可以通过命令行作为迁移CLI工具的一部分从命令行填充数据,如果你不想创建一个专用的控制器:: +你也可以通过命令行作为迁移CLI工具的一部分从命令行填充数据,如果你不想创建一个专用的控制器: - > php spark db:seed TestSeeder +.. code-block:: console + + php spark db:seed TestSeeder ********************* 创建填充器文件 @@ -53,18 +55,25 @@ 使用命令行,你可以轻松生成填充器文件。 -:: +.. code-block:: console + + php spark make:seeder user --suffix + +上述命令将输出位于 **app/Database/Seeds** 目录下的 **UserSeeder.php** 文件。 + +你可以通过提供 ``--namespace`` 选项来指定填充器文件要存储的 ``root`` 命名空间: + +For Unix: + +.. code-block:: console - > php spark make:seeder user --suffix - // 输出: UserSeeder.php 文件位于 app/Database/Seeds 目录中。 + php spark make:seeder MySeeder --namespace Acme\\Blog -你可以通过提供 ``--namespace`` 选项来指定填充器文件要存储的 ``root`` 命名空间:: +For Windows: - For Unix: - > php spark make:seeder MySeeder --namespace Acme\\Blog +.. code-block:: console - For Windows: - > php spark make:seeder MySeeder --namespace Acme\Blog + php spark make:seeder MySeeder --namespace Acme\Blog 如果 ``Acme\Blog`` 映射到 **app/Blog** 目录,那么此命令将在 **app/Blog/Database/Seeds** 目录中生成 **MySeeder.php**。 diff --git a/source/extending/composer_packages.rst b/source/extending/composer_packages.rst new file mode 100644 index 000000000..0622217ac --- /dev/null +++ b/source/extending/composer_packages.rst @@ -0,0 +1,179 @@ +########################## +创建 Composer 包 +########################## + +你可以将你创建的 :doc:`../general/modules` 转换为 Composer 包,或者为 CodeIgniter 4 创建一个 Composer 包。 + +.. contents:: + :local: + :depth: 2 + +**************** +文件夹结构 +**************** + +下面是一个典型的 Composer 包的目录结构示例:: + + your-package-name/ + ├── .gitattributes + ├── .gitignore + ├── LICENSE + ├── README.md + ├── composer.json + ├── src/ + │ └── YourClass.php + └── tests/ + └── YourClassTest.php + +********************** +创建 composer.json +********************** + +在你的包目录的根目录中,创建一个 **composer.json** 文件。该文件定义了关于你的包及其依赖项的元数据。 + +使用 ``composer init`` 命令可以帮助你创建它。 + +例如,**composer.json** 可能如下所示:: + + { + "name": "your-vendor/your-package", + "description": "Your package description", + "type": "library", + "license": "MIT", + "autoload": { + "psr-4": { + "YourVendor\\YourPackage\\": "src/" + } + }, + "authors": [ + { + "name": "Your Name", + "email": "yourname@example.com" + } + ], + "require": { + // 在此处添加你的包所需的任何依赖项 + }, + "require-dev": { + // 在此处添加开发所需的任何依赖项(例如 PHPUnit) + } + } + +包名称 +============ + +``name`` 字段在这里非常重要。包名称通常以 "vendor-name/package-name" 的格式书写,全部小写。以下是一个常见的示例: + +- ``your-vendor-name``:标识供应商(包的创建者)的名称,例如你的姓名或组织名称。 +- ``your-package-name``:你正在创建的包的名称。 + +因此,为了使名称唯一以区分其它包,使其与其他包区分开是非常重要的,尤其是在发布时。 + +命名空间 +========= + +包名称决定了 ``autoload.psr4`` 中的供应商命名空间。 + +如果你的包名称是 ``your-vendor/your-package``,那么供应商命名空间必须是 ``YourVendor``。因此,你需要像下面这样编写:: + + "autoload": { + "psr-4": { + "YourVendor\\YourPackage\\": "src/" + } + } + +这个设置指示 Composer 自动加载你的包的源代码。 + +选择许可证 +================ + +如果你对开源许可证不熟悉,请参考 https://choosealicense.com/。许多 PHP 包,包括 CodeIgniter,使用 MIT 许可证。 + +*************************** +准备开发工具 +*************************** + +有许多工具可以帮助确保代码质量。因此,你应该使用它们。你可以使用 `CodeIgniter DevKit `_ 轻松安装和配置此类工具。 + +安装 DevKit +================= + +在你的包目录的根目录中,运行以下命令: + +.. code-block:: console + + composer config minimum-stability dev + composer config prefer-stable true + composer require --dev codeigniter4/devkit + +DevKit 安装了各种 Composer 包,帮助你进行开发,并在 **vendor/codeigniter4/devkit/src/Template** 中为它们安装了模板。将其中的文件复制到你的项目根目录,并根据你的需求进行编辑。 + +配置 Coding Standards Fixer +================================== + +DevKit 提供了基于 `PHP-CS-Fixer `_ 的 `CodeIgniter Coding Standard `_ 的 Coding Standards Fixer。 + +将 **vendor/codeigniter4/devkit/src/Template/.php-cs-fixer.dist.php** 复制到你的项目根目录。 + +为缓存文件创建 **build** 文件夹:: + + your-package-name/ + ├── .php-cs-fixer.dist.php + ├── build/ + +打开你的编辑器中的 **.php-cs-fixer.dist.php** 文件,并修复文件夹路径:: + + --- a/.php-cs-fixer.dist.php + +++ b/.php-cs-fixer.dist.php + @@ -7,7 +7,7 @@ use PhpCsFixer\Finder; + $finder = Finder::create() + ->files() + ->in([ + - __DIR__ . '/app/', + + __DIR__ . '/src/', + __DIR__ . '/tests/', + ]) + ->exclude([ + +完成后,你可以运行 Coding Standards Fixer: + +.. code-block:: console + + vendor/bin/php-cs-fixer fix --ansi --verbose --diff + +如果你在 **composer.json** 中添加了 ``scripts.cs-fix``,则可以使用 ``composer cs-fix`` 命令运行它:: + + { + // ... + }, + "scripts": { + "cs-fix": "php-cs-fixer fix --ansi --verbose --diff" + } + } + +************ +配置文件 +************ + +允许用户覆盖设置 +=================================== + +如果你的包有一个配置文件,并且你希望用户能够覆盖设置,可以使用 :php:func:`config()` 函数与短类名(例如 ``config('YourConfig')``)来调用配置文件。 + +然后,用户可以通过在 **app/Config** 中放置一个与短类名相同且扩展了包配置类的配置类(例如 ``YourVendor\YourPackage\Config\YourConfig``)来覆盖包配置。 + +在 app/Config 中覆盖设置 +================================= + +如果你需要在 **app/Config** 文件夹中覆盖或添加已知配置,可以使用 :ref:`Implicit Registrars `。 + +********** +参考资料 +********** + +我们已经发布了一些官方包。你可以在创建自己的包时使用这些包作为参考: + +- https://github.com/codeigniter4/shield +- https://github.com/codeigniter4/settings +- https://github.com/codeigniter4/tasks +- https://github.com/codeigniter4/cache diff --git a/source/extending/core_classes/003.php b/source/extending/core_classes/003.php index c726b71ea..9e89d87d4 100644 --- a/source/extending/core_classes/003.php +++ b/source/extending/core_classes/003.php @@ -2,9 +2,9 @@ namespace App\Libraries; -use CodeIgniter\Router\RouteCollection; +use CodeIgniter\Router\RouteCollection as BaseRouteCollection; -class RouteCollection extends RouteCollection +class RouteCollection extends BaseRouteCollection { // ... } diff --git a/source/extending/index.rst b/source/extending/index.rst index eb9be7c7e..e5b9ebafe 100644 --- a/source/extending/index.rst +++ b/source/extending/index.rst @@ -12,4 +12,5 @@ events basecontroller authentication + composer_packages contributing diff --git a/source/general/caching.rst b/source/general/caching.rst index debf734d4..c8dcab29f 100755 --- a/source/general/caching.rst +++ b/source/general/caching.rst @@ -13,10 +13,29 @@ CodeIgniter 允许你缓存网页以达到最大性能。 缓存是如何工作的? ====================== -缓存可以在每个页面的基础上启用,你可以设置页面保持缓存的时间长度,之后会刷新。当一个页面第一次加载时,该文件将使用当前配置的缓存引擎进行缓存。在后续页面加载中,会检索缓存文件并发送给请求用户的浏览器。如果已过期,它会在发送到浏览器前删除和刷新。 +可以按页面启用缓存,并设置页面在刷新之前应保持缓存的时间长度。当首次加载页面时,将使用当前配置的缓存引擎对页面进行缓存。在后续的页面加载中,将检索缓存并发送给浏览器。如果缓存已过期,将在发送给浏览器之前删除并刷新。 .. note:: Benchmark 标签不会被缓存,所以启用缓存后你仍可以查看页面加载速度。 +配置缓存 +=================== + +设置缓存引擎 +-------------------- + +在使用网页缓存之前,你必须通过编辑 **app/Config/Cache.php** 来设置缓存引擎。有关详细信息,请参阅 :ref:`libraries-caching-configuring-the-cache`。 + +设置 $cacheQueryString +------------------------- + +你可以使用 ``Config\Cache::$cacheQueryString`` 设置是否在生成缓存时包含查询字符串。 + +有效选项为: + +- ``false``: (默认)禁用。不考虑查询字符串;对于具有相同 URI 路径但不同查询字符串的请求,返回相同的缓存。 +- ``true``: 启用,考虑所有查询参数。请注意,这可能导致为同一页面反复生成大量缓存。 +- **array**: 启用,但仅考虑指定的查询参数列表。例如: ``['q', 'page']``。 + 启用缓存 ================ @@ -28,13 +47,11 @@ CodeIgniter 允许你缓存网页以达到最大性能。 上面的标签可以放在一个方法的任何位置。它的位置不会受到影响,所以将其放在你认为最合适的位置。一旦标签就位,你的页面就会开始被缓存。 -.. important:: 如果你改变了可能影响输出的配置选项,你必须手动删除缓存文件。 - -.. note:: 在可以写入缓存文件之前,你必须通过编辑 **app/Config/Cache.php** 来设置缓存引擎。 +.. important:: 如果你改变了可能影响输出的配置选项,你必须手动删除缓存。 删除缓存 =============== -如果你不再希望缓存一个文件,可以删除缓存标签,它就不会在过期时刷新。 +如果你不再希望缓存一个页面,可以删除缓存标签,它就不会在过期时刷新。 .. note:: 删除标签不会立即删除缓存。它必须正常过期。 diff --git a/source/general/common_functions.rst b/source/general/common_functions.rst index 7bbd7549d..b8ed1c316 100755 --- a/source/general/common_functions.rst +++ b/source/general/common_functions.rst @@ -27,6 +27,21 @@ CodeIgniter 提供了一些全局定义的函数和变量,在任何时候都可 .. literalinclude:: common_functions/001.php +.. php:function:: config(string $name[, bool $getShared = true]) + + :param string $name: 配置类名。 + :param bool $getShared: 是否返回共享实例。 + :returns: 配置实例。 + :rtype: object|null + + 从工厂获取配置实例的更简单方式。 + + 有关详细信息,请参阅 :ref:`Configuration ` 和 + :ref:`Factories `。 + + ``config()`` 在内部使用 ``Factories::config()``。 + 有关第一个参数 ``$name`` 的详细信息,请参阅 :ref:`factories-loading-class`。 + .. php:function:: cookie(string $name[, string $value = ''[, array $options = []]]) :param string $name: Cookie 名称 @@ -70,7 +85,7 @@ CodeIgniter 提供了一些全局定义的函数和变量,在任何时候都可 如果 $data 是字符串,则简单转义并返回它。如果 $data 是数组,则遍历它,转义每个键/值对的 'value'。 - 有效的 context 值:html、js、css、url、attr、raw + 有效的 context 值: ``html``, ``js``, ``css``, ``url``, ``attr``, ``raw`` .. php:function:: helper($filename) @@ -101,17 +116,17 @@ CodeIgniter 提供了一些全局定义的函数和变量,在任何时候都可 获取模型实例的更简单方法。 - ``model()`` 在内部使用 ``Factories::models()``。有关第一个参数 ``$name`` 的详细信息,请参阅 :ref:`factories-example`。 + ``model()`` 在内部使用 ``Factories::models()``。有关第一个参数 ``$name`` 的详细信息,请参阅 :ref:`factories-loading-class`。 另请参阅 :ref:`使用 CodeIgniter 的模型 `。 .. php:function:: old($key[, $default = null,[, $escape = 'html']]) :param string $key: 要检查的旧表单数据的名称 - :param mixed $default: 如果 $key 不存在,返回的默认值 - :param mixed $escape: `转义 <#esc>`_ 上下文或禁用它的 false + :param string|null $default: 如果 $key 不存在,返回的默认值 + :param false|string $escape: `转义 <#esc>`_ 上下文或设置 false 禁用它 :returns: 定义键的值或默认值 - :rtype: mixed + :rtype: array|string|null 提供了一种简单的方式来访问提交表单后的“旧输入数据”。 @@ -119,7 +134,7 @@ CodeIgniter 提供了一些全局定义的函数和变量,在任何时候都可 .. literalinclude:: common_functions/002.php -.. note:: 如果使用 :doc:`表单辅助器 `,则此功能已内置。只有在不使用表单辅助器时,才需要使用此函数。 +.. note:: 如果你在 :doc:`表单辅助函数 ` 中使用了 :php:func:`set_value()`、:php:func:`set_select()`、:php:func:`set_checkbox()` 和 :php:func:`set_radio()` 函数,这个功能已经内置了。只有在不使用表单辅助函数时才需要使用此函数。 .. php:function:: session([$key]) @@ -245,7 +260,9 @@ CodeIgniter 提供了一些全局定义的函数和变量,在任何时候都可 :param RequestInterface $request: 当前 Request 对象的实例 :param ResponseInterface $response: 当前 Response 对象的实例 - 检查页面当前是否通过 HTTPS 访问。如果是,则什么都不做。如果不是,则将用户重定向回当前的 URI,但通过 HTTPS。将设置 HTTP 严格传输安全性标头,它指示现代浏览器自动将任何 HTTP 请求修改为 HTTPS 请求,持续时间为 $duration。 + 检查当前页面是否通过 HTTPS 访问。如果是,则不执行任何操作。如果不是,则将用户重定向回当前 URI,但通过 HTTPS 进行访问。将设置 HTTP 严格传输安全(HTST)头,指示现代浏览器将任何 HTTP 请求自动修改为 HTTPS 请求,持续时间为 ``$duration``。 + + .. note:: 当你将 ``Config\App:$forceGlobalSecureRequests`` 设置为 true 时,也会使用此函数。 .. php:function:: function_usable($function_name) diff --git a/source/general/common_functions/002.php b/source/general/common_functions/002.php index 1ade75d94..f00b1f816 100644 --- a/source/general/common_functions/002.php +++ b/source/general/common_functions/002.php @@ -2,8 +2,7 @@ // in controller, checking form submittal if (! $model->save($user)) { - // 'withInput' is what specifies "old data" - // should be saved. + // 'withInput()' is what specifies "old data" should be saved. return redirect()->back()->withInput(); } diff --git a/source/general/configuration.rst b/source/general/configuration.rst index 279bfa439..b33c4dca1 100755 --- a/source/general/configuration.rst +++ b/source/general/configuration.rst @@ -13,31 +13,51 @@ 使用配置文件 ********************************** +获取配置对象 +======================= + 你可以通过几种不同的方式访问类的配置文件。 -- 使用 ``new`` 关键字创建一个实例: +使用 new 关键字 +--------------- - .. literalinclude:: configuration/001.php +使用 ``new`` 关键字创建一个实例: -- 使用 ``config()`` 函数: +.. literalinclude:: configuration/001.php - .. literalinclude:: configuration/002.php +.. _configuration-config: -所有配置对象属性都是公共的,所以你可以像访问任何其他属性一样访问设置: +config() +-------- -.. literalinclude:: configuration/003.php +使用 :php:func:`config()` 函数: -如果没有提供命名空间,它将在所有定义的命名空间以及 **app/Config/** 中查找该文件。 +.. literalinclude:: configuration/002.php + +如果未提供命名空间,它将首先在 **app/Config** 文件夹中查找文件,如果找不到,则在所有定义的命名空间的 **Config** 文件夹中查找。 CodeIgniter 提供的所有配置文件都使用 ``Config`` 命名空间。在你的应用程序中使用这个命名空间将提供最佳性能,因为它确切知道在哪里可以找到这些文件。 你可以通过使用不同的命名空间将配置文件放在任何你想要的文件夹中。这允许你在生产服务器上将配置文件放在一个不可公开访问的文件夹中,同时在开发期间保持其位于 **/app** 下方便访问。 +.. note:: 在 v4.4.0 之前,``config()`` 会在有与 shortname 相同的类时,在 **app/Config/** 中查找文件,即使你指定了完全限定的类名,如 ``config(\Acme\Blog\Config\Blog::class)``。在 v4.4.0 中修复了此行为,并返回指定的实例。 + +获取配置属性 +========================= + +所有配置对象属性都是公共的,所以你可以像访问任何其他属性一样访问设置: + +.. literalinclude:: configuration/003.php + 创建配置文件 **************************** 当你需要一个新的配置时,首先在所需位置创建一个新文件。默认文件位置(大多数情况下推荐)是 **app/Config**。该类应使用适当的命名空间,并且它应扩展 ``CodeIgniter\Config\BaseConfig`` 以确保它可以接收特定环境的设置。 +你可以通过使用不同的命名空间将配置文件放置在任何 **Config** 文件夹中。 + +该类应使用适当的命名空间,并应扩展 ``CodeIgniter\Config\BaseConfig`` 以确保它可以接收特定于环境的设置。 + 定义类并用代表你的设置的公共属性填充它: .. literalinclude:: configuration/004.php @@ -85,7 +105,7 @@ CodeIgniter 期望 **.env** 文件与 **app** 目录一起位于项目的根目 .. literalinclude:: configuration/005.php -.. warning:: 请注意,来自 **.env** 文件的设置会添加到环境变量中。由此带来的一个副作用是,如果你的 CodeIgniter 应用程序生成一个 ``var_dump($_ENV)`` 或 ``phpinfo()`` (用于调试或其他有效原因) **你的安全凭据可能会公开暴露**。 +.. warning:: 请注意,来自 **.env** 文件的设置会添加到 ``$_SERVER`` and ``$_ENV`` 中。由此带来的一个副作用是,如果你的 CodeIgniter 应用程序生成一个 ``var_dump($_ENV)`` 或 ``phpinfo()`` (用于调试或其他有效原因) ,或者在 ``development`` 环境中显示了详细的错误报告,**你的安全凭据可能会公开暴露**。 嵌套变量 ----------------- @@ -138,6 +158,8 @@ CodeIgniter 期望 **.env** 文件与 **app** 目录一起位于项目的根目 .. important:: 你无法通过设置环境变量来添加新属性,也不能将标量值改变为数组。请参见 :ref:`env-var-replacements-for-data`。 +.. note:: 此功能是在 ``CodeIgniter\Config\BaseConfig`` 类中实现的。因此,它不适用于 **app/Config** 文件夹中的一些文件,这些文件不扩展该类。 + 如果命名空间变量的前缀正好匹配配置类的命名空间,那么设置的尾部(点之后)将被视为配置属性。如果它与现有的配置属性匹配,环境变量的值将替换配置文件中相应的值。如果没有匹配,配置类属性保持不变。在此用法中,前缀必须是类的完整(区分大小写)命名空间。 :: @@ -174,7 +196,7 @@ CodeIgniter 期望 **.env** 文件与 **app** 目录一起位于项目的根目 例如,你不能只是在 **.env** 中放置 ``app.myNewConfig = foo`` 并期望你的 ``Config\App`` 在运行时神奇地拥有该属性和值。 -当你在 ``Config\Database`` 中有属性 ``$default = ['encrypt' => false]`` 时,即使你在 **.env** 中放置 ``database.default.encrypt.ssl_verify = true``,也不能将 ``encrypt`` 值更改为数组。 +当你在 ``Config\Database`` 中有属性 ``$default = ['encrypt' => false]`` 时,即使你在 **.env** 中放置 ``database.default.encrypt.ssl_verify = true``,也不能将 ``encrypt`` 值更改为数组。如果你想这样做,请参阅 :ref:`Database Configuration `。 将环境变量视为数组 ======================================== @@ -218,33 +240,42 @@ CodeIgniter 期望 **.env** 文件与 **app** 目录一起位于项目的根目 .. _registrars: -注册表 +注册器 ********** -“注册表”是可以在命名空间和文件之间在运行时提供其他配置属性的任何其他类。 -注册表提供了一种在运行时跨命名空间和文件更改配置的方法。 -有两种实现注册表的方法:隐式和显式。 +“注册器”是可以在命名空间和文件之间在运行时提供其他配置属性的任何其他类。 +注册器提供了一种在运行时跨命名空间和文件更改配置的方法。 + +如果在 :doc:`模块 ` 中启用了 :ref:`auto-discovery`,则注册器可以在命名空间和文件之间在运行时更改配置属性。 -.. note:: 来自 **.env** 的值始终优先于注册表。 +有两种实现注册器的方法: **隐式** 和 **显式**。 -隐式注册表 +.. note:: 来自 **.env** 的值始终优先于注册器。 + +隐式注册器 =================== -如果在 :doc:`Modules ` 中启用了发现,任何命名空间都可以通过使用 **Config/Registrar.php** 文件来定义注册表。这些文件是方法名为你希望扩展的每个配置类的类。例如,第三方模块可能希望在不覆盖开发者已经配置的的情况下为 ``Pager`` 提供额外的模板。在 **src/Config/Registrar.php** 中将有一个 ``Registrar`` 类,其中只有单个 ``Pager()`` 方法(请注意大小写敏感性): +隐式注册器可以更改任何配置类的属性。 + +任何命名空间都可以通过使用 **Config/Registrar.php** 文件定义隐式注册器。这些文件是类,其方法的名称与你希望扩展的每个配置类的名称相同。 + +例如,第三方模块或 Composer 包可能希望为 ``Config\Pager`` 提供额外的模板,而不会覆盖开发人员已经配置的内容。在 **src/Config/Registrar.php** 中,将有一个名为 ``Registrar`` 的类,其中只有一个 ``Pager()`` 方法(注意大小写敏感): .. literalinclude:: configuration/007.php -注册方法必须始终返回一个数组,其中的键对应目标配置文件中的属性。存在的值被合并,注册表属性具有覆盖优先级。 +注册方法必须始终返回一个数组,其中的键对应目标配置文件中的属性。存在的值被合并,注册器属性具有覆盖优先级。 -显式注册表 +显式注册器 =================== -配置文件还可以显式指定任意数量的注册表。 -这是通过在配置文件中添加一个 ``$registrars`` 属性来完成的,其中包含候选注册表的名称数组: +显式注册器只能更改其注册的配置类属性。 + +配置文件还可以显式指定任意数量的注册器。 +这是通过在配置文件中添加一个 ``$registrars`` 属性来完成的,其中包含候选注册器的名称数组: .. literalinclude:: configuration/008.php -为了充当“注册表”,标识的类必须具有一个与配置类同名的静态函数,它应返回一个关联数组的属性设置。 +为了充当“注册器”,标识的类必须具有一个与配置类同名的静态函数,它应返回一个关联数组的属性设置。 在实例化配置对象时,它将循环遍历 ``$registrars`` 中指定的类。对于这些类中的每个类,它都会调用以配置类命名的方法,并合并任何返回的属性。 @@ -256,6 +287,6 @@ CodeIgniter 期望 **.env** 文件与 **app** 目录一起位于项目的根目 .. literalinclude:: configuration/010.php -通过上面的示例,在实例化 ``MySalesConfig`` 时,它将最终具有声明的三个属性,但 ``$target`` 属性的值将通过将 ``RegionalSales`` 视为“注册表”来覆盖。生成的配置属性: +通过上面的示例,在实例化 ``MySalesConfig`` 时,它将最终具有声明的三个属性,但 ``$target`` 属性的值将通过将 ``RegionalSales`` 视为“注册器”来覆盖。生成的配置属性: .. literalinclude:: configuration/011.php diff --git a/source/general/configuration/007.php b/source/general/configuration/007.php index 3a77c1ca5..5298a99b1 100644 --- a/source/general/configuration/007.php +++ b/source/general/configuration/007.php @@ -1,5 +1,7 @@ 45, diff --git a/source/general/environments.rst b/source/general/environments.rst index 9271f5d38..b06f615c6 100755 --- a/source/general/environments.rst +++ b/source/general/environments.rst @@ -46,9 +46,11 @@ ENVIRONMENT 常量 CI_ENVIRONMENT = development -.. note:: 你可以通过 ``spark env`` 命令更改 **.env** 文件中的 ``CI_ENVIRONMENT`` 值:: +.. note:: 你可以通过 ``spark env`` 命令更改 **.env** 文件中的 ``CI_ENVIRONMENT`` 值: - > php spark env production + .. code-block:: console + + php spark env production .. _environment-apache: @@ -112,9 +114,11 @@ CodeIgniter 要求与环境名称匹配的 PHP 脚本位于 **APPPATH/Config/Boo 要确认当前环境,只需打印常量 ``ENVIRONMENT``。 -你也可以通过 ``spark env`` 命令检查当前环境:: +你也可以通过 ``spark env`` 命令检查当前环境: + +.. code-block:: console - > php spark env + php spark env ************************************* 对默认框架行为的影响 diff --git a/source/general/errors.rst b/source/general/errors.rst index 0e6300c8b..00ca4b263 100755 --- a/source/general/errors.rst +++ b/source/general/errors.rst @@ -3,9 +3,10 @@ ############## CodeIgniter 通过 Exceptions 在你的系统中内置了错误报告,包括 -`SPL 集合 `_,以及框架提供的一些自定义 exceptions。 +`SPL 集合 `_,以及框架提供的一些 exceptions。 + 取决于你的环境设置,当抛出错误或异常时的默认操作是显示详细的错误报告,除非应用程序在 ``production`` 环境下运行。 -在这种情况下,会显示更通用的消息以对用户保持最佳体验。 +在 ``production`` 环境中,会显示更通用的消息以对用户保持最佳体验。 .. contents:: :local: @@ -37,10 +38,15 @@ Exceptions 简单来说就是在抛出异常时发生的事件。这将中止脚 配置 ============= -默认情况下,CodeIgniter 将在 ``development`` 和 ``testing`` 环境中显示所有错误,并且在 ``production`` 环境中不显示任何错误。你可以通过在 **.env** 文件中设置 ``CI_ENVIRONMENT`` 变量来更改此设置。 +错误报告 +--------------- + +默认情况下,CodeIgniter 将在 ``development`` 和 ``testing`` 环境中显示包含所有错误的详细错误报告,并且在 ``production`` 环境中不显示任何错误。你可以通过在 :ref:`.env ` 文件中设置 ``CI_ENVIRONMENT`` 变量来更改此设置。 .. important:: 禁用错误报告并不会停止在错误发生时写入日志。 +.. warning:: 请注意,**.env** 文件中的设置会添加到 ``$_SERVER`` 和 ``$_ENV`` 中。作为副作用,这意味着如果显示详细的错误报告,**你的安全凭据将被公开**。 + 记录 Exceptions ------------------ @@ -89,14 +95,20 @@ DatabaseException RedirectException ----------------- -此异常是一个特殊情况,允许覆盖所有其他响应路由并强制重定向到特定路由或 URL: +.. note:: 自 v4.4.0 起,``RedirectException`` 的命名空间已更改。之前是 ``CodeIgniter\Router\Exceptions\RedirectException``。之前的类已被弃用。 + +此异常是一个特殊情况,允许覆盖所有其他响应路由并强制重定向到特定的 URI: .. literalinclude:: errors/010.php -``$route`` 可以是命名路由、相对 URI 或完整 URL。你还可以提供要使用的重定向代码,而不是默认值(``302``、“临时重定向”): +``$uri`` 是相对于 baseURL 的 URI 路径。你还可以提供一个重定向代码,以替代默认值 (``302``, "temporary redirect"): .. literalinclude:: errors/011.php +另外,自 v4.4.0 版本开始,可以将实现了 ResponseInterface 接口的类的对象用作第一个参数。这种解决方案适用于需要在响应中添加额外的头部或 Cookie 的情况。 + +.. literalinclude:: errors/018.php + .. _error-specify-http-status-code: 在异常中指定 HTTP 状态码 @@ -145,3 +157,32 @@ RedirectException .. literalinclude:: errors/014.php 为了测试你的应用程序,你可能希望始终在弃用时抛出。你可以通过将环境变量 ``CODEIGNITER_SCREAM_DEPRECATIONS`` 设置为真值来配置此行为。 + +.. _custom-exception-handlers: + +自定义异常处理程序 +========================= + +.. versionadded:: 4.4.0 + +如果你需要更多地控制异常的显示方式,现在可以定义自己的处理程序并指定它们适用的情况。 + +定义新的处理程序 +------------------------ + +第一步是创建一个新的类,该类实现了 ``CodeIgniter\Debug\ExceptionHandlerInterface`` 接口。你还可以扩展 ``CodeIgniter\Debug\BaseExceptionHandler`` 类。该类包含了许多在默认异常处理程序中使用的实用方法。新的处理程序必须实现一个方法:``handle()``: + +.. literalinclude:: errors/015.php + +这个示例定义了通常需要的最少代码 - 显示一个视图并使用适当的退出代码退出。然而,``BaseExceptionHandler`` 提供了许多其他的辅助函数和对象。 + +配置新的处理程序 +--------------------------- + +告诉 CodeIgniter 使用你的新异常处理程序类是在 **app/Config/Exceptions.php** 配置文件的 ``handler()`` 方法中完成的: + +.. literalinclude:: errors/016.php + +你可以使用任何逻辑来确定应用程序是否应该处理异常,但最常见的两种情况是检查 HTTP 状态码或异常的类型。如果你的类应该处理它,则返回一个新的实例: + +.. literalinclude:: errors/017.php diff --git a/source/general/errors/010.php b/source/general/errors/010.php index ff9cfee97..dbe3736a4 100644 --- a/source/general/errors/010.php +++ b/source/general/errors/010.php @@ -1,3 +1,3 @@ render($exception, $statusCode, $this->viewPath . "error_{$statusCode}.php"); + + exit($exitCode); + } +} diff --git a/source/general/errors/016.php b/source/general/errors/016.php new file mode 100644 index 000000000..0fb61fa16 --- /dev/null +++ b/source/general/errors/016.php @@ -0,0 +1,18 @@ +redirect('https://example.com/path') + ->setHeader('Some', 'header') + ->setCookie('and', 'cookie'); + +throw new \CodeIgniter\HTTP\Exceptions\RedirectException($response); diff --git a/source/general/modules.rst b/source/general/modules.rst index 73d57430c..6faf59c25 100755 --- a/source/general/modules.rst +++ b/source/general/modules.rst @@ -2,15 +2,19 @@ 代码模块 ############ -CodeIgniter 支持一种代码模块化形式,以帮助你创建可重用的代码。模块通常围绕特定主题展开,可以认为是你更大的应用程序中的微型应用程序。框架支持的任何标准文件类型都受支持,如控制器、模型、视图、配置文件、辅助函数、语言文件等。模块可以包含尽可能少或多的这些文件。 +CodeIgniter 支持一种代码模块化形式,以帮助你创建可重用的代码。模块通常围绕特定主题展开,可以认为是你更大的应用程序中的微型应用程序。 + +框架支持的任何标准文件类型都受支持,如控制器、模型、视图、配置文件、辅助函数、语言文件等。模块可以包含尽可能少或多的这些文件。 + +如果你想将一个模块创建为 Composer 包,请参阅 :doc:`../extending/composer_packages`。 .. contents:: :local: :depth: 2 -========== +********** 命名空间 -========== +********** 模块功能的核心元素来自 CodeIgniter 使用的 :doc:`兼容 PSR-4 的自动加载 <../concepts/autoloader>`。虽然任何代码都可以使用 PSR-4 自动加载器和命名空间,但充分利用模块的主要方法是为你的代码添加命名空间并将其添加到 **app/Config/Autoload.php** 中的 ``$psr4`` 属性。 @@ -47,9 +51,9 @@ CodeIgniter 支持一种代码模块化形式,以帮助你创建可重用的代 当然,没有什么能强制你使用这个确切的结构,你应该以最适合模块的方式组织它,省略不需要的目录,为实体、接口或存储库等创建新目录。 -=========================== +*************************** 自动加载非类文件 -=========================== +*************************** 通常,你的模块不仅包含 PHP 类,还包含像程序函数、引导文件、模块常量文件等通常不会像加载类那样加载的文件。一种方法是在使用文件位置的开头 ``require`` 这些文件。 @@ -57,28 +61,30 @@ CodeIgniter 提供的另一种方法是像自动加载类一样自动加载这 .. literalinclude:: modules/002.php -============== +.. _auto-discovery: + +************** 自动发现 -============== +************** 通常,你需要指定要包含的文件的完全命名空间,但是可以通过自动发现许多不同类型的文件来配置 CodeIgniter,从而使将模块集成到应用程序中更简单,包括: - :doc:`Events <../extending/events>` - :doc:`Filters <../incoming/filters>` -- :doc:`Registrars <./configuration>` +- :ref:`registrars` - :doc:`Route files <../incoming/routing>` - :doc:`Services <../concepts/services>` 这在文件 **app/Config/Modules.php** 中配置。 -自动发现系统通过扫描在 **Config/Autoload.php** 中定义的 psr4 命名空间下的特定目录和文件来工作。 +自动发现系统通过扫描在 **Config/Autoload.php** 和 Composer 包中定义的 psr4 命名空间下的特定目录和文件来工作。 -例如,发现过程将在路径中查找可以发现的项,并应该找到 **/acme/Blog/Config/Routes.php** 中的 routes 文件。 +例如,发现过程将在路径中查找可以发现的项,并应该找到 **acme/Blog/Config/Routes.php** 中的 routes 文件。 启用/禁用发现 ======================= -你可以通过系统中的 ``$enabled`` 类变量打开或关闭所有自动发现。False 将禁用所有发现,优化性能,但会消除模块的特殊功能。 +你可以通过系统中的 ``$enabled`` 类变量打开或关闭所有自动发现。False 将禁用所有发现,优化性能,但会消除模块和 Composer 包的特殊功能。 指定要发现的项 ======================= @@ -112,9 +118,9 @@ CodeIgniter 提供的另一种方法是像自动加载类一样自动加载这 .. literalinclude:: modules/004.php -================== +****************** 使用文件 -================== +****************** 本节将查看每种文件类型(控制器、视图、语言文件等)以及如何在模块中使用它们。用户指南的相关位置已对其中一些信息进行了更详细的描述,但在此重复以更容易掌握所有部分的关系。 @@ -127,9 +133,17 @@ CodeIgniter 提供的另一种方法是像自动加载类一样自动加载这 使用模块时,如果应用程序中的路由包含通配符,这可能是一个问题。在这种情况下,请参阅 :ref:`routing-priority`。 +.. _modules-filters: + 过滤器 ======= +.. deprecated:: 4.4.2 + +.. note:: 此功能已被弃用。请改用 :ref:`registrars`,如下所示: + + .. literalinclude:: modules/015.php + 默认情况下,模块内会自动扫描 :doc:`过滤器 <../incoming/filters>`。可以在上面描述的 **Modules** 配置文件中将其关闭。 .. note:: 由于文件被包含到当前作用域中,因此 ``$filters`` 实例已为你定义。如果尝试重新定义该类,则会导致错误。 @@ -156,11 +170,11 @@ CodeIgniter 提供的另一种方法是像自动加载类一样自动加载这 .. literalinclude:: modules/008.php -每次使用始终可用的 ``config()`` 函数时,都会自动发现配置文件。 +无论何时使用始终可用的 :php:func:`config()` 函数,并将一个短类名传递给它,配置文件都会被自动发现。 -.. note:: 我们不建议在模块中使用相同的短类名。需要覆盖或添加 **app/Config/** 中已知配置的模块应使用 :ref:`registrars`。 +.. note:: 我们不建议在模块中使用相同的短类名。需要覆盖或添加 **app/Config/** 中已知配置的模块应使用 :ref:`Implicit Registrars `。 -.. note:: 当有一个相同短名称的类时,即使你指定了完全限定的类名(如 ``config(\Acme\Blog\Config\Blog::class)``), ``config()`` 也会在 **app/Config/** 中找到该文件。这是因为 ``config()`` 是 ``Factories`` 类的包装器,默认使用 ``preferApp``。有关更多信息,请参阅 :ref:`Factories 示例 `。 +.. note:: 在 v4.4.0 之前,即使你指定了一个完全限定的类名,如 ``config(\Acme\Blog\Config\Blog::class)``,``config()`` 仍会在 **app/Config/** 中查找文件,只要存在与短类名相同的类。在 v4.4.0 中修复了这个行为,并返回指定的实例。 迁移 ========== @@ -170,13 +184,19 @@ CodeIgniter 提供的另一种方法是像自动加载类一样自动加载这 种子 ===== -只要提供完全限定的命名空间,就可以从 CLI 和其他种子文件中调用种子文件。如果在 CLI 上调用,则需要提供双反斜杠:: +只要提供完全限定的命名空间,就可以从 CLI 和其他种子文件中调用种子文件。如果在 CLI 上调用,则需要提供双反斜杠: + +For Unix: + +.. code-block:: console + + php spark db:seed Acme\\Blog\\Database\\Seeds\\TestPostSeeder + +For Windows: - For Unix: - > php spark db:seed Acme\\Blog\\Database\\Seeds\\TestPostSeeder +.. code-block:: console - For Windows: - > php spark db:seed Acme\Blog\Database\Seeds\TestPostSeeder + php spark db:seed Acme\Blog\Database\Seeds\TestPostSeeder 辅助函数 ======== @@ -210,7 +230,7 @@ CodeIgniter 提供的另一种方法是像自动加载类一样自动加载这 .. note:: 我们不建议在模块中使用相同的短类名。 -.. note:: 当有一个相同短名称的类时,即使你指定了完全限定的类名(如 ``model(\Acme\Blog\Model\PostModel::class)``), ``model()`` 也会在 **app/Models/** 中找到该文件。这是因为 ``model()`` 是 ``Factories`` 类的包装器,默认使用 ``preferApp``。有关更多信息,请参阅 :ref:`Factories 示例 `。 +.. note:: 当有一个相同短名称的类时,即使你指定了完全限定的类名(如 ``model(\Acme\Blog\Model\PostModel::class)``), ``model()`` 也会在 **app/Models/** 中找到该文件。这是因为 ``model()`` 是 ``Factories`` 类的包装器,默认使用 ``preferApp``。有关更多信息,请参阅 :ref:`factories-loading-class`。 视图 ===== diff --git a/source/general/modules/015.php b/source/general/modules/015.php new file mode 100644 index 000000000..0b1f17508 --- /dev/null +++ b/source/general/modules/015.php @@ -0,0 +1,22 @@ + [ + 'session' => SessionAuth::class, + 'tokens' => TokenAuth::class, + ], + ]; + } +} diff --git a/source/general/urls.rst b/source/general/urls.rst index fb73e488e..dc691efbd 100755 --- a/source/general/urls.rst +++ b/source/general/urls.rst @@ -60,7 +60,7 @@ URI 路径 /ci-blog/blog/news/2022/10 删除 index.php 文件 =========================== -默认情况下, **index.php** 文件将包含在你的 URL 中:: +当你使用 Apache Web 服务器时,默认情况下,在你的 URL 中需要 **index.php** 文件:: example.com/index.php/news/article/my_article @@ -88,10 +88,10 @@ Apache 必须启用 *mod_rewrite* 扩展。如果是,你可以使用一些简单 .. _urls-remove-index-php-nginx: -NGINX +Nginx ----- -在 NGINX 中,你可以定义一个位置块并使用 ``try_files`` 指令来获取与我们在上面的 Apache 配置中相同的效果: +在 Nginx 中,你可以定义一个位置块并使用 ``try_files`` 指令来获取与我们在上面的 Apache 配置中相同的效果: .. code-block:: nginx diff --git a/source/helpers/array_helper.rst b/source/helpers/array_helper.rst index 249267f25..261f6d592 100644 --- a/source/helpers/array_helper.rst +++ b/source/helpers/array_helper.rst @@ -93,3 +93,25 @@ Array 辅助函数提供了几个函数来简化数组的更复杂用法。它 用户可以自己使用 ``$id`` 参数,但不需要这样做。该函数在内部使用此参数来跟踪展平后的键。如果用户将提供初始 ``$id``,它将添加到所有键前面。 .. literalinclude:: array_helper/011.php + +.. php:function:: array_group_by(array $array, array $indexes[, bool $includeEmpty = false]): array + + :param array $array: 数据行(很可能来自查询结果) + :param array $indexes: 要按索引值分组的索引。遵循点语法 + :param bool $includeEmpty: 如果为 true,则不过滤掉 ``null`` 和 ``''`` 值 + :rtype: array + :returns: 按索引值分组的数组 + + 该函数允许你按索引值将数据行分组在一起。返回的数组的深度等于作为参数传递的索引数。 + + 以下示例显示了一些数据(例如从 API 加载的数据)和嵌套数组。 + + .. literalinclude:: array_helper/012.php + + 我们首先想要按 "gender" 分组,然后按 "hr.department" 分组(最大深度为 2)。首先排除空值的结果如下: + + .. literalinclude:: array_helper/013.php + + 这里是相同的代码,但这次我们想要包括空值: + + .. literalinclude:: array_helper/014.php diff --git a/source/helpers/array_helper/012.php b/source/helpers/array_helper/012.php new file mode 100644 index 000000000..8aeb6d705 --- /dev/null +++ b/source/helpers/array_helper/012.php @@ -0,0 +1,94 @@ + 1, + 'first_name' => 'Urbano', + 'gender' => null, + 'hr' => [ + 'country' => 'Canada', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 2, + 'first_name' => 'Case', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Marketing', + ], + ], + [ + 'id' => 3, + 'first_name' => 'Emera', + 'gender' => 'Female', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 4, + 'first_name' => 'Richy', + 'gender' => null, + 'hr' => [ + 'country' => null, + 'department' => 'Sales', + ], + ], + [ + 'id' => 5, + 'first_name' => 'Mandy', + 'gender' => null, + 'hr' => [ + 'country' => 'France', + 'department' => 'Sales', + ], + ], + [ + 'id' => 6, + 'first_name' => 'Risa', + 'gender' => 'Female', + 'hr' => [ + 'country' => null, + 'department' => 'Engineering', + ], + ], + [ + 'id' => 7, + 'first_name' => 'Alfred', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 8, + 'first_name' => 'Tabby', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Marketing', + ], + ], + [ + 'id' => 9, + 'first_name' => 'Ario', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Sales', + ], + ], + [ + 'id' => 10, + 'first_name' => 'Somerset', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'Germany', + 'department' => 'Marketing', + ], + ], +]; diff --git a/source/helpers/array_helper/013.php b/source/helpers/array_helper/013.php new file mode 100644 index 000000000..5b7e722fc --- /dev/null +++ b/source/helpers/array_helper/013.php @@ -0,0 +1,81 @@ + [ + 'Marketing' => [ + [ + 'id' => 2, + 'first_name' => 'Case', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Marketing', + ], + ], + [ + 'id' => 8, + 'first_name' => 'Tabby', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Marketing', + ], + ], + [ + 'id' => 10, + 'first_name' => 'Somerset', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'Germany', + 'department' => 'Marketing', + ], + ], + ], + 'Engineering' => [ + [ + 'id' => 7, + 'first_name' => 'Alfred', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + ], + 'Sales' => [ + [ + 'id' => 9, + 'first_name' => 'Ario', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Sales', + ], + ], + ], + ], + 'Female' => [ + 'Engineering' => [ + [ + 'id' => 3, + 'first_name' => 'Emera', + 'gender' => 'Female', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 6, + 'first_name' => 'Risa', + 'gender' => 'Female', + 'hr' => [ + 'country' => null, + 'department' => 'Engineering', + ], + ], + ], + ], +]; diff --git a/source/helpers/array_helper/014.php b/source/helpers/array_helper/014.php new file mode 100644 index 000000000..99089d236 --- /dev/null +++ b/source/helpers/array_helper/014.php @@ -0,0 +1,114 @@ + [ + 'Engineering' => [ + [ + 'id' => 1, + 'first_name' => 'Urbano', + 'gender' => null, + 'hr' => [ + 'country' => 'Canada', + 'department' => 'Engineering', + ], + ], + ], + 'Sales' => [ + [ + 'id' => 4, + 'first_name' => 'Richy', + 'gender' => null, + 'hr' => [ + 'country' => null, + 'department' => 'Sales', + ], + ], + [ + 'id' => 5, + 'first_name' => 'Mandy', + 'gender' => null, + 'hr' => [ + 'country' => 'France', + 'department' => 'Sales', + ], + ], + ], + ], + 'Male' => [ + 'Marketing' => [ + [ + 'id' => 2, + 'first_name' => 'Case', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Marketing', + ], + ], + [ + 'id' => 8, + 'first_name' => 'Tabby', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Marketing', + ], + ], + [ + 'id' => 10, + 'first_name' => 'Somerset', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'Germany', + 'department' => 'Marketing', + ], + ], + ], + 'Engineering' => [ + [ + 'id' => 7, + 'first_name' => 'Alfred', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + ], + 'Sales' => [ + [ + 'id' => 9, + 'first_name' => 'Ario', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Sales', + ], + ], + ], + ], + 'Female' => [ + 'Engineering' => [ + [ + 'id' => 3, + 'first_name' => 'Emera', + 'gender' => 'Female', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 6, + 'first_name' => 'Risa', + 'gender' => 'Female', + 'hr' => [ + 'country' => null, + 'department' => 'Engineering', + ], + ], + ], + ], +]; diff --git a/source/helpers/cookie_helper.rst b/source/helpers/cookie_helper.rst index e8a039145..725a3de16 100755 --- a/source/helpers/cookie_helper.rst +++ b/source/helpers/cookie_helper.rst @@ -22,7 +22,7 @@ Cookie 辅助函数文件包含了帮助处理 cookie 的函数。 .. php:function:: set_cookie($name[, $value = ''[, $expire = ''[, $domain = ''[, $path = '/'[, $prefix = ''[, $secure = false[, $httpOnly = false[, $sameSite = '']]]]]]]]) - :param mixed $name: Cookie 名称 *或* 此函数可用的所有参数的关联数组 + :param array|Cookie|string $name: Cookie 名称 *或* 此函数可用的所有参数的关联数组 *或* ``CodeIgniter\Cookie\Cookie`` 的实例 :param string $value: Cookie 值 :param int $expire: 到期秒数。如果设置为 ``0`` 则 cookie 仅在浏览器打开时有效 :param string $domain: Cookie 域名(通常:.yourdomain.com) @@ -50,11 +50,9 @@ Cookie 辅助函数文件包含了帮助处理 cookie 的函数。 .. note:: 从 v4.2.1 开始,引入了第三个参数 ``$prefix``,并且由于一个错误修复,行为发生了一些变化。详见 :ref:`升级 `。 - 该辅助函数为获取浏览器 cookie 提供了更友好的语法。有关其使用的详细描述,请参阅 - :doc:`IncomingRequest 库 `,因为此函数的作用与 ``IncomingRequest::getCookie()`` - 非常相似,只是它还会在你可能在 **app/Config/Cookie.php** 文件中设置的 ``Config\Cookie::$prefix`` 前加上前缀。 + 这个辅助函数为你提供了更友好的语法来获取浏览器的 Cookie。有关其使用的详细描述,请参考 :doc:`IncomingRequest 库 `,因为这个函数的行为与 :php:meth:`CodeIgniter\\HTTP\\IncomingRequest::getCookie()` 非常相似,只是它还会在前面添加你在 **app/Config/Cookie.php** 文件中设置的 ``Config\Cookie::$prefix``。 - .. warning:: 使用 XSS 过滤是一个不好的做法。它不能完美地防止 XSS 攻击。在视图中建议使用正确 ``$context`` 的 ``esc()``。 + .. warning:: 使用 XSS 过滤是一个不好的做法。它不能完美地防止 XSS 攻击。在视图中建议使用正确 ``$context`` 的 :php:func:`esc()`。 .. php:function:: delete_cookie($name[, $domain = ''[, $path = '/'[, $prefix = '']]]) @@ -68,9 +66,9 @@ Cookie 辅助函数文件包含了帮助处理 cookie 的函数。 .. literalinclude:: cookie_helper/002.php - 此函数与 ``set_cookie()`` 其他方面相同,只是它没有 ``value`` 和 ``expire`` 参数。 + 此函数与 :php:func:`set_cookie()` 其他方面相同,只是它没有 ``value`` 和 ``expire`` 参数。 - .. note:: 当你使用 ``set_cookie()`` 时,如果 ``value`` 设置为空字符串且 ``expire`` 设置为 ``0``,则 cookie 将被删除。 + .. note:: 当你使用 :php:func:`set_cookie()` 时,如果 ``value`` 设置为空字符串且 ``expire`` 设置为 ``0``,则 cookie 将被删除。 如果 ``value`` 设置为非空字符串且 ``expire`` 设置为 ``0``,则 cookie 仅在浏览器打开时有效。 你可以在第一个参数中提交值数组,也可以设置离散参数。 diff --git a/source/helpers/form_helper.rst b/source/helpers/form_helper.rst index 8875ea40d..77dcd3bfc 100755 --- a/source/helpers/form_helper.rst +++ b/source/helpers/form_helper.rst @@ -436,8 +436,8 @@ 示例:: - > - > + > + > .. php:function:: set_radio($field[, $value = ''[, $default = false]]) @@ -465,6 +465,8 @@ 返回的数组与 ``Validation::getErrors()`` 相同。详见 :ref:`验证 `。 + .. note:: 此函数与 :ref:`in-model-validation` 不兼容。如果你想在模型验证中获取验证错误,请参阅 :ref:`model-getting-validation-errors`。 + 示例:: @@ -483,6 +485,8 @@ 此函数在内部使用 :php:func:`validation_errors()`。 + .. note:: 此函数与 :ref:`in-model-validation` 不兼容。如果你想在模型验证中获取验证错误,请参阅 :ref:`model-getting-validation-errors`。 + 示例:: @@ -502,6 +506,8 @@ 此函数在内部使用 :php:func:`validation_errors()`。 + .. note:: 此函数与 :ref:`in-model-validation` 不兼容。如果你想在模型验证中获取验证错误,请参阅 :ref:`model-getting-validation-errors`。 + 示例:: diff --git a/source/helpers/inflector_helper.rst b/source/helpers/inflector_helper.rst index 1b45ad9f7..2b003210a 100755 --- a/source/helpers/inflector_helper.rst +++ b/source/helpers/inflector_helper.rst @@ -89,7 +89,7 @@ Inflector 辅助函数文件包含了允许你将 **英语** 单词更改为复 获取多个驼峰或帕斯卡单词并将它们转换为下划线分隔的单词。示例: - .. literalinclude:: inflector_helper/007.php + .. literalinclude:: inflector_helper/014.php .. php:function:: humanize($string[, $separator = '_']) diff --git a/source/helpers/number_helper.rst b/source/helpers/number_helper.rst index 9462ce86c..2a9131763 100755 --- a/source/helpers/number_helper.rst +++ b/source/helpers/number_helper.rst @@ -83,7 +83,7 @@ .. php:function:: number_to_roman($num) - :param string $num: 要转换的数字 + :param int|string $num: 要转换的数字 :returns: 从给定参数转换后的罗马数字 :rtype: string|null @@ -91,4 +91,4 @@ .. literalinclude:: number_helper/009.php - 此函数仅处理 1 到 3999 范围内的数字。对于该范围之外的任何值,它都将返回 null。 + 此函数仅处理 1 到 3999 范围内的数字。对于该范围之外的任何值,它都将返回 ``null``。 diff --git a/source/helpers/number_helper/009.php b/source/helpers/number_helper/009.php index 0a520a198..a55daa265 100644 --- a/source/helpers/number_helper/009.php +++ b/source/helpers/number_helper/009.php @@ -1,5 +1,5 @@ validate() 为了简化数据检查,控制器还提供了方便的 ``validate()`` 方法。 该方法在第一个参数中接受规则数组,在可选的第二个参数中,接受自定义错误消息的数组,以在项目无效时显示。在内部,这使用控制器的 ``$this->request`` 实例来获取要验证的数据。 -.. warning:: - ``validate()`` 方法使用 :ref:`Validation::withRequest() ` 方法。 - 它会验证来自 :ref:`$request->getJSON() ` - 或 :ref:`$request->getRawInput() ` - 或 :ref:`$request->getVar() ` 的数据。 - 使用哪些数据取决于请求。请记住,攻击者可以自由地向服务器发送任何请求。 - :doc:`验证库文档 ` 有关于规则和消息数组格式以及可用规则的详细信息: .. literalinclude:: controllers/004.php +.. warning:: 当你使用 ``validate()`` 方法时,应该使用 :ref:`getValidated() ` 方法来获取经过验证的数据。因为 ``validate()`` 方法在内部使用了 :ref:`Validation::withRequest() ` 方法,并且它会验证来自 :ref:`$request->getJSON() `、:ref:`$request->getRawInput() ` 或 :ref:`$request->getVar() ` 的数据,而攻击者可能会更改要验证的数据。 + +.. note:: 自 v4.4.0 版本开始,可以使用 :ref:`$this->validator->getValidated() ` 方法。 + 如果你发现在配置文件中保持规则更简单,你可以用 **app/Config/Validation.php** 中定义的组名替换 ``$rules`` 数组: .. literalinclude:: controllers/005.php @@ -251,10 +248,6 @@ URI 的第二段通常确定控制器中的哪个方法被调用。 .. literalinclude:: controllers/022.php -.. important:: 如果 URI 中的参数比方法的参数更多, - 自动路由(改进版)不会执行该方法,并导致 404 - 未找到。 - 默认控制器 ================== @@ -282,6 +275,44 @@ URI 的第二段通常确定控制器中的哪个方法被调用。 有关更多信息,请参阅 :ref:`routes-configuration-options` 部分 :ref:`URI 路由 ` 文档。 +.. _controller-default-method-fallback: + +默认方法回退 +======================= + +.. versionadded:: 4.4.0 + +如果与方法名的 URI 段对应的控制器方法不存在,并且如果定义了默认方法,则剩余的 URI 段将传递给默认方法进行执行。 + +.. literalinclude:: controllers/024.php + +加载以下 URL:: + + example.com/index.php/product/15/edit + +该方法将传递 URI 段 2 和 3(``'15'`` 和 ``'edit'``): + +.. important:: 如果 URI 中的参数多于方法参数,自动路由(改进版)不会执行该方法,并返回 404 Not Found。 + +回退到默认控制器 +------------------------------ + +如果与控制器名的 URI 段对应的控制器不存在,并且如果默认控制器(默认为 ``Home``)存在于目录中,则剩余的 URI 段将传递给默认控制器的默认方法。 + +例如,当你在 **app/Controllers/News** 目录中有以下默认控制器 ``Home`` 时: + +.. literalinclude:: controllers/025.php + +加载以下 URL:: + + example.com/index.php/news/101 + +将找到 ``News\Home`` 控制器和默认的 ``getIndex()`` 方法。因此,默认方法将传递 URI 段 2(``'101'``): + +.. note:: 如果存在 ``App\Controllers\News`` 控制器,则它具有优先权。URI 段按顺序搜索,找到的第一个控制器将被使用。 + +.. note:: 如果 URI 中的参数多于方法参数,自动路由(改进版)不会执行该方法,并返回 404 Not Found。 + 将控制器组织到子目录中 ================================================ diff --git a/source/incoming/controllers/004.php b/source/incoming/controllers/004.php index 9216bab50..afdf0a145 100644 --- a/source/incoming/controllers/004.php +++ b/source/incoming/controllers/004.php @@ -10,11 +10,17 @@ public function updateUser(int $userID) 'email' => "required|is_unique[users.email,id,{$userID}]", 'name' => 'required|alpha_numeric_spaces', ])) { + // The validation failed. return view('users/update', [ 'errors' => $this->validator->getErrors(), ]); } - // do something here if successful... + // The validation was successful. + + // Get the validated data. + $validData = $this->validator->getValidated(); + + // ... } } diff --git a/source/incoming/controllers/005.php b/source/incoming/controllers/005.php index 48e936b60..341704373 100644 --- a/source/incoming/controllers/005.php +++ b/source/incoming/controllers/005.php @@ -7,11 +7,17 @@ class UserController extends BaseController public function updateUser(int $userID) { if (! $this->validate('userRules')) { + // The validation failed. return view('users/update', [ 'errors' => $this->validator->getErrors(), ]); } - // do something here if successful... + // The validation was successful. + + // Get the validated data. + $validData = $this->validator->getValidated(); + + // ... } } diff --git a/source/incoming/controllers/024.php b/source/incoming/controllers/024.php new file mode 100644 index 000000000..c3ae1a7c6 --- /dev/null +++ b/source/incoming/controllers/024.php @@ -0,0 +1,11 @@ +` 不同,你可以选择应用过滤器的特定 URI。传入过滤器可以修改请求,而后置过滤器可以操作甚至修改响应,提供了很大的灵活性和能力。使用过滤器可以执行的一些常见任务示例: +控制器过滤器允许你在控制器执行之前或之后执行操作。与 :doc:`事件 <../extending/events>` 不同,你可以选择将过滤器应用于特定的 URI 或路由。前置过滤器可以修改请求,而后置过滤器可以对响应进行操作甚至修改,从而提供了很大的灵活性和功能。 + +使用过滤器可以执行的一些常见任务示例: * 对传入请求执行 CSRF 保护 * 根据角色限制站点的区域访问 @@ -23,7 +25,7 @@ .. literalinclude:: filters/001.php -Before 过滤器 +前置过滤器 ============== 替换请求 @@ -51,7 +53,7 @@ Before 过滤器 .. _after-filters: -After 过滤器 +后置过滤器 ============= After 过滤器与 Before 过滤器几乎完全相同,只是你只能返回 ``$response`` 对象,并且无法停止脚本执行。这确实允许你修改最终输出,或者只是做一些最终输出的事情。这可以用于确保某些安全头正确设置,缓存最终输出,或者使用禁用词过滤器过滤最终输出。 @@ -60,10 +62,16 @@ After 过滤器与 Before 过滤器几乎完全相同,只是你只能返回 ``$r 配置过滤器 ******************* -创建过滤器后,你需要配置它们的运行时机。这是在 **app/Config/Filters.php** 中完成的。该文件包含四个属性,允许你配置过滤器的确切运行时机。 +配置过滤器运行时有两种方法。一种是在 **app/Config/Filters.php** 中进行配置,另一种是在 **app/Config/Routes.php** 中进行配置。 + +如果你想为特定的路由指定过滤器,请使用 **app/Config/Routes.php** 并参考 :ref:`URI Routing `。 + +在路由中指定的过滤器(在 **app/Config/Routes.php** 中)会在 **app/Config/Filters.php** 中指定的过滤器之前执行。 .. note:: 最安全的应用过滤器方法是 :ref:`禁用自动路由 `,并 :ref:`设置过滤器到路由 `。 +**app/Config/Filters.php** 文件包含四个属性,允许你精确配置过滤器的运行时机。 + .. warning:: 建议你在过滤器设置中的 URI 末尾始终添加 ``*``。因为控制器方法可能比你想象的通过不同的 URL 访问。例如,当启用 :ref:`auto-routing-legacy` 时,如果你有 ``Blog::index``,它可以通过 ``blog``、``blog/index`` 和 ``blog/index/1`` 等方式访问。 $aliases @@ -91,11 +99,11 @@ $globals 除了少数 URI --------------------- -有时你希望将过滤器应用于几乎所有请求,但有一些应该不受影响。一个常见的示例是,如果你需要从 CSRF 保护过滤器中排除几个 URI,以允许第三方网站的请求访问一个或两个特定的 URI,同时保持其余 URI 受保护。要做到这一点,请在别名旁边添加一个包含 ``except`` 键和要匹配的 URI 值的数组: +有时你希望将过滤器应用于几乎所有请求,但有一些应该不受影响。一个常见的示例是,如果你需要从 CSRF 保护过滤器中排除几个 URI,以允许第三方网站的请求访问一个或两个特定的 URI,同时保持其余 URI 受保护。要做到这一点,请在别名旁边添加一个包含 ``except`` 键和要匹配的 URI 路径(相对于 BaseURL)值的数组: .. literalinclude:: filters/006.php -在过滤器设置中可以使用 URI 的任何位置,你都可以使用正则表达式,或者像在这个例子中使用星号 (``*``) 作为通配符,匹配之后的所有字符。在这个例子中,任何以 ``api/`` 开头的 URL 都将被免于 CSRF 保护,但网站的表单将全部受保护。如果你需要指定多个 URI,可以使用 URI 模式数组: +在过滤器设置中可以使用 URI 路径(相对于 BaseURL)的任何位置,你都可以使用正则表达式,或者像在这个例子中使用星号 (``*``) 作为通配符,匹配之后的所有字符。在这个例子中,任何以 ``api/`` 开头的 URI 路径都将被免于 CSRF 保护,但网站的表单将全部受保护。如果你需要指定多个 URI,可以使用 URI 路径模式数组: .. literalinclude:: filters/007.php @@ -115,18 +123,22 @@ $methods $filters ======== -该属性是一个过滤器别名数组。对于每个别名,你可以为 ``before`` 和 ``after`` 数组指定过滤器应该应用到的一系列 URI 模式: +该属性是一个过滤器别名数组。对于每个别名,你可以为 ``before`` 和 ``after`` 数组指定过滤器应该应用到的一系列 URI 路径(相对于 BaseURL)模式: .. literalinclude:: filters/009.php +.. _filters-filters-filter-arguments: + 过滤器参数 -================ +---------------- + +.. versionadded:: 4.4.0 -在配置过滤器时,可以在设置路由时向过滤器传递其他参数: +在配置 ``$filters`` 时,可以传递额外的参数给过滤器: -.. literalinclude:: filters/010.php +.. literalinclude:: filters/012.php -在这个例子中,数组 ``['dual', 'noreturn']`` 将在过滤器的 ``before()`` 和 ``after()`` 实现方法的 ``$arguments`` 中传递。 +在这个例子中,当 URI 匹配 ``admin/*'`` 时,数组 ``['admin', 'superadmin']`` 将作为 ``$arguments`` 传递给 ``group`` 过滤器的 ``before()`` 方法。当 URI 匹配 ``admin/users/*'`` 时,数组 ``['users.manage']`` 将作为 ``$arguments`` 传递给 ``permission`` 过滤器的 ``before()`` 方法。 ****************** 确认过滤器 @@ -141,9 +153,11 @@ filter:check .. versionadded:: 4.3.0 -使用 **GET** 方法检查路由 ``/`` 的过滤器:: +使用 **GET** 方法检查路由 ``/`` 的过滤器: - > php spark filter:check get / +.. code-block:: console + + php spark filter:check get / 输出如下所示: @@ -166,6 +180,8 @@ CodeIgniter4 提供的过滤器有: :doc:`Honeypot <../libraries/honeypot>`、:r .. note:: 过滤器按配置文件中定义的顺序执行。但是,如果启用, ``DebugToolbar`` 总是最后执行,因为它应该能够捕获其他过滤器中发生的所有事情。 +.. _invalidchars: + InvalidChars ============= diff --git a/source/incoming/filters/008.php b/source/incoming/filters/008.php index 749da8098..945e9498b 100644 --- a/source/incoming/filters/008.php +++ b/source/incoming/filters/008.php @@ -9,8 +9,8 @@ class Filters extends BaseConfig // ... public array $methods = [ - 'post' => ['foo', 'bar'], - 'get' => ['baz'], + 'post' => ['InvalidChars', 'csrf'], + 'get' => ['csrf'], ]; // ... diff --git a/source/incoming/filters/012.php b/source/incoming/filters/012.php new file mode 100644 index 000000000..799bd57fc --- /dev/null +++ b/source/incoming/filters/012.php @@ -0,0 +1,17 @@ + ['before' => ['admin/*']], + 'permission:users.manage' => ['before' => ['admin/users/*']], + ]; + + // ... +} diff --git a/source/incoming/incomingrequest.rst b/source/incoming/incomingrequest.rst index a9c4c3c13..8b6b61713 100755 --- a/source/incoming/incomingrequest.rst +++ b/source/incoming/incomingrequest.rst @@ -147,6 +147,8 @@ getMethod() .. literalinclude:: incomingrequest/039.php +.. _incomingrequest-filtering-input-data: + 过滤输入数据 ==================== @@ -192,11 +194,9 @@ getMethod() .. literalinclude:: incomingrequest/021.php -你可以使用 ``getPath()`` 和 ``setPath()`` 方法使用当前请求的 URI 字符串(相对于 baseURL 的路径)。 -请注意,共享的 ``IncomingRequest`` 实例上的此相对路径是 :doc:`URL 辅助函数 ` -函数使用的内容,因此这是一种有用的方法来“伪造”传入请求以进行测试: +你可以使用 ``getRoutePath()`` 方法来处理当前 URI 字符串(相对于你的 baseURL 的路径)。 -.. literalinclude:: incomingrequest/022.php +.. note:: 自 v4.4.0 版本开始,可以使用 ``getRoutePath()`` 方法。在 v4.4.0 之前,``getPath()`` 方法返回相对于你的 baseURL 的路径。 上传的文件 ************** @@ -425,13 +425,16 @@ getMethod() :returns: 相对于 baseURL 的当前 URI 路径 :rtype: string - 这是确定“当前 URI”最安全的方法,因为 ``IncomingRequest::$uri`` - 可能不了解完整的 App 配置的 base URL。 + 该方法返回相对于 baseURL 的当前 URI 路径。 + + .. note:: 在 v4.4.0 之前,这是确定“当前 URI”的最安全的方法,因为 ``IncomingRequest::$uri`` 可能不知道完整的 App 配置的 base URL。 .. php:method:: setPath($path) - :param string $path: 要用作当前 URI 的相对路径 - :returns: 这个 IncomingRequest + .. deprecated:: 4.4.0 + + :param string $path: 用作当前 URI 的相对路径 + :returns: 此传入请求 :rtype: IncomingRequest - 主要用于测试目的,这允许你设置当前请求的相对路径值,而不是依赖于 URI 检测。这也会用新的路径更新底层的 ``URI`` 实例。 + .. note:: 在 v4.4.0 之前,主要用于测试目的,这允许你设置当前请求的相对路径值,而不是依赖于 URI 检测。这也会更新底层的 ``URI`` 实例的新路径。 diff --git a/source/incoming/incomingrequest/021.php b/source/incoming/incomingrequest/021.php index 558bb6a98..9a1b8601b 100644 --- a/source/incoming/incomingrequest/021.php +++ b/source/incoming/incomingrequest/021.php @@ -7,7 +7,8 @@ echo $uri->getUserInfo(); // snoopy:password echo $uri->getHost(); // example.com echo $uri->getPort(); // 88 -echo $uri->getPath(); // path/to/page +echo $uri->getPath(); // /path/to/page +echo $uri->getRoutePath(); // path/to/page echo $uri->getQuery(); // foo=bar&bar=baz print_r($uri->getSegments()); // Array ( [0] => path [1] => to [2] => page ) echo $uri->getSegment(1); // path diff --git a/source/incoming/incomingrequest/022.php b/source/incoming/incomingrequest/022.php deleted file mode 100644 index 723d81e3a..000000000 --- a/source/incoming/incomingrequest/022.php +++ /dev/null @@ -1,15 +0,0 @@ -setPath('users/list'); - - $menu = new MyMenu(); - - $this->assertTrue('users/list', $menu->getActiveLink()); - } -} diff --git a/source/incoming/message.rst b/source/incoming/message.rst index e75366cdf..c20a2ae6f 100644 --- a/source/incoming/message.rst +++ b/source/incoming/message.rst @@ -139,7 +139,7 @@ Message 类为 HTTP 消息中请求和响应共有的部分提供了一个接口 :returns: 当前的 HTTP 协议版本 :rtype: string - 返回消息的当前 HTTP 协议。如果未设置,将返回 ``null``。可接受的值为 “1.0”、“1.1” 和 “2.0”。 + 返回消息的当前 HTTP 协议。如果未设置,将返回 ``1.1``。 .. php:method:: setProtocolVersion($version) @@ -147,6 +147,6 @@ Message 类为 HTTP 消息中请求和响应共有的部分提供了一个接口 :returns: 当前消息实例 :rtype: CodeIgniter\\HTTP\\Message - 设置此消息使用的 HTTP 协议版本。有效值为 “1.0”、“1.1” 和 “2.0”: + 设置此消息使用的 HTTP 协议版本。有效值为 ``1.0``, ``1.1``, ``2.0`` 和 ``3.0``: .. literalinclude:: message/010.php diff --git a/source/incoming/routing.rst b/source/incoming/routing.rst index 76c6cbe93..cff8882d0 100755 --- a/source/incoming/routing.rst +++ b/source/incoming/routing.rst @@ -297,8 +297,9 @@ HTTP 动词路由 .. note:: 建议使用 Spark 命令作为 CLI 脚本,而不是通过 CLI 调用控制器。请参阅 :doc:`../cli/cli_commands` 页面以获取详细信息。 -你可以使用 ``cli()`` 方法创建只能从命令行使用、无法从 Web 浏览器访问的路由。 -通过任何 HTTP 动词路由方法创建的路由也无法从 CLI 访问,但通过 ``add()`` 方法创建的路由仍可从命令行使用: +任何通过基于 HTTP 动词的路由方法创建的路由也将无法从 CLI 访问,但通过 ``add()`` 方法创建的路由仍然可以从命令行访问。 + +你可以使用 ``cli()`` 方法创建只能从命令行使用、无法从 Web 浏览器访问的路由: .. literalinclude:: routing/032.php @@ -361,6 +362,15 @@ HTTP 动词路由 .. literalinclude:: routing/037.php +过滤器参数 +^^^^^^^^^^^^^^^^ + +可以向过滤器传递额外的参数: + +.. literalinclude:: routing/067.php + +在这个例子中,数组 ``['dual', 'noreturn']`` 将作为 ``$arguments`` 传递给过滤器的 ``before()`` 和 ``after()`` 实现方法。 + .. _assigning-namespace: 分配命名空间 @@ -513,8 +523,10 @@ HTTP 动词路由 路由配置选项 **************************** -RoutesCollection 类提供了几个影响所有路由的选项,可以根据你的应用需求进行修改。 -这些选项在 **app/Config/Routes.php** 的顶部可用。 +RoutesCollection 类提供了几个选项,可以影响所有路由,并且可以根据你的应用程序需求进行修改。这些选项可以在 **app/Config/Routing.php** 文件中找到。 + +.. note:: 配置文件 **app/Config/Routing.php** 自 v4.4.0 版本开始添加。 + 在之前的版本中,使用 **app/Config/Routes.php** 中的 setter 方法来更改设置。 .. _routing-default-namespace: @@ -535,12 +547,12 @@ RoutesCollection 类提供了几个影响所有路由的选项,可以根据你 转换 URI 中的破折号 ==================== -此选项使你可以自动将控制器和方法的 URI 段中的破折号 (``-``) 替换为下划线, -因此如果你需要这样做,可以节省额外的路由条目。这是必需的,因为破折号不是有效的类或方法名称字符, -如果尝试使用它会导致致命错误: +该选项允许你在使用自动路由时,自动将控制器和方法的 URI 段中的破折号(``-``)替换为下划线,从而节省了额外的路由条目。这是必需的,因为破折号不是有效的类或方法名称字符,如果你尝试使用它,将会导致致命错误: .. literalinclude:: routing/049.php +.. note:: 在使用自动路由(改进版)时,在 v4.4.0 之前,如果 ``$translateURIDashes`` 为 true,两个 URI 对应一个控制器方法,一个 URI 用于破折号(例如 **foo-bar**),另一个 URI 用于下划线(例如 **foo_bar**)。这是错误的行为。从 v4.4.0 开始,下划线的 URI(**foo_bar**)不可访问。 + .. _use-defined-routes-only: 仅使用定义的路由 @@ -550,7 +562,7 @@ RoutesCollection 类提供了几个影响所有路由的选项,可以根据你 当未找到与当前 URI 匹配的定义路由时,如果启用了自动路由,系统将尝试将该 URI 与控制器和方法匹配。 -你可以通过将 ``setAutoRoute()`` 选项设置为 false 来禁用此自动匹配, +你可以通过将 ``$autoRoute`` 属性设置为 false 来禁用此自动匹配, 并且只限制路由为你定义的路由: .. literalinclude:: routing/050.php @@ -567,6 +579,8 @@ RoutesCollection 类提供了几个影响所有路由的选项,可以根据你 .. literalinclude:: routing/051.php +在路由配置文件中使用 ``$override404`` 属性,你可以使用闭包函数。在路由文件中定义覆盖是限制在类或方法对上的。 + .. note:: ``set404Override()`` 方法不会将响应状态码更改为 ``404``。如果你不在设置的控制器中设置状态码, 将返回默认状态码 ``200``。有关如何设置状态码的信息,请参阅 :php:meth:`CodeIgniter\\HTTP\\Response::setStatusCode()`。 @@ -604,9 +618,9 @@ RoutesCollection 类提供了几个影响所有路由的选项,可以根据你 启用自动路由 =================== -要使用它,你需要在 **app/Config/Routes.php** 中将 ``setAutoRoute()`` 选项设置为 true:: +要使用它,你需要在 **app/Config/Routing.php** 中将 ``$autoRoute`` 选项设置为 true:: - $routes->setAutoRoute(true); + public bool $autoRoute = true; 并且你需要在 **app/Config/Feature.php** 中将属性 ``$autoRoutesImproved`` 设置为 ``true``:: @@ -671,6 +685,25 @@ URI 段 .. important:: 你无法使用控制器的默认方法名称访问控制器。在上面的示例中,你可以访问 **example.com/products**,但是如果访问 **example.com/products/listall** 将找不到。 +.. _auto-routing-improved-module-routing: + +模块路由 +============== + +.. versionadded:: 4.4.0 + +即使你使用 :doc:`../general/modules` 并将控制器放置在不同的命名空间中,你仍可以使用自动路由。 + +要路由到一个模块,必须在 **app/Config/Routing.php** 中设置 ``$moduleRoutes`` 属性:: + + public array $moduleRoutes = [ + 'blog' => 'Acme\Blog\Controllers', + ]; + +键是模块的第一个 URI 段,值是控制器的命名空间。在上述配置中,**http://localhost:8080/blog/foo/bar** 将被路由到 ``Acme\Blog\Controllers\Foo::getBar()``。 + +.. note:: 如果你定义了 ``$moduleRoutes``,模块的路由将优先生效。在上面的示例中,即使你有 ``App\Controllers\Blog`` 控制器,**http://localhost:8080/blog** 也将被路由到默认控制器 ``Acme\Blog\Controllers\Home``。 + .. _auto-routing-legacy: 自动路由(传统) @@ -689,7 +722,7 @@ URI 段 自 v4.2.0 起,默认禁用自动路由。 -要使用它,你需要在 **app/Config/Routes.php** 中将 ``setAutoRoute()`` 选项设置为 true:: +要使用它,你需要在 **app/Config/Routing.php** 中将 ``$autoRoute`` 选项设置为 true:: $routes->setAutoRoute(true); @@ -740,12 +773,14 @@ CodeIgniter 有以下 :doc:`命令 ` 可显示所有路由 .. _routing-spark-routes: -spark routes +spark 路由 ============ -显示所有路由和过滤器:: +显示所有路由和过滤器: + +.. code-block:: console - > php spark routes + php spark routes 输出类似以下内容: @@ -825,6 +860,21 @@ spark routes .. versionadded:: 4.3.0 -你可以按 *Handler* 对路由进行排序:: +你可以按 *Handler* 对路由进行排序: + +.. code-block:: console + + php spark routes -h + +.. _routing-spark-routes-specify-host: + +指定主机 +------------ + +.. versionadded:: 4.4.0 + +你可以使用 ``--host`` 选项在请求 URL 中指定主机: + +.. code-block:: console - > php spark routes -h + php spark routes --host accounts.example.com diff --git a/source/incoming/routing/045.php b/source/incoming/routing/045.php index 98aef9276..e9534931d 100644 --- a/source/incoming/routing/045.php +++ b/source/incoming/routing/045.php @@ -1,6 +1,12 @@ setDefaultNamespace(''); +// In app/Config/Routing.php +class Routing extends BaseRouting +{ + // ... + public string $defaultNamespace = ''; + // ... +} // Controller is \Users $routes->get('users', 'Users::index'); diff --git a/source/incoming/routing/046.php b/source/incoming/routing/046.php index fbbbee230..8998fe70c 100644 --- a/source/incoming/routing/046.php +++ b/source/incoming/routing/046.php @@ -1,5 +1,6 @@ setDefaultNamespace('App'); // Controller is \App\Users diff --git a/source/incoming/routing/049.php b/source/incoming/routing/049.php index 31d49508a..9f53bee87 100644 --- a/source/incoming/routing/049.php +++ b/source/incoming/routing/049.php @@ -1,3 +1,12 @@ setTranslateURIDashes(true); diff --git a/source/incoming/routing/050.php b/source/incoming/routing/050.php index c331102cd..6f5446f5a 100644 --- a/source/incoming/routing/050.php +++ b/source/incoming/routing/050.php @@ -1,3 +1,12 @@ setAutoRoute(false); diff --git a/source/incoming/routing/051.php b/source/incoming/routing/051.php index dddd067f0..bced5d531 100644 --- a/source/incoming/routing/051.php +++ b/source/incoming/routing/051.php @@ -1,5 +1,13 @@ set404Override('App\Errors::show404'); diff --git a/source/incoming/filters/010.php b/source/incoming/routing/067.php similarity index 100% rename from source/incoming/filters/010.php rename to source/incoming/routing/067.php diff --git a/source/installation/backward_compatibility_notes.rst b/source/installation/backward_compatibility_notes.rst index 227f6ff68..a946d7a51 100644 --- a/source/installation/backward_compatibility_notes.rst +++ b/source/installation/backward_compatibility_notes.rst @@ -13,6 +13,6 @@ 什么不是破坏兼容性的更改 ***************************** -- 已弃用的配置项目不受后向兼容性(BC)承诺约束。它可能会在下一个 **次要** 版本或更高版本中被移除。 +- 已弃用的项目不受后向兼容性(BC)承诺约束。它可能会在下一个 **次要** 版本或更高版本中被移除。例如,如果一个项目从 4.3.x 版本开始被弃用,那么它可能会在 4.5.0 版本中被移除。 - 定义在 **system/Language/en/** 中的系统消息严格用于内部框架使用,不受后向兼容性(BC)承诺约束。如果开发者依赖语言字符串输出,应该检查函数调用 (``lang('...')``),而不是内容。 - `命名参数 `_ 不受后向兼容性(BC)承诺约束。当必要时,我们可能会重命名方法/函数的参数名以改进代码库。 diff --git a/source/installation/installing_composer.rst b/source/installation/installing_composer.rst index 1e0a3c063..296dc3130 100644 --- a/source/installation/installing_composer.rst +++ b/source/installation/installing_composer.rst @@ -28,21 +28,23 @@ App Starter 安装 ------------ -在项目根目录上层文件夹中:: +在项目根目录上层文件夹中: - > composer create-project codeigniter4/appstarter 项目根目录 +.. code-block:: console + + composer create-project codeigniter4/appstarter 项目根目录 上述命令将创建一个 **项目根目录** 文件夹。 如果省略“项目根目录”参数,该命令将创建一个“appstarter”文件夹,可以根据需要重命名。 -.. note:: CodeIgniter 自动加载程序不允许特殊字符,这些字符在某些操作系统中的文件名中是非法的。 - 可以使用的符号是 ``/``, ``_``, ``.``, ``:``, ``\`` 和空格。 - 因此,如果在包含特殊字符的文件夹下安装 CodeIgniter,比如 ``(``, ``)`` 等,CodeIgniter 将无法工作。 +.. note:: 在 v4.4.0 之前,CodeIgniter 的自动加载器不允许在某些操作系统上的文件名中使用非法的特殊字符。可以使用的符号包括 ``/``、``_``、``.``、``:``、``\`` 和空格。因此,如果你将 CodeIgniter 安装在包含特殊字符(如 ``(``、``)`` 等)的文件夹中,CodeIgniter 将无法正常工作。从 v4.4.0 开始,这个限制已经被移除。 + +.. important:: 当你将应用部署到生产服务器时,不要忘记运行以下命令: -.. important:: 当你将应用部署到生产服务器时,不要忘记运行以下命令:: + .. code-block:: console - > composer install --no-dev + composer install --no-dev 上述命令将只移除开发环境下的 Composer 软件包,这些软件包在生产环境中不需要。这将大大减少 vendor 文件夹的大小。 @@ -56,9 +58,11 @@ App Starter 升级 --------- -每当有新版本发布时,在项目根目录的命令行中运行:: +每当有新版本发布时,在项目根目录的命令行中运行: + +.. code-block:: console - > composer update + composer update 阅读 :doc:`升级说明 `,并查看已破坏的更改和增强功能。 @@ -91,15 +95,41 @@ App Starter 仓库带有 ``builds`` 脚本,可在当前稳定版本和框架的 `开发用户指南 `_ 可以在线访问。 请注意,这与已发布的用户指南不同,并将明确适用于 develop 分支。 -在项目根目录中:: +最新开发版更新 +^^^^^^^^^^^^^^^^^^^^^ + +在你的项目根目录中执行以下命令: + +.. code-block:: console + + php builds development + +上述命令将更新 **composer.json**,将其指向工作仓库的 ``develop`` 分支,并更新配置文件和 XML 文件中的相应路径。 + +使用 ``builds`` 命令后,请确保运行 ``composer update``,以使你的 vendor 文件夹与最新的目标构建同步。然后,根据需要检查 :doc:`upgrading` 并更新项目文件。 + +下一个次要版本 +^^^^^^^^^^^^^^^^^^ - > php builds development +如果你想使用下一个次要版本的分支,在使用 ``builds`` 命令后手动编辑 **composer.json**。 -上述命令将更新 **composer.json** 以指向工作仓库的 ``develop`` 分支,并更新配置和 XML 文件中的相应路径。要还原这些更改,请运行:: +如果你尝试使用 ``4.4`` 分支,请将版本更改为 ``4.4.x-dev``:: - > php builds release + "require": { + "php": "^7.4 || ^8.0", + "codeigniter4/codeigniter4": "4.4.x-dev" + }, -使用 ``builds`` 命令后,请务必运行 ``composer update`` 以使用最新目标构建同步 vendor 文件夹。 +然后运行 ``composer update``,以使你的 vendor 文件夹与最新的目标构建同步。然后,根据需要检查升级指南(**user_guide_src/source/installation/upgrade_{version}.rst**)并更新项目文件。 + +恢复到稳定版本 +^^^^^^^^^^^^^^^^^^^^^^^^ + +要恢复更改,请运行: + +.. code-block:: console + + php builds release 将 CodeIgniter4 添加到现有项目中 ========================================== @@ -112,13 +142,17 @@ App Starter 仓库带有 ``builds`` 脚本,可在当前稳定版本和框架的 在 ``app`` 文件夹中开发你的应用程序, ``public`` 文件夹将是你的文档根目录。 -在项目根目录中:: +在项目根目录中: - > composer require codeigniter4/framework +.. code-block:: console -.. important:: 将应用程序部署到生产服务器时,不要忘记运行以下命令:: + composer require codeigniter4/framework - > composer install --no-dev +.. important:: 将应用程序部署到生产服务器时,不要忘记运行以下命令: + +.. code-block:: console + + composer install --no-dev 上述命令将只移除开发环境下的 Composer 软件包,这些软件包在生产环境中不需要。这将大大减少 vendor 文件夹的大小。 @@ -139,9 +173,11 @@ App Starter 仓库带有 ``builds`` 脚本,可在当前稳定版本和框架的 升级 --------- -每当有新版本发布时,在项目根目录的命令行中运行:: +每当有新版本发布时,在项目根目录的命令行中运行: - > composer update +.. code-block:: console + + composer update 阅读 :doc:`升级说明 `,并查看已破坏的更改和增强功能。 @@ -171,8 +207,10 @@ App Starter 仓库带有 ``builds`` 脚本,可在当前稳定版本和框架的 如果你想利用系统消息翻译,可以以类似的方式将它们添加到项目中。 -在项目根目录的命令行中:: +在项目根目录的命令行中: + +.. code-block:: console - > composer require codeigniter4/translations + composer require codeigniter4/translations 每次执行 ``composer update`` 时,这些都会与框架一起更新。 diff --git a/source/installation/installing_manual.rst b/source/installation/installing_manual.rst index 2d02eb531..3792c3d90 100644 --- a/source/installation/installing_manual.rst +++ b/source/installation/installing_manual.rst @@ -21,9 +21,7 @@ 下载 `最新版本 `__, 并将其提取到成为项目根目录。 -.. note:: CodeIgniter 自动加载程序不允许特殊字符,这些字符在某些操作系统中的文件名中是非法的。 - 可以使用的符号是 ``/``, ``_``, ``.``, ``:``, ``\`` 和空格。 - 因此,如果在包含特殊字符的文件夹下安装 CodeIgniter,比如 ``(``, ``)`` 等,CodeIgniter 将无法工作。 +.. note:: 在 v4.4.0 之前,CodeIgniter 的自动加载器不允许在某些操作系统上的文件名中使用非法的特殊字符。可以使用的符号包括 ``/``、``_``、``.``、``:``、``\`` 和空格。因此,如果你将 CodeIgniter 安装在包含特殊字符(如 ``(``、``)`` 等)的文件夹中,CodeIgniter 将无法正常工作。从 v4.4.0 开始,这个限制已经被移除。 初始配置 ===================== diff --git a/source/installation/running.rst b/source/installation/running.rst index 016624721..69ff700ef 100644 --- a/source/installation/running.rst +++ b/source/installation/running.rst @@ -1,76 +1,119 @@ +################ 运行你的应用程序 ################ .. contents:: :local: - :depth: 2 + :depth: 3 -CodeIgniter 4 应用程序可以通过多种方式运行:托管在 Web 服务器上,使用虚拟化,或者使用 CodeIgniter 的命令行工具进行测试。本节介绍如何使用每种技术,并解释了它们的一些优缺点。 +CodeIgniter 4 应用程序可以以多种不同的方式运行:托管在 Web 服务器上、使用虚拟化技术,或者使用 CodeIgniter 的命令行工具进行测试。本节介绍如何使用每种技术,并解释其中的一些优缺点。 -.. important:: 关于文件名的大小写,你应该始终小心。许多开发者在 Windows 或 macOS 上使用大小写不敏感的文件系统进行开发。然而,大多数服务器环境使用大小写敏感的文件系统。如果文件名大小写不正确,则在服务器上无法正常工作的代码在本地环境下也将无法正常工作。 +.. important:: 在文件名的大小写方面应始终小心。许多开发人员在 Windows 或 macOS 上使用不区分大小写的文件系统进行开发。然而,大多数服务器环境使用区分大小写的文件系统。如果文件名大小写不正确,本地上正常工作的代码在服务器上将无法正常工作。 -如果你是 CodeIgniter 的新手,请阅读用户指南的 :doc:`入门指南 ` 部分,开始学习构建动态 PHP 应用程序的方法。祝你使用愉快! +如果你是 CodeIgniter 的新手,请阅读用户指南的 :doc:`入门 ` 部分,开始学习如何构建动态的 PHP 应用程序。祝你使用愉快! .. _initial-configuration: +********************* 初始配置 -===================== +********************* + +为你的站点 URI 进行配置 +============================ + +使用文本编辑器打开 **app/Config/App.php** 文件。 + +#. $baseURL + 将你的基本 URL 设置为 ``$baseURL``。如果你需要更大的灵活性,可以在 :ref:`.env ` 文件中设置 baseURL,例如 ``app.baseURL = 'http://example.com/'``。**始终在基本 URL 的末尾使用斜杠!** + + .. note:: 如果你没有正确设置 ``baseURL``,在开发模式下,调试工具栏可能无法正确加载,网页可能需要更长的时间才能显示。 -#. 使用文本编辑器打开 **app/Config/App.php** 文件,并将你的基本 URL 设置为 ``$baseURL``。如果你需要更灵活的配置,也可以在 :ref:`.env ` 文件中设置 baseURL,例如 ``app.baseURL = 'http://example.com/'``。 (始终在基本 URL 后加上斜杠!) +#. $indexPage + 如果你不想在站点 URI 中包含 **index.php**,请将 ``$indexPage`` 设置为 ``''``。当框架生成你的站点 URI 时,将使用此设置。 - .. note:: 如果未正确设置 ``baseURL``,在开发模式下,调试工具栏可能无法正确加载,并且网页显示可能需要更长的时间。 + .. note:: 你可能需要配置你的 Web 服务器以访问不包含 **index.php** 的 URL。请参阅 :ref:`CodeIgniter URLs `。 -#. 如果你打算使用数据库,使用文本编辑器打开 - **app/Config/Database.php** 文件,并设置你的 - 数据库设置。或者,你也可以在 **.env** 文件中进行设置。 -#. 如果它不在生产服务器上,请在 **.env** 文件中将 ``CI_ENVIRONMENT`` 设置为 ``development``,以便利用提供的调试工具。有关详细信息,请参阅 :ref:`设置开发模式 `。 +配置数据库连接设置 +====================================== - .. important:: 在生产环境中,你应该禁用错误显示和任何其他仅用于开发的功能。在 CodeIgniter 中,可以通过将环境设置为“production”来实现。默认情况下,应用程序将使用“production”环境运行。另请参阅 :ref:`environment-constant`。 +如果你打算使用数据库,请使用文本编辑器打开 **app/Config/Database.php** 文件,并设置你的数据库设置。或者,你可以在 **.env** 文件中设置这些设置。 -.. note:: 如果你将使用 Web 服务器(例如 Apache 或 Nginx)运行你的站点,你需要修改项目中的 ``writable`` 文件夹的权限,以便由 Web 服务器使用的用户或账户具有写入权限。 +设置为开发模式 +======================= +如果不是在生产服务器上,请在 **.env** 文件中将 ``CI_ENVIRONMENT`` 设置为 ``development``,以利用提供的调试工具。有关详细信息,请参阅 :ref:`setting-development-mode`。 + +.. important:: 在生产环境中,应禁用错误显示和任何其他仅用于开发的功能。在 CodeIgniter 中,可以通过将环境设置为 "production" 来实现。默认情况下,应用程序将在 "production" 环境下运行。另请参阅 :ref:`environment-constant`。 + +设置可写文件夹权限 +============================== + +如果你将使用 Web 服务器(例如 Apache 或 nginx)运行你的站点,你需要修改项目中的 **writable** 文件夹的权限,以便它可以被你的 Web 服务器使用的用户或帐户写入。 + +************************ 本地开发服务器 -======================== +************************ -CodeIgniter 4 自带一个本地开发服务器,利用 PHP 的内置 Web 服务器和 CodeIgniter 的路由功能。你可以使用 ``serve`` 脚本来启动它,在主目录下使用以下命令行:: +CodeIgniter 4 自带一个本地开发服务器,利用 PHP 的内置 Web 服务器和 CodeIgniter 的路由功能。你可以使用以下命令在主目录中启动它: - > php spark serve +.. code-block:: console -这将启动服务器,并且你现在可以在浏览器中通过 http://localhost:8080 查看你的应用程序。 + php spark serve -.. note:: 内置的开发服务器应该只在本地开发机器上使用。绝对不要在生产服务器上使用。 +这将启动服务器,你现在可以在浏览器中通过 http://localhost:8080 查看你的应用程序。 -如果你需要在除 localhost 之外的主机上运行站点,你首先需要将主机添加到你的 ``hosts`` 文件中。该文件的确切位置在每个主要操作系统中都有所不同,不过所有的类 Unix 系统(包括 OS X)通常将该文件保存在 **/etc/hosts** 中。 +.. note:: 内置的开发服务器只应在本地开发机器上使用。它绝不能在生产服务器上使用。 -可以使用三个命令行选项自定义本地开发服务器: +如果你需要在除 localhost 之外的主机上运行站点,你首先需要将主机添加到你的 **hosts** 文件中。文件的确切位置因每个主要操作系统而异,但所有的类 Unix 类型的系统(包括 macOS)通常将文件保存在 **/etc/hosts** 中。 -- 你可以使用 ``--host`` CLI 选项指定要在其上运行应用程序的不同主机:: +本地开发服务器可以使用三个命令行选项进行自定义: - > php spark serve --host example.dev +- 你可以使用 ``--host`` CLI 选项指定要运行应用程序的不同主机: -- 默认情况下,服务器运行在端口 8080 上,但你可能有多个站点正在运行,或者已经有另一个应用程序使用该端口。你可以使用 ``--port`` CLI 选项指定一个不同的端口:: + .. code-block:: console - > php spark serve --port 8081 + php spark serve --host example.dev -- 你还可以使用 ``--php`` CLI 选项指定要使用的特定版本的 PHP,其值设置为要使用的 PHP 可执行文件的路径:: +- 默认情况下,服务器在端口 8080 上运行,但你可能有多个站点正在运行,或者已经有其他应用程序使用该端口。你可以使用 ``--port`` CLI 选项指定不同的端口: - > php spark serve --php /usr/bin/php7.6.5.4 + .. code-block:: console + php spark serve --port 8081 + +- 你还可以使用 ``--php`` CLI 选项指定要使用的特定版本的 PHP,将其值设置为你要使用的 PHP 可执行文件的路径: + + .. code-block:: console + + php spark serve --php /usr/bin/php7.6.5.4 + +******************* 使用 Apache 托管 -=================== +******************* -CodeIgniter 4 Web 应用程序通常托管在 Web 服务器上。Apache 的 ``httpd`` 是“标准”平台,在我们的大部分文档中都默认使用它。 +CodeIgniter 4 网站通常托管在 Web 服务器上。Apache HTTP Server 是“标准”平台,在我们的文档中假定使用它。 -Apache 随附于许多平台,但也可以从 `Bitnami `_ 下载包含数据库引擎和 PHP 的捆绑包。 +Apache 与许多平台捆绑在一起,但也可以从 `Bitnami `_ 下载捆绑了数据库引擎和 PHP 的版本。 -.htaccess ---------- +配置主配置文件 +========================== -我们的用户指南假设使用了“mod_rewrite”模块,该模块允许 URL 中没有 "index.php",请确保该模块已启用(取消注释)在主配置文件中,例如 ``apache2/conf/httpd.conf``:: +启用 mod_rewrite +-------------------- + +"mod_rewrite" 模块允许在 URL 中不包含 "index.php",我们在用户指南中假定了这一点。 + +确保在主配置文件中启用(取消注释)重写模块,例如 **apache2/conf/httpd.conf**: + +.. code-block:: apache LoadModule rewrite_module modules/mod_rewrite.so -还要确保默认文档根目录的 元素也启用了此设置,在 "AllowOverride" 设置中:: +设置文档根目录 +--------------------- + +还要确保默认文档根目录的 ```` 元素也启用了这一点,在 ``AllowOverride`` 设置中: + +.. code-block:: apache Options Indexes FollowSymLinks @@ -78,124 +121,228 @@ Apache 随附于许多平台,但也可以从 `Bitnami -去除 index.php ----------------------- +使用虚拟主机托管 +======================== -请参阅 :ref:`CodeIgniter URLs `。 +我们建议使用“虚拟主机”来运行你的应用程序。你可以为你工作的每个应用程序设置不同的别名, -虚拟主机 ---------------- +启用 vhost_alias_module +--------------------------- -我们建议使用“虚拟主机”来运行你的应用程序。你可以为你工作的每个应用程序设置不同的别名。 +确保在主配置文件中启用(取消注释)虚拟主机模块,例如 **apache2/conf/httpd.conf**: -确保虚拟主机模块已在主配置文件中启用(取消注释),例如 ``apache2/conf/httpd.conf``:: +.. code-block:: apache LoadModule vhost_alias_module modules/mod_vhost_alias.so -在你的 "hosts" 文件中添加一个主机别名,通常在类 Unix 系统的平台上是 ``/etc/hosts``,在 Windows 上是 ``c:/Windows/System32/drivers/etc/hosts``。在文件中 +添加主机别名 +----------------- + +在你的 "hosts" 文件中添加主机别名,通常在 Unix 类型平台上为 **/etc/hosts**,在 Windows 上为 **c:\Windows\System32\drivers\etc\hosts**。 -添加一行。例如,可以是 "myproject.local" 或 "myproject.test":: +在文件中添加一行。例如,可以是 ``myproject.local`` 或 ``myproject.test``:: 127.0.0.1 myproject.local -在虚拟主机配置内添加一个 元素,例如在虚拟主机配置文件中的 ``apache2/conf/extra/httpd-vhost.conf``:: +设置虚拟主机 +------------------- + +在虚拟主机配置中添加一个 ```` 元素,用于你的 Web 应用程序,例如 **apache2/conf/extra/httpd-vhost.conf**: + +.. code-block:: apache - DocumentRoot "/opt/lamp/apache2/htdocs/myproject/public" - ServerName myproject.local - ErrorLog "logs/myproject-error_log" - CustomLog "logs/myproject-access_log" common + DocumentRoot "/opt/lamp/apache2/myproject/public" + ServerName myproject.local + ErrorLog "logs/myproject-error_log" + CustomLog "logs/myproject-access_log" common + + + AllowOverride All + Require all granted + -如果你的项目文件夹不是 Apache 文档根目录的子文件夹,则你的 元素可能需要嵌套的 元素,以授予 Web 服务器对文件的访问权限。 +上述配置假设项目文件夹位于以下位置: + +.. code-block:: text + + apache2/ + ├── myproject/ (项目文件夹) + │ └── public/ (myproject.local 的 DocumentRoot) + └── htdocs/ + +重启 Apache。 + +测试 +------- + +使用上述配置,在浏览器中使用 URL **http://myproject.local/** 访问你的 Web 应用程序。 + +每当更改 Apache 配置时,都需要重新启动 Apache。 + +使用子文件夹进行托管 +====================== + +如果你希望使用类似 **http://localhost/myproject/** 的子文件夹 baseURL,有三种方法可以实现。 + +创建符号链接 +-------------- -使用 mod_userdir(共享主机) --------------------------------- +将你的项目文件夹放置在以下位置,其中 **htdocs** 是 Apache 的文档根目录:: -在共享托管环境中的常见做法是使用 Apache 模块 "mod_userdir" 自动启用每个用户的虚拟主机。为了允许从这些每个用户目录中运行 CodeIgniter4,需要进行额外的配置。 + ├── myproject/ (项目文件夹) + │ └── public/ + └── htdocs/ -以下假设服务器已经配置了 mod_userdir。关于启用此模块的指南可在 `Apache 文档 `_ 中找到。 +导航到 **htdocs** 文件夹并创建符号链接,如下所示: -因为 CodeIgniter4 默认情况下期望服务器在 ``/public/index.php`` 处找到框架前端控制器,所以你必须指定此位置作为搜索请求的替代位置(即使 CodeIgniter4 安装在每个用户的 Web 目录中)。 +.. code-block:: console -默认的用户 Web 目录 ``~/public_html`` 是由 ``UserDir`` 指令指定的,通常位于 ``/apache2/mods-available/userdir.conf`` 或 ``/apache2/conf/extra/httpd-userdir.conf`` 中:: + cd htdocs/ + ln -s ../myproject/public/ myproject + +使用别名 +----------- + +将你的项目文件夹放置在以下位置,其中 **htdocs** 是 Apache 的文档根目录: + + ├── myproject/ (项目文件夹) + │ └── public/ + └── htdocs/ + +在主配置文件中添加以下内容,例如 **apache2/conf/httpd.conf**: + +.. code-block:: apache + + Alias /myproject /opt/lamp/apache2/myproject/public + + AllowOverride All + Require all granted + + +重启 Apache。 + +添加 .htaccess +---------------- + +最后的选择是在项目根目录中添加 **.htaccess** 文件。 + +不建议将项目文件夹放置在文档根目录中。但是,如果你没有其他选择,例如在共享服务器上,你可以使用此方法。 + +将你的项目文件夹放置在以下位置,其中 **htdocs** 是 Apache 的文档根目录,并创建 **.htaccess** 文件: + + └── htdocs/ + └── myproject/ (项目文件夹) + ├── .htaccess + └── public/ + +并将 **.htaccess** 编辑如下: + +.. code-block:: apache + + + RewriteEngine On + RewriteRule ^(.*)$ public/$1 [L] + + + + Require all denied + Satisfy All + + +使用 mod_userdir 进行托管(共享主机) +======================================= + +在共享托管环境中,常见做法是使用 Apache 模块 "mod_userdir" 自动启用每个用户的虚拟主机。需要额外的配置才能允许 CodeIgniter4 从这些每个用户目录中运行。 + +以下假设服务器已经配置为 mod_userdir。有关启用此模块的指南,请参阅 Apache 文档中的 `相关部分 `_。 + +由于 CodeIgniter4 默认情况下期望服务器在 **public/index.php** 中找到框架前端控制器,因此你必须指定此位置作为替代位置以搜索请求(即使 CodeIgniter4 安装在每个用户的 Web 目录中)。 + +默认的用户 Web 目录 **~/public_html** 由 ``UserDir`` 指令指定,通常位于 **apache2/mods-available/userdir.conf** 或 **apache2/conf/extra/httpd-userdir.conf** 中: + +.. code-block:: apache UserDir public_html -因此,你需要配置 Apache,在尝试提供默认服务之前,首先查找 CodeIgniter 的 public 目录:: +因此,你需要配置 Apache 在尝试提供默认服务之前首先查找 CodeIgniter 的 public 目录: + +.. code-block:: apache UserDir "public_html/public" "public_html" -还请确保为 CodeIgniter 的 public 目录指定选项和权限。一个 ``userdir.conf`` 可能如下所示:: +确保还为 CodeIgniter 的 public 目录指定选项和权限。一个 **userdir.conf** 可能如下所示: + +.. code-block:: apache UserDir "public_html/public" "public_html" UserDir disabled root - AllowOverride All - Options MultiViews Indexes FollowSymLinks - - # Apache <= 2.2: - # Order allow,deny - # Allow from all - - # Apache >= 2.4: - Require all granted - - - # Apache <= 2.2: - # Order deny,allow - # Deny from all - - # Apache >= 2.4: - Require all denied - + AllowOverride All + Options MultiViews Indexes FollowSymLinks + + # Apache <= 2.2: + # Order allow,deny + # Allow from all + + # Apache >= 2.4: + Require all granted + + + # Apache <= 2.2: + # Order deny,allow + # Deny from all + + # Apache >= 2.4: + Require all denied + - AllowOverride All - Options MultiViews Indexes FollowSymLinks - - # Apache <= 2.2: - # Order allow,deny - # Allow from all - - # Apache >= 2.4: - Require all granted - - - # Apache <= 2.2: - # Order deny,allow - # Deny from all - - # Apache >= 2.4: - Require all denied - + AllowOverride All + Options MultiViews Indexes FollowSymLinks + + # Apache <= 2.2: + # Order allow,deny + # Allow from all + + # Apache >= 2.4: + Require all granted + + + # Apache <= 2.2: + # Order deny,allow + # Deny from all + + # Apache >= 2.4: + Require all denied + -设置环境 -------------------- +删除 index.php +====================== -请参阅 :ref:`处理多环境 `。 - -测试 -------- +请参阅 :ref:`CodeIgniter URLs `。 -通过上述配置,你的 Web 应用程序将可以通过 URL ``http://myproject.local`` 在浏览器中访问。 +设置环境 +=================== -每当更改 Apache 配置时,都需要重新启动 Apache。 +请参阅 :ref:`处理多个环境 `。 -使用 Nginx 托管 -================== +****************** +使用 nginx 托管 +****************** -Nginx 是第二个最广泛使用的 Web 托管 HTTP 服务器。这里你可以找到使用 PHP 7.3 FPM(unix sockets)的 Ubuntu Server 的示例配置。 +nginx 是第二常用的用于 Web 托管的 HTTP 服务器。以下是一个在 Ubuntu Server 上使用 PHP 8.1 FPM(Unix 套接字)的示例配置。 default.conf ------------- +============ -此配置使 URL 中没有 "index.php",并对以 ".php" 结尾的 URL 使用 CodeIgniter 的 "404 - 文件未找到"。 +此配置使 URL 中不包含 "index.php",并对以 ".php" 结尾的 URL 使用 CodeIgniter 的 "404 - 文件未找到"。 .. code-block:: nginx @@ -216,27 +363,28 @@ default.conf include snippets/fastcgi-php.conf; # 使用 php-fpm: - fastcgi_pass unix:/run/php/php7.3-fpm.sock; + fastcgi_pass unix:/run/php/php8.1-fpm.sock; # 使用 php-cgi: # fastcgi_pass 127.0.0.1:9000; } error_page 404 /index.php; - # 拒绝访问隐藏文件,例如 .htaccess + # 禁止访问隐藏文件,如 .htaccess location ~ /\. { deny all; } } 设置环境 -------------------- +=================== -请参阅 :ref:`处理多环境 `。 +请参阅 :ref:`处理多个环境 `。 +********************* 引导应用程序 -===================== +********************* -在某些场景中,你可能希望加载框架,而不实际运行整个应用程序。这对于对项目进行单元测试非常有用,但也可以用于使用第三方工具来分析和修改你的代码。该框架提供了一个专门用于这种情况的单独引导脚本:``system/Test/bootstrap.php``。 +在某些情况下,你可能希望加载框架而不实际运行整个应用程序。这对于对项目进行单元测试非常有用,但也可能对使用第三方工具分析和修改代码很有用。框架提供了一个专门用于此场景的独立引导脚本:**system/Test/bootstrap.php**。 -大多数路径到你的项目都在引导过程中定义。你可以使用预定义的常量覆盖这些路径,但是当使用默认值时,请确保你的路径与预期的目录结构对齐,以适应你的安装方法。 +在引导过程中,大部分项目路径都会被定义。你可以使用预定义的常量来覆盖这些路径,但是当使用默认值时,请确保你的路径与安装方法的预期目录结构对齐。 diff --git a/source/installation/troubleshooting.rst b/source/installation/troubleshooting.rst index 52a38d0e3..bdf5a4573 100755 --- a/source/installation/troubleshooting.rst +++ b/source/installation/troubleshooting.rst @@ -11,9 +11,11 @@ 如何知道我的安装是否正常工作? --------------------------------------- -在项目根目录的命令行中:: +在项目根目录的命令行中: - > php spark serve +.. code-block:: console + + php spark serve 然后在浏览器中打开 ``http://localhost:8080`` 应该可以看到默认的欢迎页面: diff --git a/source/installation/upgrade_420.rst b/source/installation/upgrade_420.rst index 815195b07..eaf17c384 100644 --- a/source/installation/upgrade_420.rst +++ b/source/installation/upgrade_420.rst @@ -26,11 +26,13 @@ index.php 和 spark .. important:: 如果你不更新以上两个文件,在运行 ``composer update`` 后 CodeIgniter 将完全无法工作。 - 升级过程例如如下:: + 升级过程例如如下: - > composer update - > cp vendor/codeigniter4/framework/public/index.php public/index.php - > cp vendor/codeigniter4/framework/spark . + .. code-block:: console + + composer update + cp vendor/codeigniter4/framework/public/index.php public/index.php + cp vendor/codeigniter4/framework/spark . Config/Constants.php ==================== diff --git a/source/installation/upgrade_4210.rst b/source/installation/upgrade_4210.rst index b82c937de..f94ce7236 100644 --- a/source/installation/upgrade_4210.rst +++ b/source/installation/upgrade_4210.rst @@ -15,7 +15,7 @@ 项目文件 ************* -``4.2.10`` 版本没有更改项目文件中的任何可执行代码。 +4.2.10 版本没有更改项目文件中的任何可执行代码。 所有更改 =========== diff --git a/source/installation/upgrade_4211.rst b/source/installation/upgrade_4211.rst index 39c4771d3..fceaac725 100644 --- a/source/installation/upgrade_4211.rst +++ b/source/installation/upgrade_4211.rst @@ -47,7 +47,7 @@ Session 处理程序密钥更改 项目文件 ************* -``4.2.11`` 版本没有更改项目文件中的任何可执行代码。 +4.2.11 版本没有更改项目文件中的任何可执行代码。 所有更改 =========== diff --git a/source/installation/upgrade_4212.rst b/source/installation/upgrade_4212.rst index e3119bd21..e1a1814f8 100644 --- a/source/installation/upgrade_4212.rst +++ b/source/installation/upgrade_4212.rst @@ -15,7 +15,7 @@ 项目文件 ************* -``4.2.12`` 版本没有更改项目文件中的任何可执行代码。 +4.2.12 版本没有更改项目文件中的任何可执行代码。 所有更改 =========== diff --git a/source/installation/upgrade_423.rst b/source/installation/upgrade_423.rst index 1c857d302..863d32a1c 100644 --- a/source/installation/upgrade_423.rst +++ b/source/installation/upgrade_423.rst @@ -15,4 +15,4 @@ 项目文件 ************* -``4.2.3`` 版本是出于安全考虑的内部变更,项目中不需要任何干预。 +4.2.3 版本是出于安全考虑的内部变更,项目中不需要任何干预。 diff --git a/source/installation/upgrade_425.rst b/source/installation/upgrade_425.rst index 754e9e788..2f4f509d3 100644 --- a/source/installation/upgrade_425.rst +++ b/source/installation/upgrade_425.rst @@ -15,4 +15,4 @@ 项目文件 ************* -``4.2.5`` 版本没有更改任何项目文件。 +4.2.5 版本没有更改任何项目文件。 diff --git a/source/installation/upgrade_427.rst b/source/installation/upgrade_427.rst index dae1c8159..27f1617ad 100644 --- a/source/installation/upgrade_427.rst +++ b/source/installation/upgrade_427.rst @@ -56,7 +56,7 @@ set_cookie() 项目文件 ************* -``4.2.7`` 版本没有更改项目文件中的任何可执行代码。 +4.2.7 版本没有更改项目文件中的任何可执行代码。 所有更改 =========== diff --git a/source/installation/upgrade_430.rst b/source/installation/upgrade_430.rst index a500d279d..e2bad8680 100644 --- a/source/installation/upgrade_430.rst +++ b/source/installation/upgrade_430.rst @@ -21,11 +21,13 @@ Composer 版本 如果你使用的是更早版本的 Composer,请升级你的 ``composer`` 工具, 删除 **vendor/** 目录,并再次运行 ``composer update``。 -例如,过程如下:: +例如,过程如下: - > composer self-update - > rm -rf vendor/ - > composer update +.. code-block:: console + + composer self-update + rm -rf vendor/ + composer update 必备文件变更 ********************** @@ -40,10 +42,12 @@ spark .. important:: 如果不更新此文件,在运行 ``composer update`` 后 Spark 命令将完全无法工作。 - 升级过程例如如下:: + 升级过程例如如下: + + .. code-block:: console - > composer update - > cp vendor/codeigniter4/framework/spark . + composer update + cp vendor/codeigniter4/framework/spark . 配置文件 ============ diff --git a/source/installation/upgrade_431.rst b/source/installation/upgrade_431.rst index 489d37754..2e2e7c682 100644 --- a/source/installation/upgrade_431.rst +++ b/source/installation/upgrade_431.rst @@ -21,11 +21,13 @@ Composer 版本 如果你使用的是更早版本的 Composer,请升级你的 ``composer`` 工具, 删除 **vendor/** 目录,并再次运行 ``composer update``。 -例如,过程如下:: +例如,过程如下: - > composer self-update - > rm -rf vendor/ - > composer update +.. code-block:: console + + composer self-update + rm -rf vendor/ + composer update 必备文件变更 ********************** diff --git a/source/installation/upgrade_435.rst b/source/installation/upgrade_435.rst index a88e667e0..03a22eaa9 100644 --- a/source/installation/upgrade_435.rst +++ b/source/installation/upgrade_435.rst @@ -18,7 +18,20 @@ 验证占位符 ======================= -- 为了安全地使用 :ref:`validation-placeholders`,请记得为你将用作占位符的字段创建一个验证规则。 +为了安全地使用 :ref:`validation-placeholders`,请记得为你将用作占位符的字段创建一个验证规则。 + +例如,如果你有以下代码:: + + $validation->setRules([ + 'email' => 'required|max_length[254]|valid_email|is_unique[users.email,id,{id}]', + ]); + +你需要为 ``{id}`` 添加规则:: + + $validation->setRules([ + 'id' => 'max_length[19]|is_natural_no_zero', // Add this + 'email' => 'required|max_length[254]|valid_email|is_unique[users.email,id,{id}]', + ]); Session::stop() =============== diff --git a/source/installation/upgrade_437.rst b/source/installation/upgrade_437.rst new file mode 100644 index 000000000..222366875 --- /dev/null +++ b/source/installation/upgrade_437.rst @@ -0,0 +1,82 @@ +############################# +从 4.3.6 升级到 4.3.7 +############################# + +请参考与你的安装方法相对应的升级说明。 + +- :ref:`使用 Composer 安装的应用程序启动器升级 ` +- :ref:`使用 Composer 安装的将 CodeIgniter4 添加到现有项目中升级 ` +- :ref:`手动安装升级 ` + +.. contents:: + :local: + :depth: 2 + +重大变更 +**************** + +.. _upgrade-437-feature-testing: + +功能测试请求体 +============================ + +如果你调用了以下方法: + +1. :ref:`withBody() ` +2. 并且 :ref:`withBodyFormat() ` +3. 并将 ``$params`` 传递给 :ref:`call() ` (或简写方法) + +则请求体的优先级已更改。如果你的测试代码受到此更改的影响,请进行修改。 + +例如,现在使用 ``$params`` 来构建请求体,而不使用 ``$body``:: + + $this->withBody($body)->withBodyFormat('json')->call('post', $params) + +以前,``$body`` 用于请求体。 + +Validation::loadRuleGroup() 的返回值 +=========================================== + +``Validation::loadRuleGroup()`` 的返回值已从 "**rules 数组**" 更改为 "**rules 数组** 和 **customErrors 数组**" 的 "**数组**"(``[rules, customErrors]``)。 + +如果你使用了该方法,请将代码更新如下:: + + $rules = $this->validation->loadRuleGroup($rules); + ↓ + [$rules, $customErrors] = $this->validation->loadRuleGroup($rules); + +项目文件 +************* + +**项目空间** (根目录、app、public、writable) 中的一些文件已经更新。由于这些文件位于 **system** 范围之外,因此不会在没有你干预的情况下进行更改。 + +有一些第三方 CodeIgniter 模块可用于帮助合并对项目空间的更改:`在 Packagist 上查看 `_。 + +内容更改 +=============== + +以下文件已经进行了重大更改(包括弃用或视觉调整),建议你将更新后的版本与你的应用程序合并: + +配置 +------ + +- app/Config/Kint.php + +所有更改 +=========== + +这是 **项目空间** 中所有已更改的文件的列表;其中许多只是注释或格式变化,对运行时没有影响: + +- app/Config/App.php +- app/Config/Autoload.php +- app/Config/Cache.php +- app/Config/ContentSecurityPolicy.php +- app/Config/Filters.php +- app/Config/Kint.php +- app/Config/Logger.php +- app/Config/Migrations.php +- app/Config/Modules.php +- app/Config/Paths.php +- app/Controllers/BaseController.php +- app/Controllers/Home.php +- composer.json diff --git a/source/installation/upgrade_438.rst b/source/installation/upgrade_438.rst new file mode 100644 index 000000000..8b8fa5632 --- /dev/null +++ b/source/installation/upgrade_438.rst @@ -0,0 +1,37 @@ +############################# +从 4.3.7 升级到 4.3.8 +############################# + +请参考与你的安装方法相对应的升级说明。 + +- :ref:`使用 Composer 安装的应用程序启动器升级 ` +- :ref:`使用 Composer 安装的将 CodeIgniter4 添加到现有项目中升级 ` +- :ref:`手动安装升级 ` + +.. contents:: + :local: + :depth: 2 + +项目文件 +************* + +**项目空间** (根目录、app、public、writable) 中的一些文件已经更新。由于这些文件位于 **system** 范围之外,因此不会在没有你干预的情况下进行更改。 + +有一些第三方 CodeIgniter 模块可用于帮助合并对项目空间的更改:`在 Packagist 上查看 `_。 + +内容更改 +=============== + +以下文件已经进行了重大更改(包括弃用或视觉调整),建议你将更新后的版本与你的应用程序合并: + +配置 +------ + +- composer.json + +所有更改 +=========== + +这是 **项目空间** 中所有已更改的文件的列表;其中许多只是注释或格式变化,对运行时没有影响: + +- composer.json diff --git a/source/installation/upgrade_440.rst b/source/installation/upgrade_440.rst new file mode 100644 index 000000000..2c5a03a40 --- /dev/null +++ b/source/installation/upgrade_440.rst @@ -0,0 +1,284 @@ +############################## +从 4.3.8 升级到 4.4.0 +############################## + +请参考与你的安装方法对应的升级说明。 + +- :ref:`使用 Composer 安装 App Starter 升级 ` +- :ref:`使用 Composer 安装将 CodeIgniter4 添加到现有项目并进行升级 ` +- :ref:`手动安装升级 ` + +.. contents:: + :local: + :depth: 2 + +安全性 +******** + +使用 $this->validate() 时 +============================ + +在 Controller 的 :ref:`$this->validate() ` 中存在已知的潜在漏洞,可绕过验证。 +攻击可以使开发人员误解未经验证的空数据为已验证数据并继续处理。 + +已添加 :ref:`Validation::getValidated() ` 方法,以确保获取已验证数据。 + +因此,在你的 Controllers 中使用 ``$this->validate()`` 时,应使用新的 ``Validation::getValidated()`` 方法获取已验证的数据。 + +.. literalinclude:: ../libraries/validation/045.php + :lines: 2- + +破坏性变更 +**************** + +.. _upgrade-440-uri-setsegment: + +URI::setSegment() 更改 +======================== + +由于以前版本中没有抛出异常,如果指定了最后一个段 ``+2``,此错误已经修复。 + +如果你的代码依赖于此错误,请修复段编号。 + +.. literalinclude:: upgrade_440/002.php + :lines: 2- + +站点 URI 更改 +================ + +- 由于对当前 URI 确定进行了重新制定,框架可能以与以前版本不同的方式返回站点 URI 或 URI 路径。这可能会破坏你的测试代码。如果现有测试失败,请更新断言。 +- 如果你的 baseURL 具有子目录,并且通过 ``URI::getPath()`` 方法获取当前 URI 的相对路径到 baseURL,你必须改用新的 ``SiteURI::getRoutePath()`` 方法。 + +有关详细信息,请参见 :ref:`v440-site-uri-changes`。 + +当你扩展异常时 +========================== + +如果你扩展了 ``CodeIgniter\Debug\Exceptions`` 并且未覆盖 ``exceptionHandler()`` 方法,那么在 **app/Config/Exceptions.php** 中定义新的 ``Config\Exceptions::handler()`` 方法将导致执行指定的异常处理程序。 + +你的覆盖代码将不再执行,因此请通过定义自己的异常处理程序进行必要的更改。 + +请参阅 :ref:`custom-exception-handlers` 了解详细信息。 + +自动路由(改进版)和 translateURIDashes +============================================== + +在使用自动路由(改进版)和 ``$translateURIDashes`` 为 true 时(``$routes->setTranslateURIDashes(true)``),在以前版本中由于错误,两个 URI 对应一个控制器方法,一个 URI 用于破折号(例如 **foo-bar**),另一个 URI 用于下划线(例如 **foo_bar**)。 + +此错误已经修复,现在不再支持下划线 URI(**foo_bar**)。 + +如果你有指向下划线 URI(**foo_bar**)的链接,请将其更新为破折号 URI(**foo-bar**)。 + +传递带有命名空间的类名到工厂时 +================================================== + +传递带有命名空间的类名到工厂的行为已更改。有关详细信息,请参见 :ref:`ChangeLog `。 + +如果你有类似于 ``model(\Myth\Auth\Models\UserModel::class)`` 或 +``model('Myth\Auth\Models\UserModel')`` 的代码(代码可能在第三方包中),并且希望加载你的 ``App\Models\UserModel``,你需要在加载该类之前定义要加载的类名:: + + Factories::define('models', 'Myth\Auth\Models\UserModel', 'App\Models\UserModel'); + +有关详细信息,请参见 :ref:`factories-defining-classname-to-be-loaded`。 + +接口更改 +================= + +已进行了一些接口更改。实现它们的类应该更新其 API 以反映更改。有关详细信息,请参见 :ref:`v440-interface-changes`。 + +方法签名更改 +======================== + +已进行了一些方法签名更改。扩展它们的类应该更新其 API 以反映更改。有关详细信息,请参见 :ref:`v440-method-signature-changes`。 + +此外,某些构造函数和 ``Services::security()`` 的参数类型已更改。如果你使用这些参数调用它们,请更改参数值。有关详细信息,请参见 :ref:`v440-parameter-type-changes`。 + +RouteCollection::$routes +======================== + +受保护属性 ``$routes`` 的数组结构已进行了修改以提高性能。 + +如果你扩展了 ``RouteCollection`` 并使用了 ``$routes``,请更新你的代码以匹配新的数组结构。 + +必要的文件更改 +********************** + +index.php 和 spark +=================== + +以下文件已经接收到重大更改,**你必须将更新后的版本与你的应用程序合并**: + +- ``public/index.php`` (还请参阅 :ref:`v440-codeigniter-and-exit` ) +- ``spark`` + +.. important:: 如果你不更新上述文件,运行 ``composer update`` 后 CodeIgniter 将无法正常工作。 + + 升级过程,例如如下: + + .. code-block:: console + + composer update + cp vendor/codeigniter4/framework/public/index.php public/index.php + cp vendor/codeigniter4/framework/spark spark + +配置文件 +============ + +app/Config/App.php +------------------ + +属性 ``$proxyIPs`` 必须是数组。如果你不使用代理服务器,则它必须为 ``public array $proxyIPs = [];``。 + +.. _upgrade-440-config-routing: + +app/Config/Routing.php +---------------------- + +为了清理路由系统,进行了以下更改: + +- 新的 **app/Config/Routing.php** 文件保存了以前在 Routes 文件中的设置。 +- **app/Config/Routes.php** 文件经过简化,仅包含路由,没有设置和冗余的内容。 +- 不再自动加载特定于环境的路由文件。 + +因此,你需要执行以下操作: + +1. 从新框架中复制 **app/Config/Routing.php** 到你的 **app/Config** 目录,并进行配置。 +2. 删除不再需要的 **app/Config/Routes.php** 中的所有设置。 +3. 如果使用特定于环境的路由文件,请将它们添加到 **app/Config/Routing.php** 中的 ``$routeFiles`` 属性中。 + +app/Config/Toolbar.php +---------------------- + +你需要添加新属性 ``$watchedDirectories`` 和 ``$watchedExtensions`` 以进行 :ref:`debug-toolbar-hot-reload`:: + + --- a/app/Config/Toolbar.php + +++ b/app/Config/Toolbar.php + @@ -88,4 +88,31 @@ class Toolbar extends BaseConfig + * `$maxQueries` defines the maximum amount of queries that will be stored. + */ + public int $maxQueries = 100; + + + + /** + + * -------------------------------------------------------------------------- + + * Watched Directories + + * -------------------------------------------------------------------------- + + * + + * Contains an array of directories that will be watched for changes and + + * used to determine if the hot-reload feature should reload the page or not. + + * We restrict the values to keep performance as high as possible. + + * + + * NOTE: The ROOTPATH will be prepended to all values. + + */ + + public array $watchedDirectories = [ + + 'app', + + ]; + + + + /** + + * -------------------------------------------------------------------------- + + * Watched File Extensions + + * -------------------------------------------------------------------------- + + * + + * Contains an array of file extensions that will be watched for changes and + + * used to determine if the hot-reload feature should reload the page or not. + + */ + + public array $watchedExtensions = [ + + 'php', 'css', 'js', 'html', 'svg', 'json', 'env', + + ]; + } + +app/Config/Events.php +--------------------- + +你需要添加代码以为 :ref:`debug-toolbar-hot-reload` 添加一个路由:: + + --- a/app/Config/Events.php + +++ b/app/Config/Events.php + @@ -4,6 +4,7 @@ namespace Config; + + use CodeIgniter\Events\Events; + use CodeIgniter\Exceptions\FrameworkException; + +use CodeIgniter\HotReloader\HotReloader; + + /* + * -------------------------------------------------------------------- + @@ -44,5 +45,11 @@ Events::on('pre_system', static function () { + if (CI_DEBUG && ! is_cli()) { + Events::on('DBQuery', 'CodeIgniter\Debug\Toolbar\Collectors\Database::collect'); + Services::toolbar()->respond(); + + // Hot Reload route - for framework use on the hot reloader. + + if (ENVIRONMENT === 'development') { + + Services::routes()->get('__hot-reload', static function () { + + (new HotReloader())->run(); + + }); + + } + } + }); + +app/Config/Cookie.php +--------------------- + +**app/Config/App.php** 中的 Cookie 配置项不再使用。 + +1. 从新框架中复制 **app/Config/Cookie.php** 到你的 **app/Config** 目录,并进行配置。 +2. 删除 **app/Config/App.php** 中的属性(从 ``$cookiePrefix`` 到 ``$cookieSameSite``)。 + +app/Config/Security.php +----------------------- + +**app/Config/App.php** 中的 CSRF 配置项不再使用。 + +1. 从新框架中复制 **app/Config/Security.php** 到你的 **app/Config** 目录,并进行配置。 +2. 删除 **app/Config/App.php** 中的属性(从 ``$CSRFTokenName`` 到 ``$CSRFSameSite``)。 + +app/Config/Session.php +---------------------- + +**app/Config/App.php** 中的 Session 配置项不再使用。 + +1. 从新框架中复制 **app/Config/Session.php** 到你的 **app/Config** 目录,并进行配置。 +2. 删除 **app/Config/App.php** 中的属性(从 ``$sessionDriver`` 到 ``$sessionDBGroup``)。 + +重大改进 +********************* + +- **路由:** ``RouteCollection::__construct()`` 的方法签名已更改。添加了第三个参数 ``Routing $routing``。扩展类应该同样添加参数以不违反 LSP。 +- **验证:** ``Validation::check()`` 的方法签名已更改。``$rule`` 参数上的 ``string`` 类型提示已被删除。扩展类应该同样删除类型提示以不违反 LSP。 + +项目文件 +************* + +**项目空间** 中的一些文件(根目录、app、public、writable)已接收到更新。由于这些文件位于 **system** 范围之外,它们将不会在没有你干预的情况下更改。 + +有一些第三方 CodeIgniter 模块可帮助你合并对项目空间的更改:[Packagist 上探索](https://packagist.org/explore/?query=codeigniter4%20updates)。 + +内容更改 +=============== + +以下文件已接收到重大更改(包括弃用或视觉调整),建议你将更新后的版本与你的应用程序合并: + +配置 +------ + +- app/Config/CURLRequest.php + - :ref:`$shareOptions ` 的默认值已更改为 ``false``。 +- app/Config/Exceptions.php + - 添加了新方法 ``handler()``,定义自定义异常处理程序。 + 请参阅 :ref:`custom-exception-handlers`。 + +所有更改 +=========== + +这是 **项目空间** 中所有文件的更改列表;其中许多将是对运行时没有影响的注释或格式化: + +- app/Config/App.php +- app/Config/CURLRequest.php +- app/Config/Cookie.php +- app/Config/Database.php +- app/Config/Events.php +- app/Config/Exceptions.php +- app/Config/Filters.php +- app/Config/Routes.php +- app/Config/Routing.php +- app/Config/Toolbar.php +- public/index.php +- spark diff --git a/source/installation/upgrade_440/002.php b/source/installation/upgrade_440/002.php new file mode 100644 index 000000000..9cabdfcfc --- /dev/null +++ b/source/installation/upgrade_440/002.php @@ -0,0 +1,12 @@ +setSegment(4, 'three'); +// The URI will be http://example.com/one/two/three + +// After: +$uri->setSegment(4, 'three'); // Will throw Exception +$uri->setSegment(3, 'three'); +// The URI will be http://example.com/one/two/three diff --git a/source/installation/upgrade_441.rst b/source/installation/upgrade_441.rst new file mode 100644 index 000000000..8a3d0417c --- /dev/null +++ b/source/installation/upgrade_441.rst @@ -0,0 +1,38 @@ +############################# +从 4.4.0 升级到 4.4.1 +############################# + +请参考与你的安装方法相对应的升级说明。 + +- :ref:`使用 Composer 安装 App Starter 升级 ` +- :ref:`使用 Composer 安装将 CodeIgniter4 添加到现有项目并进行升级 ` +- :ref:`手动安装升级 ` + +.. contents:: + :local: + :depth: 2 + +项目文件 +************* + +**项目空间** (根目录、app、public、writable) 中的一些文件已经更新。由于这些文件位于 **system** 范围之外,如果没有你的干预,它们将不会更改。 + +有一些第三方的 CodeIgniter 模块可用于帮助合并项目空间的更改:`在 Packagist 上查看 `_。 + +内容更改 +=============== + +版本 4.4.1 没有更改项目文件中的任何可执行代码。 + +所有更改 +=========== + +这是 **项目空间** 中所有已更改的文件列表;其中许多只是注释或格式化的简单更改,对运行时没有影响: + +- app/Config/Autoload.php +- app/Config/DocTypes.php +- app/Config/Email.php +- app/Config/ForeignCharacters.php +- app/Config/Mimes.php +- app/Config/Modules.php +- composer.json diff --git a/source/installation/upgrade_442.rst b/source/installation/upgrade_442.rst new file mode 100644 index 000000000..87e5c3ac7 --- /dev/null +++ b/source/installation/upgrade_442.rst @@ -0,0 +1,29 @@ +############################# +从 4.4.1 升级到 4.4.2 +############################# + +请参考与你的安装方法相对应的升级说明。 + +- :ref:`使用 Composer 安装 App Starter 升级 ` +- :ref:`使用 Composer 安装将 CodeIgniter4 添加到现有项目并进行升级 ` +- :ref:`手动安装升级 ` + +.. contents:: + :local: + :depth: 2 + +项目文件 +************* + +**项目空间** (根目录、app、public、writable) 中的一些文件已经更新。由于这些文件位于 **system** 范围之外,如果没有你的干预,它们将不会更改。 + +有一些第三方的 CodeIgniter 模块可用于帮助合并项目空间的更改:`在 Packagist 上查看 `_。 + +所有更改 +=========== + +这是 **项目空间** 中所有已更改的文件列表;其中许多只是注释或格式化的简单更改,对运行时没有影响: + +- app/Config/Migrations.php +- app/Config/View.php +- composer.json diff --git a/source/installation/upgrade_file_upload.rst b/source/installation/upgrade_file_upload.rst index 19e194b39..41b1081bb 100644 --- a/source/installation/upgrade_file_upload.rst +++ b/source/installation/upgrade_file_upload.rst @@ -7,7 +7,7 @@ 文档 ============== -- `CodeIgniter 3.X 输出类文档 `_ +- `CodeIgniter 3.X 文件上传类文档 `_ - :doc:`CodeIgniter 4.X 上传文件处理文档 ` 变更点 diff --git a/source/installation/upgrade_migrations.rst b/source/installation/upgrade_migrations.rst index b1693b798..d295b906b 100644 --- a/source/installation/upgrade_migrations.rst +++ b/source/installation/upgrade_migrations.rst @@ -16,9 +16,11 @@ - 首先,迁移文件的顺序命名(``001_create_users``、``002_create_posts``)不再被支持。CodeIgniter 4 版本仅支持时间戳方案(``20121031100537_create_users``、``20121031500638_create_posts``)。如果使用了顺序命名,则需要重命名每个迁移文件。 - 迁移表定义已更改。如果从 CI3 升级到 CI4 并使用相同的数据库,则需要升级迁移表定义及其数据。 -- 迁移过程也已更改。你现在可以使用简单的 CLI 命令迁移数据库:: +- 迁移过程也已更改。你现在可以使用简单的 CLI 命令迁移数据库: - > php spark migrate +.. code-block:: console + + php spark migrate 升级指南 ============= diff --git a/source/installation/upgrade_validations.rst b/source/installation/upgrade_validations.rst index de8b42b16..1641ae28b 100644 --- a/source/installation/upgrade_validations.rst +++ b/source/installation/upgrade_validations.rst @@ -18,6 +18,7 @@ 请使用 :ref:`规则类 ` 或 :ref:`闭包规则 ` 代替。 +- 在 CI3 中,回调/可调用规则具有优先级,但在 CI4 中,闭包规则没有优先级,并且按照它们在列表中的顺序进行检查。 - CI4 验证格式规则不允许为空字符串。 - CI4 验证永远不会改变你的数据。 - 从 v4.3.0 开始,引入了 :php:func:`validation_errors()`,但 API 与 CI3 的不同。 diff --git a/source/installation/upgrading.rst b/source/installation/upgrading.rst index 7f621c68b..498cdfa75 100755 --- a/source/installation/upgrading.rst +++ b/source/installation/upgrading.rst @@ -14,6 +14,11 @@ backward_compatibility_notes + upgrade_442 + upgrade_441 + upgrade_440 + upgrade_438 + upgrade_437 upgrade_436 upgrade_435 upgrade_434 diff --git a/source/intro/requirements.rst b/source/intro/requirements.rst index 78dd47373..1e226a958 100755 --- a/source/intro/requirements.rst +++ b/source/intro/requirements.rst @@ -16,6 +16,8 @@ PHP 及所需扩展 - `mbstring `_ - `json `_ +.. warning:: PHP 7.4 的生命周期结束日期是 2022 年 11 月 28 日。如果你仍在使用 PHP 7.4,应立即升级。PHP 8.0 的生命周期结束日期将是 2023 年 11 月 26 日。 + *********************** 可选的 PHP 扩展 *********************** diff --git a/source/libraries/caching.rst b/source/libraries/caching.rst index f526604e7..379fe55fa 100755 --- a/source/libraries/caching.rst +++ b/source/libraries/caching.rst @@ -20,6 +20,8 @@ CodeIgniter 提供了一些最常用的快速动态缓存的封装。除基于 .. literalinclude:: caching/002.php +.. _libraries-caching-configuring-the-cache: + ********************* 配置缓存 ********************* @@ -73,16 +75,20 @@ CodeIgniter 提供了几个可以从命令行使用的 :doc:`commands php spark cache:clear + php spark cache:clear cache:info ========== -显示当前系统中的文件缓存信息:: +显示当前系统中的文件缓存信息: - > php spark cache:info +.. code-block:: console + + php spark cache:info .. note:: 这个命令只支持 File 缓存处理程序。 @@ -286,9 +292,11 @@ Redis 是一个内存中的键值存储,可以以 LRU 缓存模式运行。要 Predis 缓存 ============== -Predis 是一个用于 Redis 键值存储的灵活且功能完善的 PHP 客户端库。要使用它,从项目根目录的命令行中运行:: +Predis 是一个用于 Redis 键值存储的灵活且功能完善的 PHP 客户端库。要使用它,从项目根目录的命令行中运行: + +.. code-block:: console - > composer require predis/predis + composer require predis/predis 有关 Redis 的更多信息,请查看 `https://github.com/nrk/predis `_。 diff --git a/source/libraries/cookies.rst b/source/libraries/cookies.rst index 2fcb0b4fe..4dac25b34 100644 --- a/source/libraries/cookies.rst +++ b/source/libraries/cookies.rst @@ -10,7 +10,7 @@ Cookie 主要用于三个目的: - **个性化**:用户首选项、主题和其他设置 - **跟踪**:记录和分析用户行为 -为了帮助你在请求和响应中跨浏览器高效使用 cookie,CodeIgniter 提供了 ``CodeIgniter\Cookie\Cookie`` 类来抽象 cookie 的交互。 +为了帮助你有效地向浏览器发送 Cookie,CodeIgniter 提供了 ``CodeIgniter\Cookie\Cookie`` 类来抽象化 Cookie 的交互。 .. contents:: :local: @@ -24,11 +24,21 @@ Cookie 主要用于三个目的: .. literalinclude:: cookies/001.php -在构造 ``Cookie`` 对象时,只需要 ``name`` 属性是必需的。其余的都是可选的。如果没有修改可选属性,它们的值将由 ``Cookie`` 类中保存的默认值填充。要覆盖类中当前存储的默认值,你可以传递一个 ``Config\Cookie`` 实例或默认值数组给静态 ``Cookie::setDefaults()`` 方法。 +在构造 ``Cookie`` 对象时,只需要 ``name`` 属性是必需的。其余的都是可选的。如果没有修改可选属性,它们的值将由 ``Cookie`` 类中保存的默认值填充。 + +覆盖默认值 +=================== + +要覆盖类中当前存储的默认值,你可以传递一个 ``Config\Cookie`` 实例或默认值数组给静态 ``Cookie::setDefaults()`` 方法。 .. literalinclude:: cookies/002.php -将 ``Config\Cookie`` 实例或数组传递给 ``Cookie::setDefaults()`` 将有效地覆盖你的默认值,并且持续到新的默认值被传递。如果你不想要这种行为,而只是想在有限的时间内更改默认值,你可以利用 ``Cookie::setDefaults()`` 的返回值,它返回旧的默认值数组。 +将 ``Config\Cookie`` 实例或数组传递给 ``Cookie::setDefaults()`` 将有效地覆盖你的默认值,并且持续到新的默认值被传递。 + +有限的时间内更改默认值 +------------------------------------ + +如果你不想要这种行为,而只是想在有限的时间内更改默认值,你可以利用 ``Cookie::setDefaults()`` 的返回值,它返回旧的默认值数组。 .. literalinclude:: cookies/003.php @@ -65,7 +75,7 @@ cookie 名称可以是任何 US-ASCII 字符,以下字符除外: - 空格或制表符; - 分隔符,例如 ``( ) < > @ , ; : \ " / [ ] ? = { }`` -如果将 ``$raw`` 参数设置为 ``true``,则会严格进行此验证。这是因为 PHP 的 ``setcookie`` 和 ``setrawcookie`` 会拒绝具有无效名称的 cookie。另外,cookie 名称不能为空字符串。 +如果将 ``$raw`` 参数设置为 ``true``,则会严格进行此验证。这是因为 PHP 的 `setcookie() `_ 和 `setrawcookie() `_ 会拒绝具有无效名称的 cookie。另外,cookie 名称不能为空字符串。 验证前缀属性 =============================== @@ -95,19 +105,43 @@ SameSite 属性只接受三个值: .. literalinclude:: cookies/006.php +*************** +发送 Cookies +*************** + +将 ``Cookie`` 对象设置在 Response 对象的 ``CookieStore`` 中,框架会自动发送 Cookies。 + +使用 :php:meth:`CodeIgniter\\HTTP\\Response::setCookie()` 来设置: + +.. literalinclude:: cookies/017.php + +你也可以使用 :php:func:`set_cookie()` 辅助函数: + +.. literalinclude:: cookies/018.php + ********************** 使用 Cookie 存储 ********************** -``CookieStore`` 类表示 ``Cookie`` 对象的一个不可变集合。可以从当前的 ``Response`` 对象访问 ``CookieStore`` 实例。 +.. note:: 通常情况下,不需要直接使用 CookieStore。 + +``CookieStore`` 类表示 ``Cookie`` 对象的一个不可变集合。 + +从 Response 获取存储 +=============================== + +可以从当前的 ``Response`` 对象访问 ``CookieStore`` 实例。 .. literalinclude:: cookies/007.php +创建 CookieStore +==================== + CodeIgniter 提供了另外三种创建 ``CookieStore`` 新实例的方法。 .. literalinclude:: cookies/008.php -.. note:: 在使用全局 ``cookies()`` 函数时,只有在第二个参数 ``$getGlobal`` 设置为 ``false`` 时,才会考虑传递的 ``Cookie`` 数组。 +.. note:: 在使用全局 :php:func:`cookies()` 函数时,只有在第二个参数 ``$getGlobal`` 设置为 ``false`` 时,才会考虑传递的 ``Cookie`` 数组。 检查存储中的 Cookie ========================= @@ -135,7 +169,7 @@ CodeIgniter 提供了另外三种创建 ``CookieStore`` 新实例的方法。 .. literalinclude:: cookies/013.php -.. note:: 帮助函数 ``get_cookie()`` 从当前的 ``Request`` 对象获取 cookie,而不是从 ``Response`` 获取。如果该 cookie 已设置,此函数会检查 ``$_COOKIE`` 数组并立即获取它。 +.. note:: 辅助函数 :php:func:`get_cookie()` 从当前的 ``Request`` 对象获取 cookie,而不是从 ``Response`` 获取。如果该 cookie 已设置,此函数会检查 ``$_COOKIE`` 数组并立即获取它。 在存储中添加/删除 Cookie ================================ @@ -154,6 +188,10 @@ CodeIgniter 提供了另外三种创建 ``CookieStore`` 新实例的方法。 分派存储中的 Cookie ============================= +.. deprecated:: 4.1.6 + +.. important:: 该方法已被弃用。将在未来的版本中移除。 + 更多时候,你不需要自己手动发送 cookie。CodeIgniter 会为你做这件事。但是,如果你真的需要手动发送 cookie,你可以使用 ``dispatch`` 方法。就像发送其他标头一样,你需要确保标头还未发送,方法是检查 ``headers_sent()`` 的值。 .. literalinclude:: cookies/016.php @@ -189,11 +227,11 @@ Cookie 个性化 .. php:staticmethod:: setDefaults([$config = []]) - :param \Config\Cookie|array $config: 配置数组或实例 + :param \\Config\\Cookie|array $config: 配置数组或实例 :rtype: array :returns: 旧的默认值 - 通过从 ``\Config\Cookie`` 配置或数组中注入值来设置 Cookie 实例的默认属性。 + 通过从 ``Config\Cookie`` 配置或数组中注入值来设置 Cookie 实例的默认属性。 .. php:staticmethod:: fromHeaderString(string $header[, bool $raw = false]) diff --git a/source/libraries/cookies/004.php b/source/libraries/cookies/004.php index ca0cf0a74..cce1ea2c7 100644 --- a/source/libraries/cookies/004.php +++ b/source/libraries/cookies/004.php @@ -19,19 +19,19 @@ ] ); -$cookie->getName(); // 'remember_token' -$cookie->getPrefix(); // '__Secure-' -$cookie->getPrefixedName(); // '__Secure-remember_token' +$cookie->getName(); // 'remember_token' +$cookie->getPrefix(); // '__Secure-' +$cookie->getPrefixedName(); // '__Secure-remember_token' $cookie->getExpiresTimestamp(); // Unix timestamp -$cookie->getExpiresString(); // 'Fri, 14-Feb-2025 00:00:00 GMT' -$cookie->isExpired(); // false -$cookie->getMaxAge(); // the difference from time() to expires -$cookie->isRaw(); // false -$cookie->isSecure(); // true -$cookie->getPath(); // '/' -$cookie->getDomain(); // '' -$cookie->isHTTPOnly(); // true -$cookie->getSameSite(); // 'Lax' +$cookie->getExpiresString(); // 'Fri, 14-Feb-2025 00:00:00 GMT' +$cookie->isExpired(); // false +$cookie->getMaxAge(); // the difference from time() to expires +$cookie->isRaw(); // false +$cookie->isSecure(); // true +$cookie->getPath(); // '/' +$cookie->getDomain(); // '' +$cookie->isHTTPOnly(); // true +$cookie->getSameSite(); // 'Lax' // additional getter $cookie->getId(); // '__Secure-remember_token;;/' diff --git a/source/libraries/cookies/006.php b/source/libraries/cookies/006.php index a8be634a9..1bf6c732b 100644 --- a/source/libraries/cookies/006.php +++ b/source/libraries/cookies/006.php @@ -2,6 +2,6 @@ use CodeIgniter\Cookie\Cookie; -Cookie::SAMESITE_LAX; // 'lax' -Cookie::SAMESITE_STRICT; // 'strict' -Cookie::SAMESITE_NONE; // 'none' +Cookie::SAMESITE_LAX; // 'lax' +Cookie::SAMESITE_STRICT; // 'strict' +Cookie::SAMESITE_NONE; // 'none' diff --git a/source/libraries/cookies/017.php b/source/libraries/cookies/017.php new file mode 100644 index 000000000..eadbc8083 --- /dev/null +++ b/source/libraries/cookies/017.php @@ -0,0 +1,16 @@ + 3600 * 2, // Expires in 2 hours + ] +); + +$response->setCookie($cookie); diff --git a/source/libraries/cookies/018.php b/source/libraries/cookies/018.php new file mode 100644 index 000000000..41dff8d6a --- /dev/null +++ b/source/libraries/cookies/018.php @@ -0,0 +1,15 @@ + 3600 * 2, // Expires in 2 hours + ] +); + +set_cookie($cookie); diff --git a/source/libraries/curlrequest.rst b/source/libraries/curlrequest.rst index 070b5d8ca..12d6fdc52 100755 --- a/source/libraries/curlrequest.rst +++ b/source/libraries/curlrequest.rst @@ -16,15 +16,19 @@ CURLRequest 类 CURLRequest 配置 ********************** +.. _curlrequest-sharing-options: + 共享选项 =============== -由于历史原因,默认情况下,CURLRequest 在请求之间共享所有选项。如果你使用该类的一个实例发送多个请求,这种行为可能会导致错误请求出现不必要的头和消息体。 +.. note:: 自 v4.4.0 起,默认值已更改为 ``false``。此设置仅用于向后兼容。新用户无需更改此设置。 -你可以通过在 **app/Config/CURLRequest.php** 中将以下配置参数值编辑为 ``false`` 来更改此行为: +如果你想在多个请求之间共享所有选项,请在 **app/Config/CURLRequest.php** 中将 ``$shareOptions`` 设置为 ``true``: .. literalinclude:: curlrequest/001.php +如果你使用该类的实例发送多个请求,此行为可能会导致发送不必要的头部和正文的错误请求。 + .. note:: 在 v4.2.0 之前,即使 ``$shareOptions`` 为 false,由于一个 bug,请求消息体也不会被重置。 ******************* @@ -172,14 +176,16 @@ connect_timeout cookie ====== -这指定了 CURL 应该使用的文件名,用于读取和保存 cookie 值。这是通过 CURL_COOKIEJAR 和 CURL_COOKIEFILE 选项完成的。例如: +这指定了 CURL 应该使用的文件名,用于读取和保存 cookie 值。这是通过 ``CURL_COOKIEJAR`` 和 ``CURL_COOKIEFILE`` 选项完成的。例如: .. literalinclude:: curlrequest/021.php debug ===== -当 ``debug`` 被传递并设置为 ``true`` 时,这将在脚本执行期间启用写入 STDERR 的其他调试信息。这是通过传递 CURLOPT_VERBOSE 并回显输出来完成的。因此,当你通过 ``spark serve`` 运行内置服务器时,你会在控制台中看到输出。否则,输出将被写入服务器的错误日志中。 +当 ``debug`` 被传递并设置为 ``true`` 时,这将在脚本执行期间启用写入 STDERR 的其他调试信息。 + +这是通过传递 ``CURLOPT_VERBOSE`` 并回显输出来完成的。因此,当你通过 ``spark serve`` 运行内置服务器时,你会在控制台中看到输出。否则,输出将被写入服务器的错误日志中。 .. literalinclude:: curlrequest/034.php @@ -217,7 +223,9 @@ headers http_errors =========== -默认情况下,如果返回的 HTTP 代码大于或等于 400,CURLRequest 将失败。你可以将 ``http_errors`` 设置为 ``false`` 来改为返回内容: +默认情况下,如果返回的 HTTP 状态码大于等于 400,CURLRequest 将抛出 ``HTTPException`` 异常。 + +如果你想查看响应正文,可以将 ``http_errors`` 设置为 ``false``,以返回内容而不是抛出异常: .. literalinclude:: curlrequest/026.php @@ -240,6 +248,17 @@ multipart .. note:: ``multipart`` 不能与 ``form_params`` 选项一起使用。你只能使用其中一个。对 ``application/x-www-form-urlencoded`` 请求使用 ``form_params``,对 ``multipart/form-data`` 请求使用 ``multipart``。 +.. _curlrequest-request-options-proxy: + +proxy +===== + +.. versionadded:: 4.4.0 + +你可以通过将关联数组作为 ``proxy`` 选项来设置代理: + +.. literalinclude:: curlrequest/035.php + query ===== diff --git a/source/libraries/curlrequest/001.php b/source/libraries/curlrequest/001.php index e89e06924..303366336 100644 --- a/source/libraries/curlrequest/001.php +++ b/source/libraries/curlrequest/001.php @@ -6,7 +6,6 @@ class CURLRequest extends BaseConfig { - public $shareOptions = false; - // ... + public bool $shareOptions = true; } diff --git a/source/libraries/curlrequest/026.php b/source/libraries/curlrequest/026.php index 36ea74cb9..6c4289454 100644 --- a/source/libraries/curlrequest/026.php +++ b/source/libraries/curlrequest/026.php @@ -1,8 +1,9 @@ request('GET', '/status/500'); -// Will fail verbosely +// If the response code is 500, an HTTPException is thrown, +// and a detailed error report is displayed if in development mode. -$res = $client->request('GET', '/status/500', ['http_errors' => false]); -echo $res->getStatusCode(); -// 500 +$response = $client->request('GET', '/status/500', ['http_errors' => false]); +echo $response->getStatusCode(); // 500 +echo $response->getBody(); // You can see the response body. diff --git a/source/libraries/curlrequest/035.php b/source/libraries/curlrequest/035.php new file mode 100644 index 000000000..a729f9f3b --- /dev/null +++ b/source/libraries/curlrequest/035.php @@ -0,0 +1,7 @@ +request( + 'GET', + 'http://example.com', + ['proxy' => 'http://localhost:3128'] +); diff --git a/source/libraries/email.rst b/source/libraries/email.rst index 0a32bbd5b..3233bf59a 100644 --- a/source/libraries/email.rst +++ b/source/libraries/email.rst @@ -55,6 +55,8 @@ CodeIgniter 强大的 Email 类支持以下功能: **app/Config/Email.php** 文件,并在电子邮件属性中设置你的配置。然后保存文件,它将被自动使用。 如果你在配置文件中设置了首选项,将 **不需要** 使用 ``$email->initialize()`` 方法。 +.. _email-ssl-tls-for-smtp: + SMTP 协议的 SSL 与 TLS -------------------------------- @@ -64,7 +66,7 @@ SMTP 协议的 SSL 与 TLS 关键差异在于端口 465 要求从一开始就使用 TLS 按照 `RFC 8314 `_ 来保护通信通道。而端口 587 上的连接允许明文连接,之后会使用 ``STARTTLS`` SMTP 命令升级通道以使用加密。 -端口 465 上的连接是否支持升级可能由服务器决定,所以如果服务器不允许, ``STARTTLS`` SMTP 命令可能会失败。如果你将端口设置为 465,你应该尝试让 ``SMTPCrypto`` 设置为空,因为通信从一开始就是用 TLS 保护的,不需要 ``STARTTLS``。 +端口 465 上的连接是否支持升级可能由服务器决定,所以如果服务器不允许, ``STARTTLS`` SMTP 命令可能会失败。如果你将端口设置为 465,你应该尝试设置 ``SMTPCrypto`` 为空字符串(``''``),因为通信从一开始就是用 TLS 保护的,不需要 ``STARTTLS``。 如果你的配置要求你连接到端口 587,你最好将 ``SMTPCrypto`` 设置为 ``tls``,因为这将在与 SMTP 服务器通信时实现 ``STARTTLS`` 命令,将明文通道切换为加密通道。初始通信将是明文的,并使用 ``STARTTLS`` 命令将通道升级为 TLS。 @@ -87,13 +89,18 @@ SMTP 协议的 SSL 与 TLS **SMTPHost** 无默认值 无 SMTP 服务器地址。 **SMTPUser** 无默认值 无 SMTP 用户名。 **SMTPPass** 无默认值 无 SMTP 密码。 -**SMTPPort** 25 无 SMTP 端口。(如果设置为 465,不管 SMTPCrypto 设置如何,都将使用 TLS 建立连接) +**SMTPPort** 25 无 SMTP 端口。(如果设置为 ``465``,不管 ``SMTPCrypto`` 设置如何, + 都将使用 TLS 建立连接) **SMTPTimeout** 5 无 SMTP 超时(秒)。 **SMTPKeepAlive** false true 或 false(布尔值) 启用持久 SMTP 连接。 -**SMTPCrypto** 无默认值 tls 或 ssl SMTP 加密。将此设置为“ssl”将使用 SSL 创建到服务器的安全通道,“tls”将向服务器发出 ``STARTTLS`` 命令。连接端口 465 应将此留空。 +**SMTPCrypto** tls tls, ssl 或空字符串 SMTP 加密。将其设置为 ``ssl`` 将使用 SSL 创建与服务器的安全通道, + 而 ``tls`` 将向服务器发出 ``STARTTLS`` 命令。 + 在端口 465 上的连接应将其设置为空字符串(``''``)。 + 另请参阅 :ref:`email-ssl-tls-for-smtp`。 **wordWrap** true true 或 false(布尔值) 启用自动换行。 **wrapChars** 76 换行处的字符数。 -**mailType** text text 或 html 邮件类型。如果发送 HTML 电子邮件,你必须将其作为完整的网页发送。确保你没有任何相对链接或相对图像路径,否则它们将无法工作。 +**mailType** text text 或 html 邮件类型。如果发送 HTML 电子邮件,你必须将其作为完整的网页发送。 + 确保你没有任何相对链接或相对图像路径,否则它们将无法工作。 **charset** utf-8 字符集(utf-8、iso-8859-1 等)。 **validate** true true 或 false(布尔值) 是否验证电子邮件地址。 **priority** 3 1、2、3、4、5 电子邮件优先级。1 最高。5 最低。3 为正常。 diff --git a/source/libraries/images.rst b/source/libraries/images.rst index acb9e4cdf..e3f8cc17a 100755 --- a/source/libraries/images.rst +++ b/source/libraries/images.rst @@ -60,7 +60,9 @@ CodeIgniter 的图像处理类允许你执行以下操作: 图像质量 ============= -``save()`` 可以接受额外的参数 ``$quality`` 来更改结果图像的质量。值的范围从 0 到 100,默认值为 90。此参数仅适用于 JPEG 图像,否则将被忽略: +``save()`` 可以接受额外的参数 ``$quality`` 来更改结果图像的质量。值的范围从 0 到 100,默认值为 90。此参数仅适用于 JPEG 和 WEBP 图像,否则将被忽略: + +.. note:: 自 v4.4.0 起,WebP 格式可以使用 ``$quality`` 参数。 .. literalinclude:: images/005.php diff --git a/source/libraries/pagination.rst b/source/libraries/pagination.rst index 3a63eb4fc..545432de0 100755 --- a/source/libraries/pagination.rst +++ b/source/libraries/pagination.rst @@ -30,11 +30,39 @@ CodeIgniter 提供了一个非常简单但灵活的分页库,它易于主题化, 在这个示例中,我们首先创建 ``UserModel`` 的新实例。然后我们填充要发送到视图的数据。第一个元素是来自数据库的结果, **users**,它为正确的页面检索出 10 个用户每页。必须发送到视图的第二个项目是 Pager 实例本身。为方便起见,Model 将保存它使用的实例,并将其存储在公共属性 ``$pager`` 中。所以,我们获取它并将其赋值给视图中的 ``$pager`` 变量。 -.. important:: 重要的是要理解 ``Model::paginate()`` 方法使用 **Model** 和 **QueryBuilder** 方法。因此,试图使用 ``$db->query()`` 和 ``Model::paginate()`` 将 **不起作用**,因为 ``$db->query()`` 会立即执行查询,并且与 QueryBuilder 不关联。 +自定义分页查询 +================ -要在模型中定义分页条件,你可以: +要在模型中自定义分页查询,你可以在 ``paginate()`` 方法之前添加 :doc:`查询构建器 <../database/query_builder>` 方法。 + +添加 WHERE +------------ + +如果你想添加 WHERE 条件,可以直接指定条件: .. literalinclude:: pagination/003.php + :lines: 2- + +你还可以将条件移动到单独的方法中: + +.. literalinclude:: pagination/017.php + +.. literalinclude:: pagination/018.php + :lines: 2- + +添加 JOIN +--------- + +你可以连接另一个表: + +.. literalinclude:: pagination/016.php + +.. important:: 需要理解的重要一点是,``Model::paginate()`` 方法使用了 **模型** 和模型中的 **查询构建器** 实例。因此,尝试使用 ``Model::paginate()`` 与 :ref:`db-query` **不起作用**,因为 ``$db->query()`` 会立即执行查询,并且与查询构建器没有关联。 + +如果你需要一个无法使用查询构建器编写的复杂 SQL 查询,请尝试使用 :ref:`db-query` 和 `手动分页`_。 + +显示分页链接 +====================== 在视图内,我们然后需要告诉它在何处显示生成的链接:: diff --git a/source/libraries/pagination/002.php b/source/libraries/pagination/002.php index c8cd31ebe..c1bc43222 100644 --- a/source/libraries/pagination/002.php +++ b/source/libraries/pagination/002.php @@ -2,9 +2,7 @@ namespace App\Controllers; -use CodeIgniter\Controller; - -class UserController extends Controller +class UserController extends BaseController { public function index() { diff --git a/source/libraries/pagination/003.php b/source/libraries/pagination/003.php index 423adc232..6ea631c7a 100644 --- a/source/libraries/pagination/003.php +++ b/source/libraries/pagination/003.php @@ -1,26 +1,9 @@ $model->where('ban', 1)->paginate(10), 'pager' => $model->pager, ]; - -// You can move the conditions to a separate method. -// Model method -class UserModel extends Model -{ - public function banned() - { - $this->builder()->where('ban', 1); - - return $this; // This will allow the call chain to be used. - } -} - -$data = [ - 'users' => $model->banned()->paginate(10), - 'pager' => $model->pager, -]; diff --git a/source/libraries/pagination/004.php b/source/libraries/pagination/004.php index 75eb88c3e..a8ef4d296 100644 --- a/source/libraries/pagination/004.php +++ b/source/libraries/pagination/004.php @@ -2,9 +2,7 @@ namespace App\Controllers; -use CodeIgniter\Controller; - -class UserController extends Controller +class UserController extends BaseController { public function index() { diff --git a/source/libraries/pagination/015.php b/source/libraries/pagination/015.php index e6ea3e86c..ac9f4fc43 100644 --- a/source/libraries/pagination/015.php +++ b/source/libraries/pagination/015.php @@ -2,9 +2,7 @@ namespace App\Controllers; -use CodeIgniter\Controller; - -class UserController extends Controller +class UserController extends BaseController { public function index() { diff --git a/source/libraries/pagination/016.php b/source/libraries/pagination/016.php new file mode 100644 index 000000000..64be257b4 --- /dev/null +++ b/source/libraries/pagination/016.php @@ -0,0 +1,24 @@ +builder() + ->select('news.*, category.name') + ->join('category', 'news.category_id = category.id'); + + return [ + 'news' => $this->paginate($perPage), + 'pager' => $this->pager, + ]; + } +} diff --git a/source/libraries/pagination/017.php b/source/libraries/pagination/017.php new file mode 100644 index 000000000..86e656be5 --- /dev/null +++ b/source/libraries/pagination/017.php @@ -0,0 +1,17 @@ +builder()->where('ban', 1); + + return $this; // This will allow the call chain to be used. + } +} diff --git a/source/libraries/pagination/018.php b/source/libraries/pagination/018.php new file mode 100644 index 000000000..002d034a6 --- /dev/null +++ b/source/libraries/pagination/018.php @@ -0,0 +1,9 @@ + $model->banned()->paginate(10), + 'pager' => $model->pager, +]; diff --git a/source/libraries/publisher.rst b/source/libraries/publisher.rst index 13919ade5..9f8c771b8 100644 --- a/source/libraries/publisher.rst +++ b/source/libraries/publisher.rst @@ -57,9 +57,11 @@ Publisher 库提供了使用强大的检测和错误检查在项目内复制文 .. literalinclude:: publisher/006.php -大多数时候你不需要自己处理发现,只需使用提供的“publish”命令:: +大多数时候你不需要自己处理发现,只需使用提供的“publish”命令: - > php spark publish +.. code-block:: console + + php spark publish 默认情况下,在你的类扩展上 ``publish()`` 将从你的 ``$source`` 添加所有文件并合并到你的目标位置,在冲突时覆盖。 @@ -99,10 +101,14 @@ Publisher 库提供了使用强大的检测和错误检查在项目内复制文 .. literalinclude:: publisher/009.php -现在通过 Composer 添加依赖项并调用 ``spark publish`` 来运行发布:: +.. note:: 在执行命令之前,必须先创建目录 ``$destination``。 + +现在通过 Composer 添加依赖项并调用 ``spark publish`` 来运行发布: + +.. code-block:: console - > composer require twbs/bootstrap - > php spark publish + composer require twbs/bootstrap + php spark publish ... 然后你会在项目中得到类似下面的结果:: diff --git a/source/libraries/publisher/009.php b/source/libraries/publisher/009.php index 1e840ce9d..f3c2fb21d 100644 --- a/source/libraries/publisher/009.php +++ b/source/libraries/publisher/009.php @@ -13,7 +13,7 @@ class BootstrapPublisher extends Publisher * * @var string */ - protected $source = 'vendor/twbs/bootstrap/'; + protected $source = VENDORPATH . 'twbs/bootstrap/'; /** * FCPATH is always the default destination, diff --git a/source/libraries/security.rst b/source/libraries/security.rst index 5abc71170..618cc269f 100755 --- a/source/libraries/security.rst +++ b/source/libraries/security.rst @@ -173,6 +173,9 @@ HTML 表单 1. ``$_POST`` 数组 2. HTTP 头 3. ``php://input`` (JSON 请求) - 请记住,这种方法是最慢的,因为我们必须解码 JSON 然后重新编码它 +4. ``php://input`` (原始 body) - 适用于 PUT、PATCH 和 DELETE 类型的请求 + +.. note:: 自 v4.4.2 起,会检查 ``php://input`` (原始 body)。 ********************* 其他有用的方法 diff --git a/source/libraries/sessions.rst b/source/libraries/sessions.rst index 9c9e578b1..1a105a9ef 100755 --- a/source/libraries/sessions.rst +++ b/source/libraries/sessions.rst @@ -18,15 +18,15 @@ CodeIgniter 带有几个 session 存储驱动器,你可以在目录内容的最 Session 通常会与每个页面加载一起全局运行,所以 Session 类应该自动初始化。 -要访问和初始化 session: +要访问和初始化 Session: .. literalinclude:: sessions/001.php -``$config`` 参数是可选的 - 你的应用配置。如果没有提供,服务注册表将实例化你的默认配置。 +``$config`` 参数是可选的 - 你的应用配置。如果没有提供,服务注册器将实例化你的默认配置。 -加载后,可以使用 ``$session`` 访问 Session 库对象: +加载后,Session 库对象将可通过以下方式访问:: -.. literalinclude:: sessions/002.php + $session 另外,你可以使用辅助函数,它将使用默认配置选项。这个版本的可读性更好一些,但不接受任何配置选项。 @@ -155,6 +155,8 @@ CodeIgniter 通过相同的方式提供对其 Session 数据的访问,因为它 .. literalinclude:: sessions/018.php +.. _sessions-flashdata: + Flashdata ========= @@ -256,6 +258,24 @@ CodeIgniter 还支持 “tempdata”,也就是在特定过期时间后自动删 .. literalinclude:: sessions/036.php +关闭一个 Session +================= + +.. _session-close: + +close() +------- + +.. versionadded:: 4.4.0 + +在不再需要当前 Session 时,可以使用 ``close()`` 方法手动关闭 Session: + +.. literalinclude:: sessions/044.php + +你不必手动关闭 Session,PHP 会在脚本终止后自动关闭它。但是,由于 Session 数据被锁定以防止并发写入,因此一次只能有一个请求操作 Session。通过在所有对 Session 数据的更改完成后立即关闭 Session,可以提高网站性能。 + +此方法的工作方式与 PHP 的 `session_write_close() `_ 函数完全相同。 + 销毁一个 Session ==================== @@ -264,11 +284,16 @@ CodeIgniter 还支持 “tempdata”,也就是在特定过期时间后自动删 destroy() --------- -要清除当前 session(例如在退出登录时),可以使用 PHP 的 `session_destroy() `_ 函数或库的 ``destroy()`` 方法。两者的作用完全相同: +要清除当前 session(例如在退出登录时),可以使用类库的 ``destroy()`` 方法: .. literalinclude:: sessions/037.php -.. note:: 这必须是在同一请求期间执行的最后一个与 session 相关的操作。所有 session 数据(包括 flashdata 和 tempdata)将被永久销毁,并且在销毁 session 后,同一请求中的函数将不可用。 +此方法的工作方式与 PHP 的 `session_destroy() `_ 函数完全相同。 + +这必须是在同一请求中进行的最后一个与 Session 相关的操作。 +所有 Session 数据(包括 flashdata 和 tempdata)将被永久销毁。 + +.. note:: 你不必在常规代码中调用此方法。清理 Session 数据而不是销毁会话。 .. _session-stop: @@ -320,14 +345,16 @@ CodeIgniter 通常会使一切正常工作。但是,Session 是任何应用程 **savePath** null 无 根据所使用的驱动程序指定存储位置。 **matchIP** false true/false(布尔值) 从 session cookie 读取时是否验证用户的 IP 地址。 请注意,某些 ISP 会动态更改 IP,因此如果你需要一个不过期的 session,你可能会将此设置为 false。 -**timeToUpdate** 300 秒数(整数) 此选项控制 session 类重新生成自身和创建新的 session ID 的频率。将其设置为 0 将禁用 session ID 重新生成。 -**regenerateDestroy** false true/false(布尔值) 是否在自动重新生成 session ID 时销毁与旧 session ID 关联的数据。将其设置为 false 时,稍后数据将由垃圾收集器删除。 +**timeToUpdate** 300 秒数(整数) 此选项控制 session 类重新生成自身和创建新的 session ID 的频率。 + 将其设置为 0 将禁用 session ID 重新生成。 +**regenerateDestroy** false true/false(布尔值) 是否在自动重新生成 session ID 时销毁与旧 session ID 关联的数据。 + 将其设置为 false 时,稍后数据将由垃圾收集器删除。 ======================= ============================================ ================================================= ============================================================================================ .. note:: 作为最后的手段,如果上述任何内容都未配置,Session 库将尝试获取 PHP 的与 session 相关的 INI 设置,以及 CodeIgniter 3 设置,如 'sess_expire_on_close'。 但是,你永远不应该依赖这种行为,因为它可能会导致意外结果或在未来更改。请正确配置一切。 -.. note:: 如果 ``sessionExpiration`` 设置为 ``0``,则将原封不动地使用 PHP 在会话管理中设置的 ``session.gc_maxlifetime`` 设置(通常默认值为 ``1440``)。 +.. note:: 如果 ``expiration`` 设置为 ``0``,则将原封不动地使用 PHP 在会话管理中设置的 ``session.gc_maxlifetime`` 设置(通常默认值为 ``1440``)。 根据需要,这需要在 ``php.ini`` 或通过 ``ini_set()`` 进行更改。 此外,在你的 **app/Config/Cookie.php** 文件中使用了以下配置值用于 Session cookie: @@ -373,11 +400,13 @@ FileHandler 驱动程序(默认) 在类 UNIX 操作系统上,这通常通过使用 `chmod` 命令对该目录设置 0700 模式权限来实现,它仅允许目录所有者在其上执行读写操作。但是要小心,因为*运行*脚本的系统用户通常不是你自己,而是类似 'www-data' 的用户,所以只设置这些权限可能会中断你的应用程序。 -相反,你应该执行类似以下操作,这取决于你的环境:: +相反,你应该执行类似以下操作,这取决于你的环境: + +.. code-block:: console - > mkdir //writable/sessions/ - > chmod 0700 //writable/sessions/ - > chown www-data //writable/sessions/ + mkdir //writable/sessions/ + chmod 0700 //writable/sessions/ + chown www-data //writable/sessions/ 奖励提示 --------- @@ -393,7 +422,7 @@ FileHandler 驱动程序(默认) DatabaseHandler 驱动程序 ========================== -.. important:: 由于其他平台缺乏顾问锁定机制,因此仅正式支持 MySQL 和 PostgreSQL 数据库。在其他平台上使用不带锁定的会话可能会导致各种问题,特别是在大量使用 AJAX 的情况下,我们不会支持此类情况。如果遇到性能问题,请在处理完 session 数据后使用 ``session_write_close()``。 +.. important:: 由于其他平台缺乏顾问锁定机制,因此仅正式支持 MySQL 和 PostgreSQL 数据库。在其他平台上使用不带锁定的会话可能会导致各种问题,特别是在大量使用 AJAX 的情况下,我们不会支持此类情况。如果遇到性能问题,请在处理完 session 数据后使用 :ref:`session-close` 方法。 'DatabaseHandler' 驱动程序使用 MySQL 或 PostgreSQL 等关系数据库来存储会话。这对许多用户来说是一个流行的选择,因为它允许开发人员轻松访问应用程序中的 session 数据——它只是数据库中的另一个表。 @@ -404,10 +433,16 @@ DatabaseHandler 驱动程序 配置 DatabaseHandler ------------------------- +设置表名 +^^^^^^^^^^^^^^^^^^ + 为了使用 'DatabaseHandler' session 驱动程序,还必须创建我们已经提到的表,然后将其设置为你的 ``$savePath`` 值。例如,如果你想使用 'ci_sessions' 作为表名,你将执行以下操作: .. literalinclude:: sessions/039.php +创建数据库表 +^^^^^^^^^^^^^^^^^^^^^^^ + 然后当然,创建数据库表...... 对于 MySQL:: @@ -433,25 +468,42 @@ DatabaseHandler 驱动程序 .. note:: ``id`` 值包含 session cookie 名称(``Config\Session::$cookieName``)和 session ID 以及一个分隔符。根据需要应增加它,例如在使用长 session ID 时。 -你还需要根据你的 $matchIP 设置添加主键:: +添加主键 +^^^^^^^^^^^^^^^^^^ + +**根据你的 $matchIP 设置**,你还需要添加一个主键。以下示例适用于 MySQL 和 PostgreSQL:: - // 当 sessionMatchIP = true 时 + // 当 $matchIP = true 时 ALTER TABLE ci_sessions ADD PRIMARY KEY (id, ip_address); - // 当 sessionMatchIP = false 时 + // 当 $matchIP = false 时 ALTER TABLE ci_sessions ADD PRIMARY KEY (id); // 删除先前创建的主键(更改设置时使用) ALTER TABLE ci_sessions DROP PRIMARY KEY; -你可以通过向 **app/Config/Session.php** 添加带有要使用的组名称的新行来选择要使用的数据库组: +.. important:: 如果你没有添加正确的主键, + 可能会出现以下错误:: + + Uncaught mysqli_sql_exception: Duplicate entry 'ci_session:***' for key 'ci_sessions.PRIMARY' + +更改数据库组 +^^^^^^^^^^^^^^^^^^^^^^^ + +默认情况下使用默认数据库组。 +你可以通过更改 **app/Config/Session.php** 文件中的 ``$DBGroup`` 属性为要使用的组的名称来更改数据库组: .. literalinclude:: sessions/040.php -当然,如果你不想手动执行所有这些操作,可以使用 cli 中的 ``php spark make:migration --session`` 命令为你生成迁移文件:: +使用命令设置数据库表 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +当然,如果你不想手动执行所有这些操作,可以使用 cli 中的 ``php spark make:migration --session`` 命令为你生成迁移文件: + +.. code-block:: console - > php spark make:migration --session - > php spark migrate + php spark make:migration --session + php spark migrate 此命令将考虑 ``$savePath`` 和 ``$matchIP`` 设置并生成代码。 diff --git a/source/libraries/sessions/003.php b/source/libraries/sessions/003.php index ed979ddc0..13d9b068f 100644 --- a/source/libraries/sessions/003.php +++ b/source/libraries/sessions/003.php @@ -1,3 +1,3 @@ close(); diff --git a/source/libraries/sessions/037.php b/source/libraries/sessions/037.php index 1e7931f34..9f474a3c7 100644 --- a/source/libraries/sessions/037.php +++ b/source/libraries/sessions/037.php @@ -1,5 +1,3 @@ destroy(); diff --git a/source/libraries/sessions/044.php b/source/libraries/sessions/044.php new file mode 100644 index 000000000..13d9b068f --- /dev/null +++ b/source/libraries/sessions/044.php @@ -0,0 +1,3 @@ +close(); diff --git a/source/libraries/time.rst b/source/libraries/time.rst index f40d18fb5..bbe86c2f4 100755 --- a/source/libraries/time.rst +++ b/source/libraries/time.rst @@ -16,7 +16,11 @@ CodeIgniter 提供了一个完全本地化的、不可变的日期/时间类,该 实例化 ************* -有几种方法可以创建新的 Time 实例。第一种就是像任何其他类一样简单地创建一个新实例。当你以这种方式执行时,你可以传递一个表示所需时间的字符串。这可以是 PHP 的 strtotime 函数可以解析的任何字符串: +有几种方法可以创建新的 Time 实例。第一种就是像任何其他类一样简单地创建一个新实例。 + +当你以这种方式执行时,你可以传递一个表示所需时间的字符串。这可以是 PHP 的 `strtotime()`_ 函数可以解析的任何字符串: + +.. _strtotime(): https://www.php.net/manual/en/function.strtotime.php .. literalinclude:: time/001.php @@ -130,7 +134,7 @@ toDateTimeString() ================== 这是三个帮助方法中的第一个,用于处理 `IntlDateFormatter `_ 而不必记住它们的值。 -这将返回以 (Y-m-d H:i:s) 格式化的本地化字符串版本: +这将返回以 (``Y-m-d H:i:s``) 格式化的本地化字符串版本: .. literalinclude:: time/016.php diff --git a/source/libraries/uploaded_files.rst b/source/libraries/uploaded_files.rst index 4e92b7207..11568768b 100755 --- a/source/libraries/uploaded_files.rst +++ b/source/libraries/uploaded_files.rst @@ -296,6 +296,16 @@ getClientMimeType() .. literalinclude:: uploaded_files/015.php +getClientPath() +--------------- + +.. versionadded:: 4.4.0 + +当客户端通过目录上传方式上传文件时,返回上传文件的 `webkit 相对路径 `_。 +在 PHP 8.1 以下的版本中,返回 ``null``。 + +.. literalinclude:: uploaded_files/023.php + 移动文件 ============ diff --git a/source/libraries/uploaded_files/023.php b/source/libraries/uploaded_files/023.php new file mode 100644 index 000000000..f8accc908 --- /dev/null +++ b/source/libraries/uploaded_files/023.php @@ -0,0 +1,4 @@ +getClientPath(); +echo $clientPath; // dir/file.txt, or dir/sub_dir/file.txt diff --git a/source/libraries/uri.rst b/source/libraries/uri.rst index d2e11b8d5..0e543f0a3 100755 --- a/source/libraries/uri.rst +++ b/source/libraries/uri.rst @@ -12,32 +12,41 @@ CodeIgniter 提供了面向对象的方式来在你的应用中使用 URI。这 创建 URI 实例 ====================== -创建一个 URI 实例就像创建一个新的类实例一样简单: +创建一个 URI 实例就像创建一个新的类实例一样简单。 + +当你创建新实例时,可以在构造函数中传递完整或部分 URL,并将其解析为相应的部分: .. literalinclude:: uri/001.php + :lines: 2- -或者,你可以使用 ``service()`` 函数来获取一个实例: +或者,你可以使用 :php:func:`service()` 函数来获取一个实例: -.. literalinclude:: uri/002.php +.. literalinclude:: uri/003.php + :lines: 2- -当你创建新实例时,可以在构造函数中传递全部或部分 URL,它会解析成适当的部分: +自 v4.4.0 起,如果你没有传递 URL,则返回当前的 URI: -.. literalinclude:: uri/003.php +.. literalinclude:: uri/002.php + :lines: 2- + +.. note:: 上述代码返回 ``SiteURI`` 实例,它扩展了 ``URI`` 类。``URI`` 类用于一般的 URI,而 ``SiteURI`` 类用于你的站点 URI。 当前 URI --------------- -大多数时候,你真正想要的只是一个表示当前请求 URL 的对象。 -你可以使用 :doc:`../helpers/url_helper` 中可用的函数之一: +很多时候,你只需要一个表示当前请求的 URL 的对象。你可以使用 :doc:`../helpers/url_helper` 中提供的 :php:func:`current_url()` 函数: .. literalinclude:: uri/004.php + :lines: 2- 你必须传递 ``true`` 作为第一个参数,否则它会返回当前 URL 的字符串表示。 这个 URI 基于当前请求对象和你在 ``Config\App`` 中的设置(``baseURL``、``indexPage`` 和 ``forceGlobalSecureRequests``)确定的相对路径。 -假设你在一个扩展 ``CodeIgniter\Controller`` 的控制器中,你可以获取这个相对路径: + +假设你在一个扩展了 ``CodeIgniter\Controller`` 的控制器中,你还可以获取当前的 SiteURI 实例: .. literalinclude:: uri/005.php + :lines: 2- =========== URI 字符串 @@ -121,8 +130,10 @@ path 是站点本身内的所有段。如你所料,可以使用 ``getPath()`` .. note:: 当用这种或类允许的任何其他方式设置路径时,它会被编码以对任何危险字符进行转义,并移除段点以确保安全。 +.. note:: 自 v4.4.0 起,``SiteURI::getRoutePath()`` 方法返回相对于 baseURL 的 URI 路径,而 ``SiteURI::getPath()`` 方法始终返回带有前导 ``/`` 的完整 URI 路径。 + Query(查询) ---------------- +----------- 可以通过类使用简单的字符串表示来操作查询数据。 diff --git a/source/libraries/uri/001.php b/source/libraries/uri/001.php index 524e67669..7219d03d2 100644 --- a/source/libraries/uri/001.php +++ b/source/libraries/uri/001.php @@ -1,3 +1,3 @@ request->getPath(); +$uri = $this->request->getUri(); diff --git a/source/libraries/validation.rst b/source/libraries/validation.rst index 60a06f073..159036f27 100755 --- a/source/libraries/validation.rst +++ b/source/libraries/validation.rst @@ -107,6 +107,8 @@ CodeIgniter 提供了一个全面的数据验证类,可以帮助 minimizing 你 .. note:: 从 v4.3.0 开始,可以使用 :ref:`$this->request->is() ` 方法。 在早期版本中,需要使用 ``if (strtolower($this->request->getMethod()) !== 'post')``。 +.. note:: 自 v4.4.0 起,可以使用 :ref:`$this->validator->getValidated() ` 方法。 + 路由 ========== @@ -214,6 +216,7 @@ CodeIgniter 4 有两种验证规则类。 该库以名为 **validation** 的服务加载: .. literalinclude:: validation/004.php + :lines: 2- 这会自动加载 ``Config\Validation`` 文件,其中包含用于包含多个 Rulesets 的设置,以及可以轻松重用的规则集合。 @@ -240,6 +243,7 @@ setRule() ``$rules`` 可以接收管道分隔的规则列表,或者规则数组: .. literalinclude:: validation/005.php + :lines: 2- 你传递给 ``$field`` 的值必须匹配传入数据数组的键。如果数据直接来自 ``$_POST``,那么它必须与表单输入名称完全匹配。 @@ -254,28 +258,35 @@ setRules() 像 ``setRule()`` 一样,但接受字段名称及其规则的数组: .. literalinclude:: validation/006.php + :lines: 2- 要给出带标签的错误消息,可以设置如下: .. literalinclude:: validation/007.php + :lines: 2- .. _validation-withrequest: +.. note:: ``setRules()`` 会覆盖先前设置的任何规则。要向现有规则集添加多个规则,请多次使用 ``setRule()``。 + 为数组数据设置规则 ============================ 如果你的数据在嵌套的关联数组中,你可以使用“点数组语法”来轻松验证数据: .. literalinclude:: validation/009.php + :lines: 2- 你可以使用通配符 ``*`` 符号匹配任意一层数组: .. literalinclude:: validation/010.php + :lines: 2- 当你有单维数组数据时,"点数组语法"也很有用。 例如,下拉多选返回的数据: .. literalinclude:: validation/011.php + :lines: 2- withRequest() ============= @@ -283,14 +294,11 @@ withRequest() 当验证从 HTTP 请求输入的数据时,你会最常使用验证库。如果需要的话,你可以传入当前请求对象的实例,它会获取所有输入数据并将其设置为要验证的数据: .. literalinclude:: validation/008.php + :lines: 2- + +.. warning:: 当你使用此方法时,应使用 :ref:`getValidated() ` 方法获取经过验证的数据。因为该方法从 :ref:`$request->getJSON() ` 获取 JSON 数据(当请求是 JSON 请求时,``Content-Type: application/json``),或者从 :ref:`$request->getRawInput() ` 获取原始数据(当请求是 PUT、PATCH、DELETE 请求且不是 HTML 表单提交时,``Content-Type: multipart/form-data``),或者从 :ref:`$request->getVar() ` 获取数据,并且攻击者可以更改要验证的数据。 -.. note:: 这个方法会从 - :ref:`$request->getJSON() ` - 获取 JSON 数据,当请求是 JSON 请求时(``Content-Type: application/json``), - 或者从 - :ref:`$request->getRawInput() ` - 获取原始数据,当请求是 PUT、PATCH、DELETE 请求且不是 HTML 表单 POST 时(``Content-Type: multipart/form-data``), - 或者从 :ref:`$request->getVar() ` 获取数据。 +.. note:: 自 v4.4.0 起,可以使用 :ref:`getValidated() ` 方法。 *********************** 使用验证 @@ -310,6 +318,7 @@ withRequest() 如果验证成功,该方法返回 true。 .. literalinclude:: validation/043.php + :lines: 2- 运行多个验证 ============================ @@ -319,13 +328,37 @@ withRequest() 如果你打算运行多个验证,例如对不同的数据集或之后的规则,你可能需要在每次运行之前调用 ``$validation->reset()`` 来清除之前运行的错误。要注意 ``reset()`` 会使任何数据、规则或自定义错误无效,所以 ``setRules()``、``setRuleGroup()`` 等需要重复: .. literalinclude:: validation/019.php + :lines: 2- 验证单个值 ================== -对一个值针对一个规则进行验证: +``check()`` 方法根据规则验证一个值。 +第一个参数 ``$value`` 是要验证的值。第二个参数 ``$rule`` 是验证规则。 +可选的第三个参数 ``$errors`` 是自定义错误消息。 .. literalinclude:: validation/012.php + :lines: 2- + +.. note:: 在 v4.4.0 之前,此方法的第二个参数 ``$rule`` 的类型提示为 ``string``。在 v4.4.0 及之后的版本中,类型提示被移除,允许接受数组。 + +.. note:: 此方法调用 ``setRule()`` 方法在内部设置规则。 + +.. _validation-getting-validated-data: + +获取经过验证的数据 +====================== + +.. versionadded:: 4.4.0 + +可以使用 ``getValidated()`` 方法获取实际经过验证的数据。 +该方法返回一个仅包含已通过验证规则的元素的数组。 + +.. literalinclude:: validation/044.php + :lines: 2- + +.. literalinclude:: validation/045.php + :lines: 2- 将一组验证规则保存到配置文件 ================================================== @@ -347,6 +380,7 @@ withRequest() 当调用 ``run()`` 方法时,你可以在第一个参数中指定要使用的组: .. literalinclude:: validation/014.php + :lines: 2- 如何保存错误消息 -------------------------- @@ -364,17 +398,21 @@ withRequest() 获取和设置规则组 ----------------------------- -**获取规则组** +获取规则组 +^^^^^^^^^^^^^^ 此方法从验证配置中获取规则组: .. literalinclude:: validation/017.php + :lines: 2- -**设置规则组** +设置规则组 +^^^^^^^^^^^^^^ 此方法将验证配置中的规则组设置到验证服务中: .. literalinclude:: validation/018.php + :lines: 2- .. _validation-placeholders: @@ -384,16 +422,19 @@ withRequest() 验证类提供了一种简单的方法,基于传入的数据替换规则的一部分。这听起来比较模糊,但在使用 ``is_unique`` 验证规则时特别有用。占位符简单地是以花括号括起来的字段名(或数组键),该字段名作为 ``$data`` 传入。它将被传入字段的 **值** 所替换。以下示例将阐明这一点: .. literalinclude:: validation/020.php + :lines: 2- .. note:: 从 v4.3.5 开始,你必须为占位符字段(``id``)设置验证规则。 在这组规则中,它说明电子邮件地址在数据库中应该是唯一的,除了具有与占位符的值匹配的 id 的行。假设表单 POST 数据如下: .. literalinclude:: validation/021.php + :lines: 2- 然后 ``{id}`` 占位符会被替换为数字 **4**,得到这条修改后的规则: .. literalinclude:: validation/022.php + :lines: 2- 所以在验证电子邮件唯一性时,它会忽略数据库中 ``id=4`` 的行。 @@ -421,10 +462,12 @@ withRequest() 作为最后一个参数: .. literalinclude:: validation/023.php + :lines: 2- 或者以标签样式: .. literalinclude:: validation/024.php + :lines: 2- 如果你想要包含字段的“人类”名称,或某些规则允许的可选参数(如 max_length),或者被验证的值,你可以分别在消息中添加 ``{field}``、``{param}`` 和 ``{value}`` 标签:: @@ -444,6 +487,7 @@ withRequest() 我们可以简单地使用这个文件中定义的语言行,像这样: .. literalinclude:: validation/025.php + :lines: 2- .. _validation-getting-all-errors: @@ -453,6 +497,7 @@ withRequest() 如果你需要检索所有失败字段的错误消息,可以使用 ``getErrors()`` 方法: .. literalinclude:: validation/026.php + :lines: 2- 如果没有错误,将返回一个空数组。 @@ -482,22 +527,24 @@ withRequest() 你可以使用 ``getError()`` 方法检索单个字段的错误。唯一的参数是字段名称: .. literalinclude:: validation/027.php + :lines: 2- 如果没有错误,将返回一个空字符串。 .. note:: 当使用通配符时,所有匹配通配符的找到的错误将组合成一行,以 EOL 字符分隔。 - 检查错误是否存在 ===================== 你可以使用 ``hasError()`` 方法检查是否存在错误。唯一的参数是字段名称: .. literalinclude:: validation/028.php + :lines: 2- 当指定使用通配符的字段时,将检查匹配通配符的所有错误: .. literalinclude:: validation/029.php + :lines: 2- .. _validation-redirect-and-validation-errors: @@ -532,6 +579,7 @@ PHP 请求之间不共享任何内容。所以在验证失败时重定向,重定 在视图内可以使用名为 ``$errors`` 的数组,它包含错误列表,其中键是有错误的字段名称,值是错误消息,像这样: .. literalinclude:: validation/031.php + :lines: 2- 实际上有两种类型的视图你可以创建。第一种具有所有错误的数组,这就是我们刚才看到的。另一种更简单,只包含一个名为 ``$error`` 的变量,其中包含错误消息。这在使用 ``showError()`` 方法时使用,该方法必须指定错误属于的字段:: @@ -593,6 +641,7 @@ PHP 请求之间不共享任何内容。所以在验证失败时重定向,重定 你的新自定义规则现在可以像任何其他规则一样使用了: .. literalinclude:: validation/036.php + :lines: 2- 允许参数 ------------------- @@ -622,6 +671,7 @@ PHP 请求之间不共享任何内容。所以在验证失败时重定向,重定 你需要为验证规则使用数组: .. literalinclude:: validation/040.php + :lines: 2- 你必须为闭包规则设置错误消息。 设置错误消息时,请为闭包规则设置数组键。 @@ -630,6 +680,7 @@ PHP 请求之间不共享任何内容。所以在验证失败时重定向,重定 或者可以使用以下参数: .. literalinclude:: validation/041.php + :lines: 2- *************** 可用规则 @@ -639,6 +690,7 @@ PHP 请求之间不共享任何内容。所以在验证失败时重定向,重定 ``ignore_value`` 前后不能有空格。 .. literalinclude:: validation/038.php + :lines: 2- 常规规则 ===================== diff --git a/source/libraries/validation/001.php b/source/libraries/validation/001.php index af2acb754..e797b21a6 100644 --- a/source/libraries/validation/001.php +++ b/source/libraries/validation/001.php @@ -2,8 +2,6 @@ namespace App\Controllers; -use Config\Services; - class Form extends BaseController { protected $helpers = ['form']; @@ -20,6 +18,9 @@ public function index() return view('signup'); } + // If you want to get the validated data. + $validData = $this->validator->getValidated(); + return view('success'); } } diff --git a/source/libraries/validation/002.php b/source/libraries/validation/002.php index d1eeff19c..b8d5811f3 100644 --- a/source/libraries/validation/002.php +++ b/source/libraries/validation/002.php @@ -3,10 +3,10 @@ // ... $rules = [ - 'username' => 'required', - 'password' => 'required|min_length[10]', - 'passconf' => 'required|matches[password]', - 'email' => 'required|valid_email', + 'username' => 'required|max_length[30]', + 'password' => 'required|max_length[255]|min_length[10]', + 'passconf' => 'required|max_length[255]|matches[password]', + 'email' => 'required|max_length[254]|valid_email', ]; // ... diff --git a/source/libraries/validation/003.php b/source/libraries/validation/003.php index fbd3ee579..e83233803 100644 --- a/source/libraries/validation/003.php +++ b/source/libraries/validation/003.php @@ -2,7 +2,9 @@ namespace Config; -class Validation +// ... + +class Validation extends BaseConfig { // ... diff --git a/source/libraries/validation/005.php b/source/libraries/validation/005.php index 9d129e642..6ab43932d 100644 --- a/source/libraries/validation/005.php +++ b/source/libraries/validation/005.php @@ -1,4 +1,4 @@ setRule('username', 'Username', 'required|min_length[3]'); -$validation->setRule('password', 'Password', ['required', 'min_length[8]', 'alpha_numeric_punct']); +$validation->setRule('username', 'Username', 'required|max_length[30]|min_length[3]'); +$validation->setRule('password', 'Password', ['required', 'max_length[255]', 'min_length[8]', 'alpha_numeric_punct']); diff --git a/source/libraries/validation/006.php b/source/libraries/validation/006.php index 5f7cbf645..bd4dbf10c 100644 --- a/source/libraries/validation/006.php +++ b/source/libraries/validation/006.php @@ -1,11 +1,11 @@ setRules([ - 'username' => 'required', - 'password' => 'required|min_length[10]', + 'username' => 'required|max_length[30]', + 'password' => 'required|max_length[255]|min_length[10]', ]); // or $validation->setRules([ - 'username' => 'required', - 'password' => ['required', 'min_length[10]'], + 'username' => ['required', 'max_length[30]'], + 'password' => ['required', 'max_length[255]', 'min_length[10]'], ]); diff --git a/source/libraries/validation/007.php b/source/libraries/validation/007.php index cb12c5a61..c545e70c8 100644 --- a/source/libraries/validation/007.php +++ b/source/libraries/validation/007.php @@ -1,11 +1,11 @@ setRules([ - 'username' => ['label' => 'Username', 'rules' => 'required'], - 'password' => ['label' => 'Password', 'rules' => 'required|min_length[10]'], + 'username' => ['label' => 'Username', 'rules' => 'required|max_length[30]'], + 'password' => ['label' => 'Password', 'rules' => 'required|max_length[255]|min_length[10]'], ]); // or $validation->setRules([ - 'username' => ['label' => 'Username', 'rules' => 'required'], - 'password' => ['label' => 'Password', 'rules' => ['required', 'min_length[10]']], + 'username' => ['label' => 'Username', 'rules' => 'required|max_length[30]'], + 'password' => ['label' => 'Password', 'rules' => ['required', 'max_length[255]', 'min_length[10]']], ]); diff --git a/source/libraries/validation/008.php b/source/libraries/validation/008.php index 1b570f1aa..38656dda3 100644 --- a/source/libraries/validation/008.php +++ b/source/libraries/validation/008.php @@ -1,3 +1,11 @@ withRequest($this->request)->run(); +$validation = \Config\Services::validation(); +$request = \Config\Services::request(); + +if ($validation->withRequest($request)->run()) { + // If you want to get the validated data. + $validData = $validation->getValidated(); + + // ... +} diff --git a/source/libraries/validation/009.php b/source/libraries/validation/009.php index 58f0749a0..7e321e966 100644 --- a/source/libraries/validation/009.php +++ b/source/libraries/validation/009.php @@ -19,10 +19,10 @@ // Joe Smith $validation->setRules([ - 'contacts.name' => 'required', + 'contacts.name' => 'required|max_length[60]', ]); // Fred Flintsone & Wilma $validation->setRules([ - 'contacts.friends.name' => 'required', + 'contacts.friends.name' => 'required|max_length[60]', ]); diff --git a/source/libraries/validation/010.php b/source/libraries/validation/010.php index 94c691931..0951f582c 100644 --- a/source/libraries/validation/010.php +++ b/source/libraries/validation/010.php @@ -2,5 +2,5 @@ // Fred Flintsone & Wilma $validation->setRules([ - 'contacts.*.name' => 'required', + 'contacts.*.name' => 'required|max_length[60]', ]); diff --git a/source/libraries/validation/011.php b/source/libraries/validation/011.php index 2754c41ac..9b5e03eb4 100644 --- a/source/libraries/validation/011.php +++ b/source/libraries/validation/011.php @@ -13,5 +13,5 @@ // Rule $validation->setRules([ - 'user_ids.*' => 'required', + 'user_ids.*' => 'required|max_length[19]', ]); diff --git a/source/libraries/validation/012.php b/source/libraries/validation/012.php index a43f9ab1a..9b9e89c21 100644 --- a/source/libraries/validation/012.php +++ b/source/libraries/validation/012.php @@ -1,3 +1,5 @@ check($value, 'required'); +if ($validation->check($value, 'required')) { + // $value is valid. +} diff --git a/source/libraries/validation/013.php b/source/libraries/validation/013.php index 671b5386f..b43a32f97 100644 --- a/source/libraries/validation/013.php +++ b/source/libraries/validation/013.php @@ -2,13 +2,17 @@ namespace Config; -class Validation +// ... + +class Validation extends BaseConfig { - public $signup = [ - 'username' => 'required', - 'password' => 'required', - 'pass_confirm' => 'required|matches[password]', - 'email' => 'required|valid_email', + // ... + + public array $signup = [ + 'username' => 'required|max_length[30]', + 'password' => 'required|max_length[255]', + 'pass_confirm' => 'required|max_length[255]|matches[password]', + 'email' => 'required|max_length[254]|valid_email', ]; // ... diff --git a/source/libraries/validation/015.php b/source/libraries/validation/015.php index 5815abfac..6e666e41c 100644 --- a/source/libraries/validation/015.php +++ b/source/libraries/validation/015.php @@ -2,16 +2,20 @@ namespace Config; -class Validation +// ... + +class Validation extends BaseConfig { - public $signup = [ - 'username' => 'required', - 'password' => 'required', - 'pass_confirm' => 'required|matches[password]', - 'email' => 'required|valid_email', + // ... + + public array $signup = [ + 'username' => 'required|max_length[30]', + 'password' => 'required|max_length[255]', + 'pass_confirm' => 'required|max_length[255]|matches[password]', + 'email' => 'required|max_length[254]|valid_email', ]; - public $signup_errors = [ + public array $signup_errors = [ 'username' => [ 'required' => 'You must choose a username.', ], diff --git a/source/libraries/validation/016.php b/source/libraries/validation/016.php index 5b08e7398..1eb72bc8c 100644 --- a/source/libraries/validation/016.php +++ b/source/libraries/validation/016.php @@ -2,21 +2,26 @@ namespace Config; -class Validation +// ... + +class Validation extends BaseConfig { - public $signup = [ + // ... + + public array $signup = [ 'username' => [ - 'rules' => 'required', + 'rules' => 'required|max_length[30]', 'errors' => [ 'required' => 'You must choose a Username.', ], ], 'email' => [ - 'rules' => 'required|valid_email', + 'rules' => 'required|max_length[254]|valid_email', 'errors' => [ 'valid_email' => 'Please check the Email field. It does not appear to be valid.', ], ], ]; + // ... } diff --git a/source/libraries/validation/020.php b/source/libraries/validation/020.php index 67fdb567e..792b7ba6b 100644 --- a/source/libraries/validation/020.php +++ b/source/libraries/validation/020.php @@ -1,6 +1,6 @@ setRules([ - 'id' => 'is_natural_no_zero', - 'email' => 'required|valid_email|is_unique[users.email,id,{id}]', + 'id' => 'max_length[19]|is_natural_no_zero', + 'email' => 'required|max_length[254]|valid_email|is_unique[users.email,id,{id}]', ]); diff --git a/source/libraries/validation/022.php b/source/libraries/validation/022.php index 001c0f0d7..f3dda9e96 100644 --- a/source/libraries/validation/022.php +++ b/source/libraries/validation/022.php @@ -1,6 +1,6 @@ setRules([ - 'id' => 'is_natural_no_zero', - 'email' => 'required|valid_email|is_unique[users.email,id,4]', + 'id' => 'max_length[19]|is_natural_no_zero', + 'email' => 'required|max_length[254]|valid_email|is_unique[users.email,id,4]', ]); diff --git a/source/libraries/validation/023.php b/source/libraries/validation/023.php index f9ba86d1f..85fd02e5b 100644 --- a/source/libraries/validation/023.php +++ b/source/libraries/validation/023.php @@ -2,8 +2,8 @@ $validation->setRules( [ - 'username' => 'required|is_unique[users.username]', - 'password' => 'required|min_length[10]', + 'username' => 'required|max_length[30]|is_unique[users.username]', + 'password' => 'required|max_length[254]|min_length[10]', ], [ // Errors 'username' => [ diff --git a/source/libraries/validation/024.php b/source/libraries/validation/024.php index 17f053ee4..a8fb8151d 100644 --- a/source/libraries/validation/024.php +++ b/source/libraries/validation/024.php @@ -3,14 +3,14 @@ $validation->setRules([ 'username' => [ 'label' => 'Username', - 'rules' => 'required|is_unique[users.username]', + 'rules' => 'required|max_length[30]|is_unique[users.username]', 'errors' => [ 'required' => 'All accounts must have {field} provided', ], ], 'password' => [ 'label' => 'Password', - 'rules' => 'required|min_length[10]', + 'rules' => 'required|max_length[255]|min_length[10]', 'errors' => [ 'min_length' => 'Your {field} is too short. You want to get hacked?', ], diff --git a/source/libraries/validation/025.php b/source/libraries/validation/025.php index 6e2f1e408..60f53893e 100644 --- a/source/libraries/validation/025.php +++ b/source/libraries/validation/025.php @@ -3,14 +3,14 @@ $validation->setRules([ 'username' => [ 'label' => 'Rules.username', - 'rules' => 'required|is_unique[users.username]', + 'rules' => 'required|max_length[30]|is_unique[users.username]', 'errors' => [ 'required' => 'Rules.username.required', ], ], 'password' => [ 'label' => 'Rules.password', - 'rules' => 'required|min_length[10]', + 'rules' => 'required|max_length[255]|min_length[10]', 'errors' => [ 'min_length' => 'Rules.password.min_length', ], diff --git a/source/libraries/validation/032.php b/source/libraries/validation/032.php index acec3cfc6..cf8bcf8b9 100644 --- a/source/libraries/validation/032.php +++ b/source/libraries/validation/032.php @@ -2,9 +2,13 @@ namespace Config; -class Validation +// ... + +class Validation extends BaseConfig { - public $templates = [ + // ... + + public array $templates = [ 'list' => 'CodeIgniter\Validation\Views\list', 'single' => 'CodeIgniter\Validation\Views\single', 'my_list' => '_errors_list', diff --git a/source/libraries/validation/033.php b/source/libraries/validation/033.php index fe61b137f..aa67529ba 100644 --- a/source/libraries/validation/033.php +++ b/source/libraries/validation/033.php @@ -2,12 +2,13 @@ namespace Config; +use CodeIgniter\Config\BaseConfig; use CodeIgniter\Validation\CreditCardRules; use CodeIgniter\Validation\FileRules; use CodeIgniter\Validation\FormatRules; use CodeIgniter\Validation\Rules; -class Validation +class Validation extends BaseConfig { // ... diff --git a/source/libraries/validation/036.php b/source/libraries/validation/036.php index 2cc83471e..66919dadf 100644 --- a/source/libraries/validation/036.php +++ b/source/libraries/validation/036.php @@ -1,5 +1,5 @@ setRules([ - 'foo' => 'required|even', + 'foo' => 'required|max_length[19]|even', ]); diff --git a/source/libraries/validation/038.php b/source/libraries/validation/038.php index f4ec33715..1330267eb 100644 --- a/source/libraries/validation/038.php +++ b/source/libraries/validation/038.php @@ -3,10 +3,10 @@ // is_unique[table.field,ignore_field,ignore_value] $validation->setRules([ - 'name' => "is_unique[supplier.name,uuid, {$uuid}]", // is not ok - 'name' => "is_unique[supplier.name,uuid,{$uuid} ]", // is not ok - 'name' => "is_unique[supplier.name,uuid,{$uuid}]", // is ok - 'name' => 'is_unique[supplier.name,uuid,{uuid}]', // is ok - see "Validation Placeholders" + 'name' => "max_length[36]|is_unique[supplier.name,uuid, {$uuid}]", // is not ok + 'name' => "max_length[36]|is_unique[supplier.name,uuid,{$uuid} ]", // is not ok + 'name' => "max_length[36]|is_unique[supplier.name,uuid,{$uuid}]", // is ok + 'name' => 'max_length[36]|is_unique[supplier.name,uuid,{uuid}]', // is ok - see "Validation Placeholders" ]); // Warning: If `$uuid` is a user input, be sure to validate the format of the value before using it. // Otherwise, it is vulnerable. diff --git a/source/libraries/validation/044.php b/source/libraries/validation/044.php new file mode 100644 index 000000000..7f3a0e0e0 --- /dev/null +++ b/source/libraries/validation/044.php @@ -0,0 +1,21 @@ +setRules([ + 'username' => 'required', + 'password' => 'required|min_length[10]', +]); + +$data = [ + 'username' => 'john', + 'password' => 'BPi-$Swu7U5lm$dX', + 'csrf_token' => '8b9218a55906f9dcc1dc263dce7f005a', +]; + +if ($validation->run($data)) { + $validatedData = $validation->getValidated(); + // $validatedData = [ + // 'username' => 'john', + // 'password' => 'BPi-$Swu7U5lm$dX', + // ]; +} diff --git a/source/libraries/validation/045.php b/source/libraries/validation/045.php new file mode 100644 index 000000000..1becb578d --- /dev/null +++ b/source/libraries/validation/045.php @@ -0,0 +1,18 @@ +validate([ + 'username' => 'required', + 'password' => 'required|min_length[10]', +])) { + // The validation failed. + return view('login', [ + 'errors' => $this->validator->getErrors(), + ]); +} + +// The validation was successful. + +// Get the validated data. +$validData = $this->validator->getValidated(); diff --git a/source/models/entities.rst b/source/models/entities.rst index 5d366fe82..870598278 100644 --- a/source/models/entities.rst +++ b/source/models/entities.rst @@ -8,8 +8,9 @@ CodeIgniter 在其数据库层全面支持实体类,同时保持它们的完全 :local: :depth: 2 +************ 实体用法 -============ +************ 核心上,实体类仅仅是一个代表单个数据库行的类。它具有表示数据库列的类属性,并提供任何其他方法来实现该行的业务逻辑。但是,关键是它不知道如何持久化自己。这是模型或存储库类的责任。这样,如果你需要保存对象的方式发生了任何更改,你不需要更改整个应用程序中该对象的使用方式。这使得在快速原型阶段使用 JSON 或 XML 文件存储对象成为可能,然后在概念证明有效时轻松切换到数据库。 @@ -26,7 +27,7 @@ CodeIgniter 在其数据库层全面支持实体类,同时保持它们的完全 .. important:: ``attributes`` 是一个保留字,供内部使用。如果你将其用作列名,实体将无法正确工作。 创建实体类 ------------------------ +======================= 现在创建一个新的实体类。由于没有默认的位置来存储这些类,也不符合现有的目录结构,所以在 **app/Entities** 中创建一个新目录。在 **app/Entities/User.php** 中创建实体本身。 @@ -35,7 +36,7 @@ CodeIgniter 在其数据库层全面支持实体类,同时保持它们的完全 就这么简单,尽管我们马上会让它更有用。 创建模型 ----------------- +================ 首先在 **app/Models/UserModel.php** 创建模型,以便我们可以与其交互: @@ -44,7 +45,7 @@ CodeIgniter 在其数据库层全面支持实体类,同时保持它们的完全 模型在数据库的所有活动中使用 ``users`` 表。我们已经设置了 ``$allowedFields`` 属性,以包含我们希望外部类更改的所有字段。 ``id``、``created_at`` 和 ``updated_at`` 字段由类或数据库自动处理,所以我们不想更改它们。最后,我们将实体类设置为 ``$returnType``。这确保从数据库返回行的所有模型方法都会返回我们的 User 实体类的实例,而不是正常的对象或数组。 使用实体类 ------------------------------ +============================= 现在各部分就绪,你将像使用任何其他类一样使用实体类: @@ -59,7 +60,7 @@ CodeIgniter 在其数据库层全面支持实体类,同时保持它们的完全 .. note:: 当我们调用 ``insert()`` 时,实体的所有值都传递给该方法,但当我们调用 ``update()`` 时,只传递更改的值。 快速填充属性 --------------------------- +========================== 实体类还提供了一个方法 ``fill()``,允许你将键/值对数组推入类中并填充类属性。数组中的任何属性都将在实体上设置。但是,通过模型保存时,实际上只会将 ``$allowedFields`` 中的字段保存到数据库,所以你可以在实体上存储其他数据,而不必担心错误地保存多余的字段。 @@ -70,12 +71,13 @@ CodeIgniter 在其数据库层全面支持实体类,同时保持它们的完全 .. literalinclude:: entities/005.php 批量访问属性 -------------------------- +========================= 实体类有两个方法可以将所有可用属性提取到一个数组中:``toArray()`` 和 ``toRawArray()``。使用原始版本将绕过魔术“getter”方法和转换。两个方法都可以接受一个布尔第一个参数,指定返回的值是否应该由更改的那些过滤,以及一个布尔最后一个参数,以使方法递归,以防出现嵌套的实体。 +*********************** 处理业务逻辑 -======================= +*********************** 虽然上面的例子很方便,但它们没有帮助执行任何业务逻辑。基本实体类实现了一些智能的 ``__get()`` 和 ``__set()`` 方法,这些方法将检查特殊方法并使用那些方法,而不是直接使用属性,从而允许你执行任何需要的业务逻辑或数据转换。 @@ -95,8 +97,22 @@ CodeIgniter 在其数据库层全面支持实体类,同时保持它们的完全 .. literalinclude:: entities/007.php +.. _entities-special-getter-setter: + +特殊的 Getter/Setter +===================== + +.. versionadded:: 4.4.0 + +例如,如果你的实体的父类已经定义了一个名为 ``getParent()`` 的方法,并且你的实体还有一个名为 ``parent`` 的列,当你尝试在实体类中为 ``getParent()`` 方法添加业务逻辑时,该方法已经被定义了。 + +在这种情况下,你可以使用特殊的 getter/setter。而不是使用 ``getX()``/``setX()``,使用 ``_getX()``/``_setX()``。 + +在上面的示例中,如果你的实体有一个名为 ``_getParent()`` 的方法,当你获取 ``$entity->parent`` 时将使用该方法,当你设置 ``$entity->parent`` 时将使用 ``_setParent()`` 方法。 + +************ 数据映射 -============ +************ 在你的职业生涯的多个时间点上,你会遇到应用程序使用的情况发生变化,数据库中的原始列名不再有意义的情况。或者你发现你的编程风格更喜欢驼峰式类属性,但你的数据库模式要求使用蛇形名称。这些情况可以通过实体类的数据映射功能轻松处理。 @@ -116,11 +132,12 @@ CodeIgniter 在其数据库层全面支持实体类,同时保持它们的完全 .. note:: 当你使用数据映射时,你必须为数据库列名定义 ``set*()`` 和 ``get*()`` 方法。在这个例子中,你必须定义 ``setFullName()`` 和 ``getFullName()``。 +******** 变更器 -======== +******** 日期变更器 -------------- +============= 默认情况下,当设置或检索名称为 `created_at`、`updated_at` 或 `deleted_at` 的字段时,实体类会将其转换为 :doc:`时间 ` 实例。Time 类以不可变的本地化方式提供了大量有用的方法。 @@ -135,11 +152,19 @@ CodeIgniter 在其数据库层全面支持实体类,同时保持它们的完全 .. _entities-property-casting: 属性转换 ----------------- +================ + +你可以使用 ``$casts`` 属性指定实体中的属性应该转换为常见的数据类型。 +该选项应该是一个数组,其中键是类属性的名称,值是应该转换为的数据类型。 -你可以使用 ``$casts`` 属性指定实体中的属性应转换为常用的数据类型。这个选项应该是一个数组,其中键是类属性的名称,值是它应该转换到的数据类型。转换只影响读取值时。不会发生影响实体或数据库中的永久值的转换。属性可以转换为以下任何数据类型: +属性转换影响读取(获取)和写入(设置),但某些类型只影响读取(获取)。 + +标量类型转换 +------------------- + +属性可以转换为以下任何数据类型: **integer**、**float**、**double**、**string**、**boolean**、**object**、**array**、**datetime**、**timestamp**、**uri** 和 **int-bool**。 -在类型前加问号表示属性可为空,即 **?string**、**?integer**。 +在类型的开头加上问号,将属性标记为可为空,例如 **?string**、**?integer**。 .. note:: **int-bool** 可以从 v4.3.0 开始使用。 @@ -199,12 +224,11 @@ CSV 转换 .. literalinclude:: entities/019.php -**参数** +参数 +---------- 在某些情况下,一种类型是不够的。在这种情况下,你可以使用额外的参数。 -额外的参数用方括号表示,并以逗号分隔列表。 - -**type[param1,param2]** +额外的参数用方括号表示,并以逗号分隔列表,例如 ``type[param1, param2]``。 .. literalinclude:: entities/020.php @@ -213,8 +237,9 @@ CSV 转换 .. note:: 如果标记为 nullable 的转换类型是 ``?bool``,并且传入的值不是 null,那么参数 ``nullable`` 将传递给转换类型处理程序。 如果转换类型具有预定义的参数,则 ``nullable`` 将添加到列表的末尾。 +******************************* 检查更改的属性 -=============================== +******************************* 你可以检查自创建以来实体属性是否发生了更改。唯一的参数是要检查的属性名称: diff --git a/source/models/model.rst b/source/models/model.rst index 0c2e43059..e2ae53c63 100644 --- a/source/models/model.rst +++ b/source/models/model.rst @@ -4,7 +4,7 @@ .. contents:: :local: - :depth: 2 + :depth: 3 模型 ****** @@ -24,7 +24,7 @@ CodeIgniter 的 Model 提供了方便的特性和额外的功能,可以使处理 .. literalinclude:: model/001.php -``model()`` 内部使用 ``Factories::models()``。详情参见 :ref:`factories-example`。 +``model()`` 内部使用 ``Factories::models()``。详情参见 :ref:`factories-loading-class`。 CodeIgniter 的 Model *********************** @@ -377,6 +377,8 @@ purgeDeleted() .. literalinclude:: model/026.php +.. _in-model-validation: + 模型内验证 =================== @@ -391,10 +393,17 @@ purgeDeleted() 如果你想检查必填字段,可以通过配置更改行为。 详情参见 :ref:`clean-validation-rules`。 +设置验证规则 +------------------------ + 第一步是用将应用的字段和规则填充 ``$validationRules`` 类属性。如果你有要使用的自定义错误消息,请将其放入 ``$validationMessages`` 数组中: .. literalinclude:: model/027.php +如果你更喜欢在验证配置文件中组织你的规则和错误消息,你可以这样做,并将 ``$validationRules`` 设置为你创建的验证规则组的名称: + +.. literalinclude:: model/034.php + 向字段设置验证规则的另一种方法是使用函数: .. php:namespace:: CodeIgniter @@ -445,7 +454,17 @@ purgeDeleted() .. literalinclude:: model/031.php -现在,每当调用 ``insert()``、``update()`` 或 ``save()`` 方法时,数据都会被验证。如果失败,模型将返回布尔值 **false**。你可以使用 ``errors()`` 方法检索验证错误: +获取验证结果 +------------------------- + +现在,每当你调用 ``insert()``、``update()`` 或 ``save()`` 方法时,数据将被验证。如果验证失败,模型将返回布尔值 **false**。 + +.. _model-getting-validation-errors: + +获取验证错误 +------------------------- + +你可以使用 ``errors()`` 方法来检索验证错误: .. literalinclude:: model/032.php @@ -453,10 +472,6 @@ purgeDeleted() .. literalinclude:: model/033.php -如果你更喜欢在验证配置文件中组织规则和错误消息,你可以这样做,并简单地将 ``$validationRules`` 设置为你创建的验证规则组的名称: - -.. literalinclude:: model/034.php - 检索验证规则 --------------------------- @@ -479,6 +494,8 @@ purgeDeleted() .. literalinclude:: model/038.php +.. note:: 自 v4.3.5 起,你必须为占位符字段(``id``)设置验证规则。 + 在这组规则中,它说明电子邮件地址在数据库中应该是唯一的,除了具有与占位符的值匹配的 id 的行。假设表单 POST 数据如下: .. literalinclude:: model/039.php @@ -489,6 +506,8 @@ purgeDeleted() 所以在验证电子邮件唯一性时,它会忽略数据库中 ``id=4`` 的行。 +.. note:: 自 v4.3.5 起,如果占位符(``id``)的值未通过验证,占位符将不会被替换。 + 这也可以用于在运行时创建更动态的规则,只要你注意传入的动态键不与你的表单数据冲突即可。 保护字段 diff --git a/source/models/model/027.php b/source/models/model/027.php index 5c457a09d..3a039df49 100644 --- a/source/models/model/027.php +++ b/source/models/model/027.php @@ -7,10 +7,10 @@ class UserModel extends Model { protected $validationRules = [ - 'username' => 'required|alpha_numeric_space|min_length[3]', - 'email' => 'required|valid_email|is_unique[users.email]', - 'password' => 'required|min_length[8]', - 'pass_confirm' => 'required_with[password]|matches[password]', + 'username' => 'required|max_length[30]|alpha_numeric_space|min_length[3]', + 'email' => 'required|max_length[254]|valid_email|is_unique[users.email]', + 'password' => 'required|max_length[255]|min_length[8]', + 'pass_confirm' => 'required_with[password]|max_length[255]|matches[password]', ]; protected $validationMessages = [ 'email' => [ diff --git a/source/models/model/028.php b/source/models/model/028.php index acffbf22b..9cea0eb31 100644 --- a/source/models/model/028.php +++ b/source/models/model/028.php @@ -1,6 +1,6 @@ setValidationRule($fieldName, $fieldRules); diff --git a/source/models/model/029.php b/source/models/model/029.php index b7876797a..93ba8ae51 100644 --- a/source/models/model/029.php +++ b/source/models/model/029.php @@ -1,9 +1,9 @@ 'required|alpha_numeric_space|min_length[3]', + 'username' => 'required|max_length[30]|alpha_numeric_space|min_length[3]', 'email' => [ - 'rules' => 'required|valid_email|is_unique[users.email]', + 'rules' => 'required|max_length[254]|valid_email|is_unique[users.email]', 'errors' => [ 'required' => 'We really need your email.', ], diff --git a/source/models/model/038.php b/source/models/model/038.php index 820d37def..5ed40baae 100644 --- a/source/models/model/038.php +++ b/source/models/model/038.php @@ -7,6 +7,7 @@ class MyModel extends Model { protected $validationRules = [ - 'email' => 'required|valid_email|is_unique[users.email,id,{id}]', + 'id' => 'max_length[19]|is_natural_no_zero', + 'email' => 'required|max_length[254]|valid_email|is_unique[users.email,id,{id}]', ]; } diff --git a/source/models/model/040.php b/source/models/model/040.php index 6e1ff1711..df4d628d1 100644 --- a/source/models/model/040.php +++ b/source/models/model/040.php @@ -7,6 +7,7 @@ class MyModel extends Model { protected $validationRules = [ - 'email' => 'required|valid_email|is_unique[users.email,id,4]', + 'id' => 'max_length[19]|is_natural_no_zero', + 'email' => 'required|max_length[254]|valid_email|is_unique[users.email,id,4]', ]; } diff --git a/source/outgoing/localization.rst b/source/outgoing/localization.rst index 159d3a9a2..f00e64639 100644 --- a/source/outgoing/localization.rst +++ b/source/outgoing/localization.rst @@ -47,6 +47,8 @@ CodeIgniter 提供了几个工具来帮助你为不同的语言本地化应用 如果你需要直接设置区域设置,请参见 `设置当前区域设置`_ 。 +自 v4.4.0 起,添加了 ``IncomingRequest::setValidLocales()`` 方法,用于设置(和重置)从 ``Config\App::$supportedLocales`` 设置中设置的有效区域设置。 + 内容协商 ------------------- @@ -207,6 +209,8 @@ CodeIgniter 提供了几个工具来帮助你为不同的语言本地化应用 或者,在你的项目中运行以下命令会更好: - > composer require codeigniter4/translations +.. code-block:: console + + composer require codeigniter4/translations 翻译的消息将自动被使用,因为翻译文件夹得到了正确的映射。 diff --git a/source/outgoing/response.rst b/source/outgoing/response.rst index 9d57fecf9..1a080d680 100644 --- a/source/outgoing/response.rst +++ b/source/outgoing/response.rst @@ -24,7 +24,7 @@ Response 类通过只适合服务器对调用它的客户端做出响应的方 .. literalinclude:: response/002.php -你可以将一个数组格式化为 JSON 或 XML,并通过 ``setJSON`` 和 ``setXML`` 方法将 content type header 设置为适当的 MIME 类型。通常,你会传递一个数据数组进行转换: +你可以将一个数组格式化为 JSON 或 XML,并通过 ``setJSON()`` 和 ``setXML()`` 方法将 content type header 设置为适当的 MIME 类型。通常,你会传递一个数据数组进行转换: .. literalinclude:: response/003.php @@ -143,6 +143,15 @@ Response 类提供了一种简单的方法来将文件发送给客户端,提示 .. note:: 必须返回响应对象以便下载被发送到客户端。这允许在被发送到客户端之前通过所有的 **after** 过滤器来传递响应。 +.. _open-file-in-browser: + +在浏览器中打开文件 +-------------------- + +一些浏览器可以显示诸如 PDF 等文件。为了告诉浏览器显示文件而不是保存它,调用 ``DownloadResponse::inline()`` 方法。 + +.. literalinclude:: response/033.php + HTTP 缓存 ============ @@ -379,7 +388,7 @@ HTTP 缓存 .. php:method:: setCookie($name = ''[, $value = ''[, $expire = ''[, $domain = ''[, $path = '/'[, $prefix = ''[, $secure = false[, $httponly = false[, $samesite = null]]]]]]]]) - :param array|Cookie|string $name: Cookie 名称或参数数组或 ``CodeIgniter\Cookie\Cookie`` 实例 + :param array|Cookie|string $name: Cookie 名称 *或* 包含此方法可用的所有参数的关联数组 *或* ``CodeIgniter\Cookie\Cookie`` 的实例。 :param string $value: Cookie 值 :param int $expire: Cookie 到期时间,以秒为单位。如果设置为 ``0`` cookie 将只保持浏览器打开时有效 :param string $domain: Cookie 域名 diff --git a/source/outgoing/response/003.php b/source/outgoing/response/003.php index e5bff8cab..da5819d98 100644 --- a/source/outgoing/response/003.php +++ b/source/outgoing/response/003.php @@ -6,5 +6,6 @@ ]; return $this->response->setJSON($data); + // or return $this->response->setXML($data); diff --git a/source/outgoing/response/033.php b/source/outgoing/response/033.php new file mode 100644 index 000000000..8afaf8496 --- /dev/null +++ b/source/outgoing/response/033.php @@ -0,0 +1,6 @@ +response->download($name, $data)->inline(); diff --git a/source/outgoing/table.rst b/source/outgoing/table.rst index d1fd99e3a..8a1dc70c1 100644 --- a/source/outgoing/table.rst +++ b/source/outgoing/table.rst @@ -55,6 +55,25 @@ Table 类允许你设置一个表格模板来指定布局设计。下面是模 .. literalinclude:: table/008.php +.. _table-sync-rows-with-headings: + +同步行与标题 +================================ + +.. versionadded:: 4.4.0 + +``setSyncRowsWithHeading(true)`` 方法使得每个数据值都放置在与 ``setHeading()`` 中定义的相同列中,如果参数使用了关联数组。这在处理通过 REST API 加载的数据时特别有用,因为其顺序可能不符合你的要求,或者如果 API 返回了过多的数据。 + +如果数据行包含一个在标题中不存在的键,则其值将被过滤。相反,如果数据行中没有列在标题中列出的键,则会在其位置放置一个空单元格。 + +.. literalinclude:: table/019.php + +.. important:: 你必须在通过 ``addRow([...])`` 添加任何行之前调用 ``setSyncRowsWithHeading(true)`` 和 ``setHeading([...])``,以进行列的重新排列。 + +使用数组作为 ``generate()`` 的输入会产生相同的结果: + +.. literalinclude:: table/020.php + *************** 类参考 *************** @@ -84,7 +103,7 @@ Table 类允许你设置一个表格模板来指定布局设计。下面是模 .. php:method:: setCaption($caption) :param string $caption: 表格标题 - :returns: Table 实例(方法链) + :returns: Table 实例(方法链式调用) :rtype: Table 允许你为表格添加标题。 @@ -94,7 +113,7 @@ Table 类允许你设置一个表格模板来指定布局设计。下面是模 .. php:method:: setHeading([$args = [] [, ...]]) :param mixed $args: 包含表格列标题的数组或多个字符串 - :returns: Table 实例(方法链) + :returns: Table 实例(方法链式调用) :rtype: Table 允许你设置表格标题。你可以提交数组或离散参数: @@ -104,7 +123,7 @@ Table 类允许你设置一个表格模板来指定布局设计。下面是模 .. php:method:: setFooting([$args = [] [, ...]]) :param mixed $args: 包含表格页脚值的数组或多个字符串 - :returns: Table 实例(方法链) + :returns: Table 实例(方法链式调用) :rtype: Table 允许你设置表格页脚。你可以提交数组或离散参数: @@ -114,7 +133,7 @@ Table 类允许你设置一个表格模板来指定布局设计。下面是模 .. php:method:: addRow([$args = [] [, ...]]) :param mixed $args: 包含行值的数组或多个字符串 - :returns: Table 实例(方法链) + :returns: Table 实例(方法链式调用) :rtype: Table 允许你向表格添加行。你可以提交数组或离散参数: @@ -151,7 +170,7 @@ Table 类允许你设置一个表格模板来指定布局设计。下面是模 .. php:method:: setEmpty($value) :param mixed $value: 放入空单元格中的值 - :returns: Table 实例(方法链) + :returns: Table 实例(方法链式调用) :rtype: Table 允许你为使用在任何空表格单元格中的默认值设置值。 @@ -161,7 +180,7 @@ Table 类允许你设置一个表格模板来指定布局设计。下面是模 .. php:method:: clear() - :returns: Table 实例(方法链) + :returns: Table 实例(方法链式调用) :rtype: Table 允许你清除表格标题、行数据和标题。 @@ -170,3 +189,10 @@ Table 类允许你设置一个表格模板来指定布局设计。下面是模 例子 .. literalinclude:: table/018.php + + .. php:method:: setSyncRowsWithHeading(bool $orderByKey) + + :returns: Table 实例(方法链式调用) + :rtype: Table + + 启用每个行数据键按照标题键进行排序。这样可以更好地控制数据在正确列中的显示位置。确保在调用第一个 ``addRow()`` 方法之前设置此值。 diff --git a/source/outgoing/table/019.php b/source/outgoing/table/019.php new file mode 100644 index 000000000..5478867fd --- /dev/null +++ b/source/outgoing/table/019.php @@ -0,0 +1,40 @@ +setHeading(['name' => 'Name', 'color' => 'Color', 'size' => 'Size']) + ->setSyncRowsWithHeading(true) + ->addRow(['color' => 'Blue', 'name' => 'Fred', 'size' => 'Small']) + ->addRow(['size' => 'Large', 'age' => '24', 'name' => 'Mary']) + ->addRow(['color' => 'Green']); + +echo $table->generate(); +?> + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameColorSize
FredBlueSmall
MaryLarge
Green
diff --git a/source/outgoing/table/020.php b/source/outgoing/table/020.php new file mode 100644 index 000000000..42bda4d2f --- /dev/null +++ b/source/outgoing/table/020.php @@ -0,0 +1,24 @@ + 'Blue', + 'name' => 'Fred', + 'size' => 'Small', + ], + [ + 'size' => 'Large', + 'age' => '24', + 'name' => 'Mary', + ], + [ + 'color' => 'Green', + ], +]; + +$table = new \CodeIgniter\View\Table(); + +$table->setHeading(['name' => 'Name', 'color' => 'Color', 'size' => 'Size']) + ->setSyncRowsWithHeading(true); + +echo $table->generate($data); diff --git a/source/outgoing/view_cells.rst b/source/outgoing/view_cells.rst index 376a559bd..8a0935cf5 100644 --- a/source/outgoing/view_cells.rst +++ b/source/outgoing/view_cells.rst @@ -1,10 +1,10 @@ ########## -视图单元格 +视图单元 ########## -许多应用程序都有可以在页面之间重复使用的小视图片段,或者在页面的不同位置重复使用。这些通常是帮助框、导航控件、广告、登录表单等。CodeIgniter 允许你将这些表示块的逻辑封装在视图单元格中。它们基本上是可以在其他视图中包含的迷你视图。它们可以内置逻辑来处理任何单元格特定的显示逻辑。它们可以用于通过将每个单元格的逻辑分隔到自己的类中来使视图更可读和可维护。 +许多应用程序都有一些小的视图片段,可以在页面之间重复使用,或者在页面的不同位置使用。这些通常是帮助框、导航控件、广告、登录表单等。CodeIgniter 允许你将这些呈现块的逻辑封装在视图单元中。它们基本上是可以包含在其他视图中的小视图。它们可以内置逻辑来处理任何特定于单元的显示逻辑。它们可以通过将每个单元的逻辑分离到自己的类中,使你的视图更易读和可维护。 -CodeIgniter 支持两种类型的视图单元格:简单和受控。简单视图单元格可以从你选择的任何类和方法生成,并不必遵循任何规则,只要它返回一个字符串即可。受控视图单元格必须从扩展 ``Codeigniter\View\Cells\Cell`` 类的类生成,该类提供了额外的功能,使你的视图单元格更灵活、更快速地使用。 +CodeIgniter 支持两种类型的视图单元:简单单元和受控单元。简单单元可以从你选择的任何类和方法生成,并且不必遵循任何规则,只需返回一个字符串即可。受控单元必须从扩展 ``Codeigniter\View\Cells\Cell`` 类的类生成,该类提供了额外的功能,使你的视图单元更加灵活和快速。 .. contents:: :local: @@ -13,288 +13,143 @@ CodeIgniter 支持两种类型的视图单元格:简单和受控。简单视图 .. _app-cells: ******************* -调用视图单元格 +调用视图单元 ******************* -无论你使用哪种类型的视图单元格,都可以通过在任何视图中使用 ``view_cell()`` 辅助方法来调用它。第一个参数是要调用的类和方法的名称,第二个参数是要传递给该方法的参数数组。该方法必须返回一个字符串,该字符串将插入到调用 ``view_cell()`` 方法的视图中。 -:: +无论你使用哪种类型的视图单元,都可以使用 ``view_cell()`` 辅助函数从任何视图中调用它。 - 'value1', 'param2' => 'value2']) ?> +第一个参数是要调用的类和方法的名称,第二个参数是要传递给方法的参数数组: -如果你没有包含类的完整命名空间,它将假定可以在 ``App\Cells`` 命名空间中找到它。所以,以下示例将尝试在 ``app/Cells/MyClass.php`` 中找到 ``MyClass`` 类。如果没有找到,将扫描所有命名空间,直到找到它,在每个命名空间的 ``Cells`` 子目录中搜索。 -:: +.. literalinclude:: view_cells/001.php - 'value1', 'param2' => 'value2']) ?> +单元方法必须返回一个字符串,该字符串将插入到调用 ``view_cell()`` 函数的视图中。 -.. note:: 从 v4.3.0 和更高版本开始支持省略命名空间。 +省略命名空间 +================== -你也可以以键/值字符串的方式传递参数: -:: +.. versionadded:: 4.3.0 - +如果你没有包含类的完整命名空间,它将假定可以在 ``App\Cells`` 命名空间中找到。因此,以下示例将尝试在 **app/Cells/MyClass.php** 中查找 ``MyClass`` 类。如果在那里找不到,将扫描所有命名空间,直到找到为止,在每个命名空间的 **Cells** 子目录中搜索: -************ -简单单元格 -************ +.. literalinclude:: view_cells/002.php + +将参数作为键/值字符串传递 +====================================== -简单单元格是从选择的方法返回字符串的类。警报消息单元格的一个简单示例如下: -:: +你还可以将参数作为键/值字符串传递: - namespace App\Cells; +.. literalinclude:: view_cells/003.php - class AlertMessage - { - public function show(array $params): string - { - return "
{$params['message']}
"; - } - } +************ +简单单元 +************ -你可以在视图内这样调用它: -:: +简单单元是从所选方法返回字符串的类。一个简单的警告消息单元的示例可能如下所示: - 'success', 'message' => 'The user has been updated.']) ?> +.. literalinclude:: view_cells/004.php -另外,你可以使用与方法中的参数变量匹配的参数名称以提高可读性。 -以这种方式使用时,在视图单元格调用中必须始终指定所有参数:: +你可以在视图中这样调用它: - // 在视图中 - +.. literalinclude:: view_cells/005.php - // 在单元格中 - public function recentPosts(string $category, int $limit) - { - $posts = $this->blogModel->where('category', $category) - ->orderBy('published_on', 'desc') - ->limit($limit) - ->get(); +此外,你可以使用与方法中的参数变量匹配的参数名称以提高可读性。 +当你以这种方式使用时,视图单元调用中必须始终指定所有参数: - return view('recentPosts', ['posts' => $posts]); - } +.. literalinclude:: view_cells/006.php + +.. literalinclude:: view_cells/007.php .. _controlled-cells: **************** -受控单元格 +受控单元 **************** .. versionadded:: 4.3.0 -受控单元格主要有两个目标:尽可能快速地构建单元格,并在视图需要时为视图提供额外的逻辑和灵活性。该类必须扩展 ``CodeIgniter\View\Cells\Cell``。它们应该在同一文件夹中有一个视图文件。按照惯例,类名应采用 PascalCase 后缀为 ``Cell``,视图应该是类名的 snake_case 版本,没有后缀。例如,如果有一个 ``MyCell`` 类,视图文件应该是 ``my.php``。 +受控单元有两个主要目标:尽可能快地构建单元,并为视图提供额外的逻辑和灵活性(如果需要)。该类必须扩展 ``CodeIgniter\View\Cells\Cell``。它们应该在同一文件夹中有一个视图文件。按照惯例,类名应为 PascalCase,后缀为 ``Cell``,视图应为类名的 snake_case 版本,不包括后缀。例如,如果你有一个 ``MyCell`` 类,视图文件应为 ``my.php``。 -.. note:: 在 v4.3.5 之前的版本中,生成的视图文件以 ``_cell.php`` 结尾。尽管 v4.3.5 和更高版本将生成不带 ``_cell`` 后缀的视图文件,但现有的视图文件仍然会被定位和加载。 +.. note:: 在 v4.3.5 之前,生成的视图文件以 ``_cell.php`` 结尾。尽管 v4.3.5 及更高版本将生成不带 ``_cell`` 后缀的视图文件,但现有的视图文件仍将被定位和加载。 -创建受控单元格 +创建受控单元 ========================== -在最基本的级别上,你需要在类中实现的是公共属性。这些属性将自动供视图文件使用。将上面的 AlertMessage 作为受控单元格实现如下: -:: - - // app/Cells/AlertMessageCell.php - namespace App\Cells; +在类中实现的最基本的级别上,你只需要实现公共属性。这些属性将自动提供给视图文件。将上面的 AlertMessage 实现为受控单元将如下所示: - use CodeIgniter\View\Cells\Cell; +.. literalinclude:: view_cells/008.php - class AlertMessageCell extends Cell - { - public $type; - public $message; - } +.. literalinclude:: view_cells/009.php - // app/Cells/alert_message.php -
- -
- - // 在主视图中调用: - +.. literalinclude:: view_cells/010.php .. _generating-cell-via-command: -通过命令生成单元格 +通过命令生成单元 =========================== -你还可以通过内置命令从 CLI 生成受控单元格。该命令是 ``php spark make:cell``。它接受一个参数,要创建的单元格的名称。名称应采用 PascalCase,类将在 ``app/Cells`` 目录中创建。视图文件也将在 ``app/Cells`` 目录中创建。 +你还可以通过 CLI 中的内置命令创建受控单元。该命令是 ``php spark make:cell``。它接受一个参数,要创建的单元的名称。名称应为 PascalCase,类将在 **app/Cells** 目录中创建。视图文件也将在 **app/Cells** 目录中创建。 -:: +.. code-block:: console - > php spark make:cell AlertMessageCell + php spark make:cell AlertMessageCell 使用不同的视图 ====================== -你可以通过在类中设置 ``view`` 属性来指定自定义视图名称。视图的定位与正常视图相同。 - -:: - - namespace App\Cells; - - use CodeIgniter\View\Cells\Cell; +你可以通过在类中设置 ``view`` 属性来指定自定义视图名称。视图将像正常情况下一样被定位: - class AlertMessageCell extends Cell - { - public $type; - public $message; - - protected $view = 'my/custom/view'; - } +.. literalinclude:: view_cells/011.php 自定义渲染 ======================= -如果你需要更多地控制 HTML 的渲染,可以实现一个 ``render()`` 方法。此方法允许你执行其他逻辑并在需要时向视图传递额外的数据。 ``render()`` 方法必须返回一个字符串。要利用受控单元格的全部功能,你应该使用 ``$this->view()`` 代替正常的 ``view()`` 辅助函数。 -:: - - namespace App\Cells; - - use CodeIgniter\View\Cells\Cell; - - class AlertMessageCell extends Cell - { - public $type; - public $message; +如果你需要更多控制HTML的渲染过程,可以实现一个 ``render()`` 方法。该方法允许你执行其他逻辑并向视图传递额外的数据(如果需要)。``render()`` 方法必须返回一个字符串。为了充分利用受控单元的全部功能,你应该使用 ``$this->view()`` 而不是普通的 ``view()`` 辅助函数: - public function render(): string - { - return $this->view('my/custom/view', ['extra' => 'data']); - } - } +.. literalinclude:: view_cells/012.php 计算属性 =================== -如果你需要为一个或多个属性执行其他逻辑,可以使用计算属性。这需要将属性设置为 ``protected`` 或 ``private``,并实现一个由属性名称包围在 ``get`` 和 ``Property`` 之间的公共方法。 -:: +如果你需要为一个或多个属性执行其他逻辑,可以使用计算属性。这需要将属性设置为 ``protected`` 或 ``private``,并实现一个公共方法,该方法的名称由属性名称包围 ``get`` 和 ``Property`` 组成: - // 在视图中初始化受保护的属性 - view_cell('AlertMessageCell', ['type' => 'note', 'message' => 'test']); +.. literalinclude:: view_cells/013.php - // app/Cells/AlertMessageCell.php - namespace App\Cells; +.. literalinclude:: view_cells/014.php - use CodeIgniter\View\Cells\Cell; +.. literalinclude:: view_cells/015.php - class AlertMessageCell extends Cell - { - protected $type; - protected $message; - private $computed; +.. important:: 无法设置在单元初始化期间声明为私有的属性。 - public function mount() - { - $this->computed = sprintf('%s - %s', $this->type, $this->message); - } - - public function getComputedProperty(): string - { - return $this->computed; - } - - public function getTypeProperty(): string - { - return $this->type; - } - - public function getMessageProperty(): string - { - return $this->message; - } - } - - // app/Cells/alert_message.php -
-

type -

-

message -

-

computed:

-
- -.. important:: 在单元格初始化期间,你不能设置声明为私有的属性。 - -呈现方法 +演示方法 ==================== -有时你需要对视图执行其他逻辑,但你不想将其作为参数传递。你可以实现一个将从单元格的视图本身调用的方法。这可以帮助提高视图的可读性。 -:: - - // app/Cells/RecentPostsCell.php - namespace App\Cells; - - use CodeIgniter\View\Cells\Cell; +有时你需要为视图执行其他逻辑,但不想将其作为参数传递。你可以实现一个在单元的视图内部调用的方法。这可以提高视图的可读性: - class RecentPostsCell extends Cell - { - protected $posts; +.. literalinclude:: view_cells/016.php - public function linkPost($post) - { - return anchor('posts/' . $post->id, $post->title); - } - } - - // app/Cells/recent_posts.php -
    - -
  • linkPost($post) ?>
  • - -
+.. literalinclude:: view_cells/017.php 执行设置逻辑 ====================== -如果你需要在渲染视图之前执行其他逻辑,可以实现一个 ``mount()`` 方法。在类实例化后立即调用此方法,可用于设置其他属性或执行其他逻辑。 - -:: - - namespace App\Cells; - - use CodeIgniter\View\Cells\Cell; - - class RecentPostsCell extends Cell - { - protected $posts; - - public function mount() - { - $this->posts = model('PostModel')->getRecent(); - } - } - -你可以通过将它们作为数组传递给 ``view_cell()`` 辅助函数来向 ``mount()`` 方法传递其他参数。传递的与 ``mount`` 方法参数名称匹配的任何参数都将被传递进去。 -:: - - // app/Cells/RecentPostsCell.php - namespace App\Cells; +如果你需要在渲染视图之前执行其他逻辑,可以实现一个 ``mount()`` 方法。该方法将在类实例化后立即调用,并可用于设置其他属性或执行其他逻辑: - use CodeIgniter\View\Cells\Cell; +.. literalinclude:: view_cells/018.php - class RecentPostsCell extends Cell - { - protected $posts; +你可以通过将它们作为数组传递给 ``view_cell()`` 辅助函数来将其他参数传递给 ``mount()`` 方法。任何与 ``mount()`` 方法的参数名称匹配的参数将被传递进去: - public function mount(?int $categoryId) - { - $this->posts = model('PostModel') - ->when($categoryId, function ($query, $category) { - return $query->where('category_id', $categoryId); - }) - ->getRecent(); - } - } +.. literalinclude:: view_cells/019.php - // 在主视图中调用: - 5]) ?> +.. literalinclude:: view_cells/020.php ************ -单元格缓存 +单元缓存 ************ -你可以通过将要缓存数据的秒数作为第三个参数传入来缓存视图单元格调用的结果。这将使用当前配置的缓存引擎。 -:: +你可以通过将要缓存数据的秒数作为第三个参数传递来缓存视图单元调用的结果。这将使用当前配置的缓存引擎: - // 缓存视图 5 分钟 - +.. literalinclude:: view_cells/021.php -如果喜欢,你可以提供一个自定义名称代替自动生成的名称,方法是将新名称作为第四个参数传递:: +如果需要,你可以提供一个自定义名称,而不是自动生成的名称,通过将新名称作为第四个参数传递: - // 缓存视图 5 分钟 - +.. literalinclude:: view_cells/022.php diff --git a/source/outgoing/view_cells/001.php b/source/outgoing/view_cells/001.php new file mode 100644 index 000000000..2a9e43617 --- /dev/null +++ b/source/outgoing/view_cells/001.php @@ -0,0 +1,2 @@ +// In a View. + 'value1', 'param2' => 'value2']) ?> diff --git a/source/outgoing/view_cells/002.php b/source/outgoing/view_cells/002.php new file mode 100644 index 000000000..1dcea3556 --- /dev/null +++ b/source/outgoing/view_cells/002.php @@ -0,0 +1,2 @@ +// In a View. + 'value1', 'param2' => 'value2']) ?> diff --git a/source/outgoing/view_cells/003.php b/source/outgoing/view_cells/003.php new file mode 100644 index 000000000..93fa8eea5 --- /dev/null +++ b/source/outgoing/view_cells/003.php @@ -0,0 +1,2 @@ +// In a View. + diff --git a/source/outgoing/view_cells/004.php b/source/outgoing/view_cells/004.php new file mode 100644 index 000000000..26009d86a --- /dev/null +++ b/source/outgoing/view_cells/004.php @@ -0,0 +1,11 @@ +{$params['message']}"; + } +} diff --git a/source/outgoing/view_cells/005.php b/source/outgoing/view_cells/005.php new file mode 100644 index 000000000..776b26573 --- /dev/null +++ b/source/outgoing/view_cells/005.php @@ -0,0 +1,2 @@ +// In a View. + 'success', 'message' => 'The user has been updated.']) ?> diff --git a/source/outgoing/view_cells/006.php b/source/outgoing/view_cells/006.php new file mode 100644 index 000000000..64157d309 --- /dev/null +++ b/source/outgoing/view_cells/006.php @@ -0,0 +1,2 @@ +// In a View. + diff --git a/source/outgoing/view_cells/007.php b/source/outgoing/view_cells/007.php new file mode 100644 index 000000000..ab1e56b3c --- /dev/null +++ b/source/outgoing/view_cells/007.php @@ -0,0 +1,20 @@ +blogModel->where('category', $category) + ->orderBy('published_on', 'desc') + ->limit($limit) + ->get(); + + return view('recentPosts', ['posts' => $posts]); + } +} diff --git a/source/outgoing/view_cells/008.php b/source/outgoing/view_cells/008.php new file mode 100644 index 000000000..f8b8c68ea --- /dev/null +++ b/source/outgoing/view_cells/008.php @@ -0,0 +1,13 @@ +"> + + diff --git a/source/outgoing/view_cells/010.php b/source/outgoing/view_cells/010.php new file mode 100644 index 000000000..3fdcbd9d1 --- /dev/null +++ b/source/outgoing/view_cells/010.php @@ -0,0 +1,2 @@ +// Called in main View: + diff --git a/source/outgoing/view_cells/011.php b/source/outgoing/view_cells/011.php new file mode 100644 index 000000000..75f37da19 --- /dev/null +++ b/source/outgoing/view_cells/011.php @@ -0,0 +1,13 @@ +view('my/custom/view', ['extra' => 'data']); + } +} diff --git a/source/outgoing/view_cells/013.php b/source/outgoing/view_cells/013.php new file mode 100644 index 000000000..32e02a03d --- /dev/null +++ b/source/outgoing/view_cells/013.php @@ -0,0 +1,2 @@ +// In a View. Initialize the protected properties. + 'note', 'message' => 'test']) ?> diff --git a/source/outgoing/view_cells/014.php b/source/outgoing/view_cells/014.php new file mode 100644 index 000000000..29378e119 --- /dev/null +++ b/source/outgoing/view_cells/014.php @@ -0,0 +1,34 @@ +computed = sprintf('%s - %s', $this->type, $this->message); + } + + public function getComputedProperty(): string + { + return $this->computed; + } + + public function getTypeProperty(): string + { + return $this->type; + } + + public function getMessageProperty(): string + { + return $this->message; + } +} diff --git a/source/outgoing/view_cells/015.php b/source/outgoing/view_cells/015.php new file mode 100644 index 000000000..382152cfb --- /dev/null +++ b/source/outgoing/view_cells/015.php @@ -0,0 +1,6 @@ +// app/Cells/alert_message.php +
+

type -

+

message -

+

computed:

+
diff --git a/source/outgoing/view_cells/016.php b/source/outgoing/view_cells/016.php new file mode 100644 index 000000000..4b2c10ef0 --- /dev/null +++ b/source/outgoing/view_cells/016.php @@ -0,0 +1,17 @@ +id, $post->title); + } +} diff --git a/source/outgoing/view_cells/017.php b/source/outgoing/view_cells/017.php new file mode 100644 index 000000000..359eb6fce --- /dev/null +++ b/source/outgoing/view_cells/017.php @@ -0,0 +1,6 @@ +// app/Cells/recent_posts.php +
    + +
  • linkPost($post) ?>
  • + +
diff --git a/source/outgoing/view_cells/018.php b/source/outgoing/view_cells/018.php new file mode 100644 index 000000000..3f64e6846 --- /dev/null +++ b/source/outgoing/view_cells/018.php @@ -0,0 +1,15 @@ +posts = model('PostModel')->orderBy('created_at', 'DESC')->findAll(10); + } +} diff --git a/source/outgoing/view_cells/019.php b/source/outgoing/view_cells/019.php new file mode 100644 index 000000000..df78ab0ac --- /dev/null +++ b/source/outgoing/view_cells/019.php @@ -0,0 +1,23 @@ +posts = model('PostModel') + ->when( + $categoryId, + static fn ($query, $categoryId) => $query->where('category_id', $categoryId) + ) + ->orderBy('created_at', 'DESC') + ->findAll(10); + } +} diff --git a/source/outgoing/view_cells/020.php b/source/outgoing/view_cells/020.php new file mode 100644 index 000000000..55d678821 --- /dev/null +++ b/source/outgoing/view_cells/020.php @@ -0,0 +1,2 @@ +// Called in main View: + 5]) ?> diff --git a/source/outgoing/view_cells/021.php b/source/outgoing/view_cells/021.php new file mode 100644 index 000000000..a6b814d1f --- /dev/null +++ b/source/outgoing/view_cells/021.php @@ -0,0 +1,2 @@ +// Cache the view for 5 minutes + diff --git a/source/outgoing/view_cells/022.php b/source/outgoing/view_cells/022.php new file mode 100644 index 000000000..8c5886957 --- /dev/null +++ b/source/outgoing/view_cells/022.php @@ -0,0 +1,2 @@ +// Cache the view for 5 minutes + diff --git a/source/outgoing/view_layouts.rst b/source/outgoing/view_layouts.rst index 65cd021a7..09dd6149f 100644 --- a/source/outgoing/view_layouts.rst +++ b/source/outgoing/view_layouts.rst @@ -8,6 +8,8 @@ CodeIgniter 支持一个简单且非常灵活的布局系统,可以轻松地在整个应用程序中使用一个或多个基本页面布局。布局支持可以从任何被渲染的视图中插入的内容部分。你可以创建不同的布局以支持单列、双列、博客存档页面等。布局从不直接渲染。相反,你渲染一个视图,它指定了它想要扩展的布局。 +.. _creating-a-layout: + ***************** 创建布局 ***************** @@ -26,7 +28,22 @@ CodeIgniter 支持一个简单且非常灵活的布局系统,可以轻松地在 -``renderSection()`` 方法只有一个参数 - 部分的名称。这样任何子视图都知道要命名内容部分的名称。 +``renderSection()`` 方法有两个参数:``$sectionName`` 和 ``$saveData``。``$sectionName`` 是任何子视图用来命名内容部分的部分名称。如果布尔参数 ``$saveData`` 设置为 true,该方法会保存数据以供后续调用使用。否则,该方法在显示内容后会清除数据。 + +例如 **app/Views/welcome_message.php**:: + + + + + <?= $this->renderSection('page_title', true) ?> + + +

renderSection('page_title') ?>

+

renderSection('content') ?>

+ + + +.. note:: ``$saveData`` 可以在 v4.4.0 版本之后使用。 ********************** 在视图中使用布局 diff --git a/source/outgoing/view_parser.rst b/source/outgoing/view_parser.rst index 7f55f71e5..afb899846 100644 --- a/source/outgoing/view_parser.rst +++ b/source/outgoing/view_parser.rst @@ -356,18 +356,11 @@ upper 将字符串显示为全部大写。 自定义过滤器 -------------- -你可以通过编辑 **app/Config/View.php** 并向 ``$filters`` 数组添加新条目来轻松创建自己的过滤器。每个键是在视图中调用过滤器的名称,其值是任何有效的 PHP 可调用函数: +你可以通过编辑 **app/Config/View.php** 并向 ``$filters`` 数组中添加新条目来轻松创建自己的过滤器。每个键是视图中调用过滤器的名称,其值是任何有效的 PHP 可调用对象: .. literalinclude:: view_parser/012.php -将 PHP 原生函数用作过滤器 -------------------------------- - -你可以通过编辑 **app/Config/View.php** 并向 ``$filters`` 数组添加新条目,将原生的 PHP 函数用作过滤器。每个键是在视图中调用的原生 PHP 函数的名称,其值是任何有效的原生 PHP 函数,前缀为: - -.. literalinclude:: view_parser/013.php - -插件 +解析器插件 ============== 插件允许你扩展解析器,为每个项目添加自定义功能。它们可以是任何 PHP 可调用的,因此实现起来非常简单。在模板中,插件由 ``{+ +}`` 标记指定:: diff --git a/source/outgoing/view_parser/012.php b/source/outgoing/view_parser/012.php index 000642837..696bd2d29 100644 --- a/source/outgoing/view_parser/012.php +++ b/source/outgoing/view_parser/012.php @@ -7,8 +7,8 @@ class View extends BaseView { public $filters = [ - 'abs' => '\CodeIgniter\View\Filters::abs', - 'capitalize' => '\CodeIgniter\View\Filters::capitalize', + 'foo' => '\Some\Class::methodName', + 'str_repeat' => 'str_repeat', // native php function ]; // ... diff --git a/source/outgoing/view_parser/023.php b/source/outgoing/view_parser/023.php index 1bdb256d9..8e65d489f 100644 --- a/source/outgoing/view_parser/023.php +++ b/source/outgoing/view_parser/023.php @@ -1,3 +1,3 @@ render('myview'); +return $parser->renderString('
  • Item 1
  • Item 2
'); diff --git a/source/testing/controllers.rst b/source/testing/controllers.rst index 50158fd23..e3b17f9fe 100644 --- a/source/testing/controllers.rst +++ b/source/testing/controllers.rst @@ -88,6 +88,10 @@ withURI(string $uri) 在测试期间始终提供 URI 可以避免意外情况,这是一种好的实践。 +.. note:: 从 v4.4.0 版本开始,该方法使用 URI 创建一个新的 Request 实例。 + 因为 Request 实例应该具有 URI 实例。如果 URI 字符串中的主机名与 ``Config\App`` 中的设置不匹配, + 将会设置有效的主机名。 + withBody($body) --------------- diff --git a/source/testing/debugging.rst b/source/testing/debugging.rst index 82be67225..b8aa7c622 100644 --- a/source/testing/debugging.rst +++ b/source/testing/debugging.rst @@ -141,3 +141,18 @@ CodeIgniter 带有以下收集器: ``getVarData()`` 应返回包含要显示的键值对数组的数组。外部数组的键为 Vars 选项卡中的部分名称: .. literalinclude:: debugging/006.php + +.. _debug-toolbar-hot-reload: + +热重载 +============= + +.. versionadded:: 4.4.0 + +调试工具栏包含一个名为热重载的功能,它允许你对应用程序的代码进行更改,并在浏览器中自动重新加载,而无需刷新页面。这在开发过程中非常省时。 + +在开发过程中启用热重载,你可以点击工具栏左侧的按钮,它看起来像一个刷新图标。这将在所有页面上启用热重载,直到你禁用它。 + +热重载通过每秒扫描 **app** 目录中的文件并查找更改来工作。如果发现任何更改,它将向浏览器发送消息以重新加载页面。它不会扫描任何其他目录,因此如果你对 **app** 目录之外的文件进行更改,你需要手动刷新页面。 + +如果你需要监视 **app** 目录之外的文件,或者由于项目的大小而导致速度较慢,你可以在 **app/Config/Toolbar.php** 配置文件的 ``$watchedDirectories`` 和 ``$watchedExtensions`` 属性中指定要扫描的目录和文件扩展名。 diff --git a/source/testing/feature.rst b/source/testing/feature.rst index 5555d41aa..7678f559f 100644 --- a/source/testing/feature.rst +++ b/source/testing/feature.rst @@ -15,19 +15,29 @@ HTTP 功能测试 .. literalinclude:: feature/001.php +.. _feature-requesting-a-page: + 请求页面 ================= -从本质上讲,功能测试只是允许你在应用程序上调用一个端点并获取结果。要做到这一点,请使用 ``call()`` 方法。第一个参数是要使用的 HTTP 方法(最常见的是 GET 或 POST)。第二个参数是要测试的站点上的路径。第三个参数接受一个数组,用于填充与你使用的 HTTP 动词对应的全局变量。因此, **GET** 方法会填充 **$_GET** 变量, **post** 请求会填充 **$_POST** 数组。 +基本上,功能测试允许你调用应用程序上的一个端点,并获取结果返回。 +为此,你可以使用 ``call()`` 方法。 + +1. 第一个参数是要使用的 HTTP 方法(通常是 GET 或 POST)。 +2. 第二个参数是要测试的站点上的 URI 路径。 +3. 第三个参数 ``$params`` 接受一个数组,用于填充你正在使用的 HTTP 动词的超全局变量。因此,**GET** 方法将填充 **$_GET** 变量,而 **POST** 请求将填充 **$_POST** 数组。``$params`` 也用于 :ref:`feature-formatting-the-request`。 + + .. note:: ``$params`` 数组并不适用于每个 HTTP 动词,但为了保持一致性而包含在内。 .. literalinclude:: feature/002.php +缩写方法 +----------------- + 为每个 HTTP 动词提供了缩写方法,以减少输入并增加清晰度: .. literalinclude:: feature/003.php -.. note:: 并非每个 HTTP 动词都适合 ``$params`` 数组,但为了一致性而包含它。 - 设置不同的路由 ------------------------ @@ -58,17 +68,28 @@ HTTP 功能测试 .. literalinclude:: feature/007.php +.. _feature-formatting-the-request: + 格式化请求 ----------------------- -你可以使用 ``withBodyFormat()`` 方法设置请求正文的格式。当前,这支持 `json` 或 `xml`。这将获取传递到 ``call()``、``post()``、``get()`` 等中的参数,并以给定格式分配给请求正文。这也会相应地为请求设置 `Content-Type` 标头。当测试 JSON 或 XML API 时,这很有用,以便以控制器期望的形式设置请求。 +你可以使用 ``withBodyFormat()`` 方法设置请求体的格式。目前支持 ``json`` 或 ``xml``。 +这在测试 JSON 或 XML API 时非常有用,因为你可以设置请求的格式,以符合控制器的预期。 + +这将接收传递给 ``call()``, ``post()``, ``get()``... 的参数,并将它们分配给请求体,以给定的格式。 + +这还将相应地设置请求的 `Content-Type` 标头。 .. literalinclude:: feature/008.php -设置正文 +.. _feature-setting-the-body: + +设置 Body ---------------- -你可以使用 ``withBody()`` 方法设置请求的正文。这允许你按照想要的格式设置正文格式。如果要测试更复杂的 XML,建议使用此方法。这也不会为你设置 Content-Type 标头,如果需要,可以使用 ``withHeaders()`` 方法设置它。 +你可以使用 ``withBody()`` 方法设置请求的 Body。这允许你按照所需的格式设置请求 Body。如果你有更复杂的 XML 需要测试,建议使用此方法。 + +这不会为你设置 `Content-Type` 标头。如果需要,你可以使用 ``withHeaders()`` 方法设置它。 检查响应 ===================== diff --git a/source/testing/overview.rst b/source/testing/overview.rst index df2096649..90a1a718d 100644 --- a/source/testing/overview.rst +++ b/source/testing/overview.rst @@ -22,17 +22,23 @@ Composer 推荐的方法是使用 `Composer `__ 在项目中安装它。尽管可以全局安装,但我们不建议这样做,因为随着时间的推移,它可能与系统上的其他项目造成兼容性问题。 -确保系统中安装了 Composer。从项目根目录(包含应用程序和系统目录的目录)命令行输入以下命令:: +确保系统中安装了 Composer。从项目根目录(包含应用程序和系统目录的目录)命令行输入以下命令: - > composer require --dev phpunit/phpunit +.. code-block:: console -这将为当前 PHP 版本安装正确的版本。完成后,可以通过输入以下命令来运行此项目的所有测试:: + composer require --dev phpunit/phpunit - > vendor/bin/phpunit +这将为当前 PHP 版本安装正确的版本。完成后,可以通过输入以下命令来运行此项目的所有测试: -如果使用 Windows,请使用以下命令:: +.. code-block:: console - > vendor\bin\phpunit + vendor/bin/phpunit + +如果使用 Windows,请使用以下命令: + +.. code-block:: console + + vendor\bin\phpunit Phar ---- @@ -128,7 +134,7 @@ assertHeaderEmitted($header, $ignoreCase = false) .. literalinclude:: overview/009.php -.. note:: 带有此内容的测试用例应 `在 PHPunit 中作为单独进程运行 `_。 +.. note:: 带有此内容的测试用例应 `在 PHPunit 中作为单独进程运行 `_。 assertHeaderNotEmitted($header, $ignoreCase = false) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -137,7 +143,7 @@ assertHeaderNotEmitted($header, $ignoreCase = false) .. literalinclude:: overview/010.php -.. note:: 带有此内容的测试用例应 `在 PHPunit 中作为单独进程运行 `_。 +.. note:: 带有此内容的测试用例应 `在 PHPunit 中作为单独进程运行 `_。 assertCloseEnough($expected, $actual, $message = '', $tolerance = 1) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -204,6 +210,8 @@ Services::reset() 你也可以使用 ``CIUnitTestCase`` 提供的 ``$this->resetServices()`` 方法。 +.. note:: 此方法会重置所有服务的状态,并且 ``RouteCollection`` 将不包含任何路由。如果你想要使用加载的路由,你需要调用 ``loadRoutes()`` 方法,例如 ``Services::routes()->loadRoutes()``。 + Services::resetSingle(string $name) ----------------------------------- diff --git a/source/testing/response.rst b/source/testing/response.rst index d74446fd9..2f35e4e1e 100644 --- a/source/testing/response.rst +++ b/source/testing/response.rst @@ -5,6 +5,7 @@ ``TestResponse`` 类提供了许多有用的函数来解析测试用例中的响应并对其进行测试。通常, ``TestResponse`` 将作为你的结果提供,:doc:`控制器测试 ` 或 :doc:`HTTP 功能测试 `,但你始终可以直接使用任何 ``ResponseInterface`` 创建自己的: .. literalinclude:: response/001.php + :lines: 2- .. contents:: :local: @@ -24,6 +25,7 @@ request() 如果在测试期间设置了请求,你可以直接访问请求对象: .. literalinclude:: response/002.php + :lines: 2- response() ---------- @@ -31,6 +33,7 @@ response() 这允许你直接访问响应对象: .. literalinclude:: response/003.php + :lines: 2- 检查响应状态 ======================== @@ -41,6 +44,7 @@ isOK() 根据响应是否被视为“正常”返回布尔值 true/false。这主要由 200 或 300 范围内的响应状态代码确定。如果重定向,空响应正文不被视为有效。 .. literalinclude:: response/004.php + :lines: 2- assertOK() ---------- @@ -48,6 +52,7 @@ assertOK() 此断言简单地使用 ``isOK()`` 方法来测试响应。``assertNotOK()`` 是此断言的逆。 .. literalinclude:: response/005.php + :lines: 2- isRedirect() ------------ @@ -55,6 +60,7 @@ isRedirect() 根据响应是否重定向返回布尔值 true/false。 .. literalinclude:: response/006.php + :lines: 2- assertRedirect() ---------------- @@ -62,6 +68,7 @@ assertRedirect() 断言响应是 RedirectResponse 的一个实例。``assertNotRedirect()`` 是此断言的逆。 .. literalinclude:: response/007.php + :lines: 2- assertRedirectTo() ------------------ @@ -69,6 +76,7 @@ assertRedirectTo() 断言响应是一个 RedirectResponse 实例,且目标与给定的 uri 匹配。 .. literalinclude:: response/008.php + :lines: 2- getRedirectUrl() ---------------- @@ -76,6 +84,7 @@ getRedirectUrl() 返回设置为 RedirectResponse 的 URL,如果失败则为 null。 .. literalinclude:: response/009.php + :lines: 2- assertStatus(int $code) ----------------------- @@ -83,6 +92,7 @@ assertStatus(int $code) 断言返回的 HTTP 状态码匹配 $code。 .. literalinclude:: response/010.php + :lines: 2- 会话断言 ================== @@ -93,6 +103,7 @@ assertSessionHas(string $key, $value = null) 断言结果会话中存在一个值。如果传递了 $value,还将断言变量的值与指定的相匹配。 .. literalinclude:: response/011.php + :lines: 2- assertSessionMissing(string $key) --------------------------------- @@ -100,6 +111,7 @@ assertSessionMissing(string $key) 断言结果会话不包括指定的 $key。 .. literalinclude:: response/012.php + :lines: 2- 标头断言 ================= @@ -110,6 +122,7 @@ assertHeader(string $key, $value = null) 断言响应中存在一个名为 ``$key`` 的标头。如果 ``$value`` 非空,还将断言值匹配。 .. literalinclude:: response/013.php + :lines: 2- assertHeaderMissing(string $key) -------------------------------- @@ -117,6 +130,7 @@ assertHeaderMissing(string $key) 断言响应中不存在名为 ``$key`` 的标头。 .. literalinclude:: response/014.php + :lines: 2- Cookie 断言 ================= @@ -127,6 +141,7 @@ assertCookie(string $key, $value = null, string $prefix = '') 断言响应中存在一个名为 ``$key`` 的 Cookie。如果 ``$value`` 非空,还将断言值匹配。如有必要,可以通过第三个参数传入前缀来设置 Cookie 前缀。 .. literalinclude:: response/015.php + :lines: 2- assertCookieMissing(string $key) -------------------------------- @@ -134,6 +149,7 @@ assertCookieMissing(string $key) 断言响应中不存在名为 ``$key`` 的 Cookie。 .. literalinclude:: response/016.php + :lines: 2- assertCookieExpired(string $key, string $prefix = '') ----------------------------------------------------- @@ -141,8 +157,9 @@ assertCookieExpired(string $key, string $prefix = '') 断言一个名为 ``$key`` 的 Cookie 存在,但已过期。如有必要,可以通过第二个参数传入前缀来设置 Cookie 前缀。 .. literalinclude:: response/017.php + :lines: 2- -DOM 帮助方法 +DOM 辅助函数 =============== 你得到的响应包含许多帮助方法来检查响应中的 HTML 输出。这些在测试中的断言中很有用。 @@ -150,13 +167,15 @@ DOM 帮助方法 see() ----- -``see()`` 方法检查页面上的文本,看它是否自身存在,或者更具体地说是在由类型、类或 id 指定的标记内存在: +根据页面上的文本是否存在,返回一个布尔值 true/false。可以通过 type、class 或 id 来指定文本所在的标签。 .. literalinclude:: response/018.php + :lines: 2- ``dontSee()`` 方法正好相反: .. literalinclude:: response/019.php + :lines: 2- seeElement() ------------ @@ -164,6 +183,7 @@ seeElement() ``seeElement()`` 和 ``dontSeeElement()`` 与前面的方法非常相似,但不检查元素的值。相反,它们仅检查元素是否存在于页面上: .. literalinclude:: response/020.php + :lines: 2- seeLink() --------- @@ -171,6 +191,7 @@ seeLink() 你可以使用 ``seeLink()`` 来确保页面上存在具有指定文本的链接: .. literalinclude:: response/021.php + :lines: 2- seeInField() ------------ @@ -178,6 +199,7 @@ seeInField() ``seeInField()`` 方法检查是否存在具有给定名称和值的输入标签: .. literalinclude:: response/022.php + :lines: 2- seeCheckboxIsChecked() ---------------------- @@ -185,6 +207,7 @@ seeCheckboxIsChecked() 最后,你可以使用 ``seeCheckboxIsChecked()`` 方法检查复选框是否存在并已被选中: .. literalinclude:: response/023.php + :lines: 2- DOM 断言 ============== @@ -197,6 +220,7 @@ assertSee(string $search = null, string $element = null) 断言文本/HTML 存在于页面上,无论是自身存在还是更具体地说是存在于由类型、类或 id 指定的标记内: .. literalinclude:: response/024.php + :lines: 2- assertDontSee(string $search = null, string $element = null) ------------------------------------------------------------ @@ -204,6 +228,7 @@ assertDontSee(string $search = null, string $element = null) 与 ``assertSee()`` 方法完全相反: .. literalinclude:: response/025.php + :lines: 2- assertSeeElement(string $search) -------------------------------- @@ -211,6 +236,7 @@ assertSeeElement(string $search) 类似于 ``assertSee()``,但是这只检查存在的元素。它不检查特定文本: .. literalinclude:: response/026.php + :lines: 2- assertDontSeeElement(string $search) ------------------------------------ @@ -218,6 +244,7 @@ assertDontSeeElement(string $search) 类似于 ``assertSee()``,但是这只检查缺失的现有元素。它不检查特定文本: .. literalinclude:: response/027.php + :lines: 2- assertSeeLink(string $text, string $details = null) --------------------------------------------------- @@ -225,6 +252,7 @@ assertSeeLink(string $text, string $details = null) 断言找到一个锚定标签,其标签体匹配 ``$text``: .. literalinclude:: response/028.php + :lines: 2- assertSeeInField(string $field, string $value = null) ----------------------------------------------------- @@ -232,6 +260,7 @@ assertSeeInField(string $field, string $value = null) 断言存在具有给定名称和值的输入标签: .. literalinclude:: response/029.php + :lines: 2- 使用 JSON ================= @@ -244,19 +273,22 @@ getJSON() 此方法将以 JSON 字符串的形式返回响应正文: .. literalinclude:: response/030.php + :lines: 2- 你可以使用此方法来确定 ``$response`` 是否确实包含 JSON 内容: .. literalinclude:: response/031.php + :lines: 2- .. note:: 请注意结果中的 JSON 字符串将美化打印。 assertJSONFragment(array $fragment) ----------------------------------- -断言 $fragment 在 JSON 响应中找到。它不需要匹配整个 JSON 值。 +断言 ``$fragment`` 在 JSON 响应中找到。它不需要匹配整个 JSON 值。 .. literalinclude:: response/032.php + :lines: 2- assertJSONExact($test) ---------------------- diff --git a/source/testing/response/018.php b/source/testing/response/018.php index a76172d50..a32d2742d 100644 --- a/source/testing/response/018.php +++ b/source/testing/response/018.php @@ -1,10 +1,21 @@ see('Hello World'); +if ($results->see('Hello World')) { + // ... +} + // Check that "Hello World" is within an h1 tag -$results->see('Hello World', 'h1'); +if ($results->see('Hello World', 'h1')) { + // ... +} + // Check that "Hello World" is within an element with the "notice" class -$results->see('Hello World', '.notice'); +if ($results->see('Hello World', '.notice')) { + // ... +} + // Check that "Hello World" is within an element with id of "title" -$results->see('Hello World', '#title'); +if ($results->see('Hello World', '#title')) { + // ... +} diff --git a/source/testing/response/019.php b/source/testing/response/019.php index f96493b52..128e6862b 100644 --- a/source/testing/response/019.php +++ b/source/testing/response/019.php @@ -1,6 +1,11 @@ dontSee('Hello World'); +if ($results->dontSee('Hello World')) { + // ... +} + // Checks that "Hellow World" does NOT exist within any h1 tag -$results->dontSee('Hello World', 'h1'); +if ($results->dontSee('Hello World', 'h1')) { + // ... +} diff --git a/source/testing/response/020.php b/source/testing/response/020.php index 8b716717b..94598bf98 100644 --- a/source/testing/response/020.php +++ b/source/testing/response/020.php @@ -1,8 +1,16 @@ seeElement('.notice'); +if ($results->seeElement('.notice')) { + // ... +} + // Check that an element with id 'title' exists -$results->seeElement('#title'); +if ($results->seeElement('#title')) { + // ... +} + // Verify that an element with id 'title' does NOT exist -$results->dontSeeElement('#title'); +if ($results->dontSeeElement('#title')) { + // ... +} diff --git a/source/testing/response/021.php b/source/testing/response/021.php index bc74e3ad9..d073ec5a3 100644 --- a/source/testing/response/021.php +++ b/source/testing/response/021.php @@ -1,6 +1,11 @@ seeLink('Upgrade Account'); +if ($results->seeLink('Upgrade Account')) { + // ... +} + // Check that a link exists with 'Upgrade Account' as the text, AND a class of 'upsell' -$results->seeLink('Upgrade Account', '.upsell'); +if ($results->seeLink('Upgrade Account', '.upsell')) { + // ... +} diff --git a/source/testing/response/022.php b/source/testing/response/022.php index 9bb549c9d..791d72da4 100644 --- a/source/testing/response/022.php +++ b/source/testing/response/022.php @@ -1,6 +1,11 @@ seeInField('user', 'John Snow'); +if ($results->seeInField('user', 'John Snow')) { + // ... +} + // Check a multi-dimensional input -$results->seeInField('user[name]', 'John Snow'); +if ($results->seeInField('user[name]', 'John Snow')) { + // ... +} diff --git a/source/testing/response/023.php b/source/testing/response/023.php index 3378144f6..af7c768e3 100644 --- a/source/testing/response/023.php +++ b/source/testing/response/023.php @@ -1,6 +1,11 @@ seeCheckboxIsChecked('.foo'); +if ($results->seeCheckboxIsChecked('.foo')) { + // ... +} + // Check if checkbox with id of 'bar' is checked -$results->seeCheckboxIsChecked('#bar'); +if ($results->seeCheckboxIsChecked('#bar')) { + // ... +} diff --git a/source/testing/response/024.php b/source/testing/response/024.php index 822d62974..9238cd2fd 100644 --- a/source/testing/response/024.php +++ b/source/testing/response/024.php @@ -1,10 +1,13 @@ assertSee('Hello World'); -// Check that "Hello World" is within an h1 tag + +// Verify that "Hello World" is within an h1 tag $result->assertSee('Hello World', 'h1'); -// Check that "Hello World" is within an element with the "notice" class + +// Verify that "Hello World" is within an element with the "notice" class $result->assertSee('Hello World', '.notice'); -// Check that "Hello World" is within an element with id of "title" + +// Verify that "Hello World" is within an element with id of "title" $result->assertSee('Hello World', '#title'); diff --git a/source/testing/response/025.php b/source/testing/response/025.php index b255f4155..ea65727d0 100644 --- a/source/testing/response/025.php +++ b/source/testing/response/025.php @@ -1,6 +1,7 @@ dontSee('Hello World'); -// Checks that "Hello World" does NOT exist within any h1 tag -$results->dontSee('Hello World', 'h1'); +// Verify that "Hello World" does NOT exist on the page +$results->assertDontSee('Hello World'); + +// Verify that "Hello World" does NOT exist within any h1 tag +$results->assertDontSee('Hello World', 'h1'); diff --git a/source/testing/response/026.php b/source/testing/response/026.php index 4afd20161..5a6b38df3 100644 --- a/source/testing/response/026.php +++ b/source/testing/response/026.php @@ -1,6 +1,7 @@ seeElement('.notice'); -// Check that an element with id 'title' exists -$results->seeElement('#title'); +// Verify that an element with class 'notice' exists +$results->assertSeeElement('.notice'); + +// Verify that an element with id 'title' exists +$results->assertSeeElement('#title'); diff --git a/source/testing/response/027.php b/source/testing/response/027.php index 978048913..e5df1d09f 100644 --- a/source/testing/response/027.php +++ b/source/testing/response/027.php @@ -1,4 +1,4 @@ dontSeeElement('#title'); +$results->assertDontSeeElement('#title'); diff --git a/source/testing/response/028.php b/source/testing/response/028.php index bc74e3ad9..91b42c99a 100644 --- a/source/testing/response/028.php +++ b/source/testing/response/028.php @@ -1,6 +1,7 @@ seeLink('Upgrade Account'); -// Check that a link exists with 'Upgrade Account' as the text, AND a class of 'upsell' -$results->seeLink('Upgrade Account', '.upsell'); +// Verify that a link exists with 'Upgrade Account' as the text:: +$results->assertSeeLink('Upgrade Account'); + +// Verify that a link exists with 'Upgrade Account' as the text, AND a class of 'upsell' +$results->assertSeeLink('Upgrade Account', '.upsell'); diff --git a/source/testing/response/029.php b/source/testing/response/029.php index 1ae7910e9..dfa511aa0 100644 --- a/source/testing/response/029.php +++ b/source/testing/response/029.php @@ -1,6 +1,7 @@ assertSeeInField('user', 'John Snow'); -// Check a multi-dimensional input + +// Verify a multi-dimensional input $results->assertSeeInField('user[name]', 'John Snow'); diff --git a/source/tutorial/create_news_items.rst b/source/tutorial/create_news_items.rst index 37dc6d778..cfa18ea32 100755 --- a/source/tutorial/create_news_items.rst +++ b/source/tutorial/create_news_items.rst @@ -3,7 +3,7 @@ .. contents:: :local: - :depth: 2 + :depth: 3 你现在已经知道如何使用 CodeIgniter 从数据库中读取数据,但是你还没有将任何信息写入数据库。在本节中,你将扩展之前创建的新闻控制器和模型,以包括此功能。 @@ -23,32 +23,30 @@ 因为 :ref:`auto-routing-legacy` 允许任何 HTTP 方法访问控制器。 使用你不期望的方法访问控制器可能会绕过过滤器。 -创建表单 -************* +添加路由规则 +******************** + +在你开始向 CodeIgniter 应用程序中添加新闻项目之前,你需要在 **app/Config/Routes.php** 文件中添加一个额外的规则。确保你的文件包含以下内容: -视图 -==== +.. literalinclude:: create_news_items/004.php -为了将数据输入数据库,你需要创建一个表单,在表单中你可以输入要存储的信息。这意味着你需要一个带有两个字段的表单,一个用于标题,一个用于文本。我们会在模型中从标题中派生 slug。在 **app/Views/news/create.php** 中创建一个新的视图:: +``'news/new'`` 的路由指令放置在 ``'news/(:segment)'`` 的指令之前,以确保显示创建新闻项目的表单。 -

+``$routes->post()`` 行定义了一个 POST 请求的路由器。它仅匹配 URI 路径 **/news** 的 POST 请求,并映射到 ``News`` 类的 ``create()`` 方法。 - getFlashdata('error') ?> - +你可以在 :ref:`defined-route-routing` 中了解更多关于不同路由类型的信息。 -
- +创建表单 +************* - - -
+创建 news/create 视图文件 +============================ - - -
+为了将数据输入数据库,你需要创建一个表单,在表单中你可以输入要存储的信息。这意味着你需要一个带有两个字段的表单,一个用于标题,一个用于文本。我们会在模型中从标题中派生 slug。 - -
+在 **app/Views/news/create.php** 中创建一个新的视图: + +.. literalinclude:: create_news_items/006.php 这里可能只有四件事看起来不太熟悉。 @@ -61,69 +59,85 @@ :php:func:`set_value()` 函数由 :doc:`../helpers/form_helper` 提供,用于在发生错误时显示旧输入数据。 -控制器 -========== +News 控制器 +=============== + +返回到你的 ``News`` 控制器。 + +添加 News::new() 方法以显示表单 +----------------------------------- -返回你的 **News** 控制器。你将在此做两件事,检查表单是否已提交以及提交的数据是否通过了验证规则。 -你将使用 :ref:`Controller 中的 validation 方法 ` 来完成此操作。 +首先,创建一个方法来显示你创建的 HTML 表单。 .. literalinclude:: create_news_items/002.php +我们使用 :php:func:`helper()` 函数加载 :doc:`Form 辅助函数 <../helpers/form_helper>`。大多数辅助函数在使用之前都需要加载辅助函数。 + +然后返回创建的表单视图。 + +添加 News::create() 以创建新闻项目 +---------------------------------------- + +接下来,创建一个方法来根据提交的数据创建新闻项目。 + +在这里,你将完成三件事: + +1. 检查提交的数据是否通过了验证规则。 +2. 将新闻项目保存到数据库中。 +3. 返回一个成功页面。 + +.. literalinclude:: create_news_items/005.php + 上面的代码添加了很多功能。 -首先,我们使用 :php:func:`helper()` 函数加载 :doc:`表单辅助函数 <../helpers/form_helper>`。 -大多数辅助函数需要在使用前加载辅助函数。 +验证数据 +^^^^^^^^^^^^^^^^^ -接下来,我们使用 :doc:`IncomingRequest <../incoming/incomingrequest>` 对象 ``$this->request`` 检查我们是否处理 **POST** 请求。 -它由框架在控制器中设置。 -:ref:`IncomingRequest::is() ` 方法检查请求的类型。 -由于 **create()** 端点的路由处理 **GET** 和 **POST** 请求,所以如果请求不是 POST,我们可以安全地假设它是 GET 类型, -表单被加载并返回以显示。 +你将使用控制器提供的辅助函数 :ref:`validate() ` 来验证提交的数据。 +在这种情况下,标题和正文字段是必填的,并且有特定的长度要求。 +CodeIgniter 提供了一个强大的验证库,如上所示。你可以阅读更多关于 :doc:`验证库 <../libraries/validation>` 的信息。 -然后,我们从 POST 数据中获取用户提交的必要项,并将它们设置在 ``$post`` 变量中。 -我们还使用 :doc:`IncomingRequest <../incoming/incomingrequest>` 对象 ``$this->request``。 +如果验证失败,我们调用刚刚创建的 ``new()`` 方法并返回 HTML 表单。 -之后,使用 Controller 提供的辅助函数 :ref:`validateData() ` 来验证 ``$post`` 数据。 -在这种情况下,标题和正文字段是必需的且长度在特定范围内。 -CodeIgniter 拥有强大的验证库,如上所示。你可以阅读更多关于 :doc:`验证库 <../libraries/validation>` 的信息。 +保存新闻项目 +^^^^^^^^^^^^^^^^^^ -如果验证失败,表单将被加载并返回以显示。 +如果验证通过了所有规则,我们通过 :ref:`$this->validator->getValidated() ` 获取验证后的数据,并将其设置在 ``$post`` 变量中。 -如果验证通过了所有规则, **NewsModel** 将被加载并调用。这负责将新闻项传递到模型中。:ref:`model-save` 方法会自动插入或更新记录,这取决于它是否找到与主键匹配的数组键。 +加载并调用 ``NewsModel``。这将负责将新闻项目传递给模型。根据是否找到与主键匹配的数组键,:ref:`model-save` 方法会自动处理插入或更新记录。 这包含一个新函数 :php:func:`url_title()`。这个函数由 :doc:`URL 辅助函数 <../helpers/url_helper>` 提供 - 它会剥离你传递给它的字符串,用破折号 (``-``) 替换所有空格,并确保所有内容都是小写。 这会给你一个不错的 slug,非常适合创建 URI。 +返回成功页面 +^^^^^^^^^^^^^^^^^^^ + 之后,视图文件被加载并返回以显示成功消息。在 **app/Views/news/success.php** 中创建一个视图,并编写成功消息。 这可以简单地写成::

新闻项目创建成功。

-更新模型 +更新 NewsModel ************** 唯一剩下的就是确保你的模型设置为允许数据被正确保存。 在使用的 ``save()`` 方法将确定信息应插入还是如果行已经存在则应更新,这取决于主键的存在。 -在这种情况下,没有传递 ``id`` 字段给它,所以它会在它的表格 **news** 中插入新行。 +在这种情况下,没有传递 ``id`` 字段给它,所以它会在它的表格 ``news`` 中插入新行。 但是,默认情况下,模型中的 insert 和 update 方法实际上不会保存任何数据,因为它不知道哪些字段是安全更新的。 -编辑 **NewsModel** 以在 ``$allowedFields`` 属性中为其提供可更新字段的列表。 +编辑 ``NewsModel`` 以在 ``$allowedFields`` 属性中为其提供可更新字段的列表。 .. literalinclude:: create_news_items/003.php 这个新属性现在包含我们允许保存到数据库的字段。请注意,我们排除了 ``id`` 字段?这是因为你几乎永远不需要这样做,因为它是一个数据库中的自动递增字段。 这有助于防止批量分配漏洞。如果你的模型处理了时间戳,你也会排除它们。 -路由 -******* - -在你可以开始将新闻添加到 CodeIgniter 应用程序之前,你必须在 **app/Config/Routes.php** 文件中添加一个额外的规则。确保你的文件包含以下内容。这可以确保 CodeIgniter 将 ``create()`` 视为一个方法,而不是新闻项的 slug。你可以在 :doc:`../incoming/routing` 中阅读有关不同路由类型的更多信息。 - -.. literalinclude:: create_news_items/004.php +创建新闻项目 +****************** -现在指向你安装了 CodeIgniter 的本地开发环境的浏览器,并在 URL 中添加 ``/news/create``。 +现在指向你安装了 CodeIgniter 的本地开发环境的浏览器,并在 URL 中添加 **/news/new**。 添加一些新闻并查看你创建的不同页面。 .. image:: ../images/tutorial3.png @@ -141,9 +155,28 @@ CodeIgniter 拥有强大的验证库,如上所示。你可以阅读更多关于 你刚刚完成了你的第一个 CodeIgniter4 应用程序! -下面的图像显示了你的项目的 **app** 文件夹, -你创建的所有文件显示为红色。 -两个已修改的配置文件(**Config/Routes.php** & **Config/Filters.php**)未显示。 - -.. image:: ../images/tutorial9.png - :align: left +下面的图表显示了你的项目的 **app** 文件夹,其中包含你创建或修改的所有文件。 + +.. code-block:: none + + app/ + ├── Config + │ ├── Filters.php (Modified) + │ └── Routes.php (Modified) + ├── Controllers + │ ├── News.php + │ └── Pages.php + ├── Models + │ └── NewsModel.php + └── Views + ├── news + │ ├── create.php + │ ├── index.php + │ ├── success.php + │ └── view.php + ├── pages + │ ├── about.php + │ └── home.php + └── templates + ├── footer.php + └── header.php diff --git a/source/tutorial/create_news_items/002.php b/source/tutorial/create_news_items/002.php index be1ad6a54..0b196dfa7 100644 --- a/source/tutorial/create_news_items/002.php +++ b/source/tutorial/create_news_items/002.php @@ -3,46 +3,18 @@ namespace App\Controllers; use App\Models\NewsModel; +use CodeIgniter\Exceptions\PageNotFoundException; class News extends BaseController { // ... - public function create() + public function new() { helper('form'); - // Checks whether the form is submitted. - if (! $this->request->is('post')) { - // The form is not submitted, so returns the form. - return view('templates/header', ['title' => 'Create a news item']) - . view('news/create') - . view('templates/footer'); - } - - $post = $this->request->getPost(['title', 'body']); - - // Checks whether the submitted data passed the validation rules. - if (! $this->validateData($post, [ - 'title' => 'required|max_length[255]|min_length[3]', - 'body' => 'required|max_length[5000]|min_length[10]', - ])) { - // The validation fails, so returns the form. - return view('templates/header', ['title' => 'Create a news item']) - . view('news/create') - . view('templates/footer'); - } - - $model = model(NewsModel::class); - - $model->save([ - 'title' => $post['title'], - 'slug' => url_title($post['title'], '-', true), - 'body' => $post['body'], - ]); - return view('templates/header', ['title' => 'Create a news item']) - . view('news/success') + . view('news/create') . view('templates/footer'); } } diff --git a/source/tutorial/create_news_items/004.php b/source/tutorial/create_news_items/004.php index 3de06f181..6b04f3c66 100644 --- a/source/tutorial/create_news_items/004.php +++ b/source/tutorial/create_news_items/004.php @@ -5,10 +5,10 @@ use App\Controllers\News; use App\Controllers\Pages; -$routes->match(['get', 'post'], 'news/create', [News::class, 'create']); -$routes->get('news/(:segment)', [News::class, 'view']); $routes->get('news', [News::class, 'index']); +$routes->get('news/new', [News::class, 'new']); // Add this line +$routes->post('news', [News::class, 'create']); // Add this line +$routes->get('news/(:segment)', [News::class, 'show']); + $routes->get('pages', [Pages::class, 'index']); $routes->get('(:segment)', [Pages::class, 'view']); - -// ... diff --git a/source/tutorial/create_news_items/005.php b/source/tutorial/create_news_items/005.php new file mode 100644 index 000000000..6d5657891 --- /dev/null +++ b/source/tutorial/create_news_items/005.php @@ -0,0 +1,40 @@ +validate([ + 'title' => 'required|max_length[255]|min_length[3]', + 'body' => 'required|max_length[5000]|min_length[10]', + ])) { + // The validation fails, so returns the form. + return $this->new(); + } + + // Gets the validated data. + $post = $this->validator->getValidated(); + + $model = model(NewsModel::class); + + $model->save([ + 'title' => $post['title'], + 'slug' => url_title($post['title'], '-', true), + 'body' => $post['body'], + ]); + + return view('templates/header', ['title' => 'Create a news item']) + . view('news/success') + . view('templates/footer'); + } +} diff --git a/source/tutorial/create_news_items/006.php b/source/tutorial/create_news_items/006.php new file mode 100644 index 000000000..b19bbfe9b --- /dev/null +++ b/source/tutorial/create_news_items/006.php @@ -0,0 +1,18 @@ +

+ +getFlashdata('error') ?> + + +
+ + + + +
+ + + +
+ + +
diff --git a/source/tutorial/index.rst b/source/tutorial/index.rst index a4c53fa8a..ec249de7b 100755 --- a/source/tutorial/index.rst +++ b/source/tutorial/index.rst @@ -50,9 +50,11 @@ ====================== 你可以从网站手动下载版本,但是对于本教程,我们将使用推荐的方法,通过 Composer 安装 AppStarter 包。 -在命令行输入以下命令:: +在命令行输入以下命令: - > composer create-project codeigniter4/appstarter ci-news +.. code-block:: console + + composer create-project codeigniter4/appstarter ci-news 这将创建一个新文件夹 **ci-news**,其中包含你的应用程序代码,CodeIgniter 安装在 vendor 文件夹中。 @@ -62,7 +64,7 @@ ======================== 默认情况下,CodeIgniter 以生产模式启动。这是一个安全特性,以防止在站点上线后设置被搞乱。 -所以首先让我们解决这个问题。复制或重命名 ``env`` 文件为 ``.env``。打开它。 +所以首先让我们解决这个问题。复制或重命名 **env** 文件为 **.env**。打开它。 此文件包含服务器特定的设置。这意味着你永远不需要将任何敏感信息提交到版本控制系统。 它已经包含一些你想要输入的常见设置,不过都是注释掉的。因此,取消注释带有 ``CI_ENVIRONMENT`` 的那行,并将 ``production`` 更改为 ``development``:: @@ -72,9 +74,11 @@ 运行开发服务器 ========================== -搞定这些后,是时候在浏览器中查看你的应用程序了。你可以通过任何你选择的服务器提供服务,比如 Apache、Nginx 等,但是 CodeIgniter 提供了一个简单的命令,利用 PHP 的内置服务器快速在你的开发机器上启动并运行。在项目的根目录下在命令行输入以下命令:: +搞定这些后,是时候在浏览器中查看你的应用程序了。你可以通过任何你选择的服务器提供服务,比如 Apache、Nginx 等,但是 CodeIgniter 提供了一个简单的命令,利用 PHP 的内置服务器快速在你的开发机器上启动并运行。在项目的根目录下在命令行输入以下命令: + +.. code-block:: console - > php spark serve + php spark serve 欢迎页面 ****************** diff --git a/source/tutorial/news_section.rst b/source/tutorial/news_section.rst index cc07d8c9b..4080c829b 100755 --- a/source/tutorial/news_section.rst +++ b/source/tutorial/news_section.rst @@ -40,7 +40,7 @@ CodeIgniter 安装假定你已经按 :ref:`要求 db`` 对象使数据库类可用。 +添加 NewsModel::getNews() 方法 +=============================== + 现在数据库和模型已经设置好了,你需要一个从数据库中获取所有帖子的方法。为此,CodeIgniter 包含的数据库抽象层 :doc:`查询构建器 <../database/query_builder>` 在 ``CodeIgniter\Model`` 中使用。这使你可以编写一次'查询',并在 :doc:`所有支持的数据库系统 <../intro/requirements>` 上使用。Model 类也允许你轻松使用 Query Builder 并提供一些额外的工具,以简化使用数据。向模型添加以下代码。 .. literalinclude:: news_section/002.php @@ -66,12 +72,26 @@ CodeIgniter 安装假定你已经按 :ref:`要求 ` 会为你完成这一步。 -这里使用的两个方法 ``findAll()`` 和 ``first()`` 由 ``CodeIgniter\Model`` 类提供。它们已经知道基于我们早先在 **NewsModel** 类中设置的 ``$table`` 属性要使用的表。它们是使用 Query Builder 在当前表上运行命令的辅助方法,并以你选择的格式返回结果数组。在本示例中, ``findAll()`` 返回数组的数组。 +这里使用的两个方法 ``findAll()`` 和 ``first()`` 由 ``CodeIgniter\Model`` 类提供。它们已经知道基于我们早先在 ``NewsModel`` 类中设置的 ``$table`` 属性要使用的表。它们是使用 Query Builder 在当前表上运行命令的辅助方法,并以你选择的格式返回结果数组。在本示例中, ``findAll()`` 返回数组的数组。 显示新闻 **************** -现在查询已经编写好了,应该将模型与要显示新闻项的视图绑定。这可以在我们早先创建的 ``Pages`` 控制器中完成,但是为了清晰起见,定义了一个新的 ``News`` 控制器。在 **app/Controllers/News.php** 中创建新控制器。 +现在查询已经编写好了,应该将模型与要显示新闻项的视图绑定。这可以在我们早先创建的 ``Pages`` 控制器中完成,但是为了清晰起见,定义了一个新的 ``News`` 控制器。 + +添加路由规则 +==================== + +修改你的 **app/Config/Routes.php** 文件,使其如下所示: + +.. literalinclude:: news_section/008.php + +这样可以确保请求到达 ``News`` 控制器,而不是直接到达 ``Pages`` 控制器。第二个 ``$routes->get()`` 行将带有 slug 的 URI 路由到 ``News`` 控制器中的 ``show()`` 方法。 + +创建 News 控制器 +====================== + +在 **app/Controllers/News.php** 中创建新的控制器。 .. literalinclude:: news_section/003.php @@ -79,15 +99,23 @@ CodeIgniter 安装假定你已经按 :ref:`要求 ` 或第三方解析器。 -新闻概述页面现已完成,但是仍缺少页面来显示单个新闻条目。早期创建的模型以这样的方式制作,可以轻松地用于此功能。你只需要添加一些控制器代码并创建一个新视图。返回 ``News`` 控制器并使用以下内容更新 ``view()`` 方法: +完成 News::show() 方法 +============================ + +新闻概述页面现已完成,但是仍缺少页面来显示单个新闻条目。早期创建的模型以这样的方式制作,可以轻松地用于此功能。你只需要添加一些控制器代码并创建一个新视图。返回 ``News`` 控制器并使用以下内容更新 ``show()`` 方法: .. literalinclude:: news_section/006.php 不要忘记添加 ``use CodeIgniter\Exceptions\PageNotFoundException;`` 来导入 ``PageNotFoundException`` 类。 -与不带参数调用 ``getNews()`` 方法不同,传递了 ``$slug`` 变量,因此它将返回特定的新闻条目。唯一剩下的就是在 **app/Views/news/view.php** 中创建相应的视图。在此文件中放入以下代码。 +与不带参数调用 ``getNews()`` 方法不同,传递了 ``$slug`` 变量,因此它将返回特定的新闻条目。 -.. literalinclude:: news_section/007.php - -路由 -******* +创建 news/view 视图文件 +========================== -修改你的路由文件(**app/Config/Routes.php**),使其如下所示。这可以确保请求到达 ``News`` 控制器,而不是直接到达 ``Pages`` 控制器。第一行的路由将带 slug 的 URI 路由到 ``News`` 控制器中的 ``view()`` 方法。 +唯一剩下的就是在 **app/Views/news/view.php** 中创建相应的视图。在此文件中放入以下代码。 -.. literalinclude:: news_section/008.php +.. literalinclude:: news_section/007.php -将浏览器指向你的“新闻”页面,即 ``localhost:8080/news``,你应该看到新闻条目列表,每个条目都有一个链接只显示一篇文章。 +将浏览器指向你的 "news" 页面,即 **localhost:8080/news**,你应该会看到新闻项目的列表,每个项目都有一个链接,可以显示单独的文章。 .. image:: ../images/tutorial2.png :align: center diff --git a/source/tutorial/news_section/003.php b/source/tutorial/news_section/003.php index 13c3b7263..22b24577b 100644 --- a/source/tutorial/news_section/003.php +++ b/source/tutorial/news_section/003.php @@ -13,7 +13,7 @@ public function index() $data['news'] = $model->getNews(); } - public function view($slug = null) + public function show($slug = null) { $model = model(NewsModel::class); diff --git a/source/tutorial/news_section/006.php b/source/tutorial/news_section/006.php index 657211792..95e48aa77 100644 --- a/source/tutorial/news_section/006.php +++ b/source/tutorial/news_section/006.php @@ -9,7 +9,7 @@ class News extends BaseController { // ... - public function view($slug = null) + public function show($slug = null) { $model = model(NewsModel::class); diff --git a/source/tutorial/news_section/008.php b/source/tutorial/news_section/008.php index 077efd422..df1159845 100644 --- a/source/tutorial/news_section/008.php +++ b/source/tutorial/news_section/008.php @@ -2,12 +2,11 @@ // ... -use App\Controllers\News; +use App\Controllers\News; // Add this line use App\Controllers\Pages; -$routes->get('news/(:segment)', [News::class, 'view']); -$routes->get('news', [News::class, 'index']); +$routes->get('news', [News::class, 'index']); // Add this line +$routes->get('news/(:segment)', [News::class, 'show']); // Add this line + $routes->get('pages', [Pages::class, 'index']); $routes->get('(:segment)', [Pages::class, 'view']); - -// ... diff --git a/source/tutorial/static_pages.rst b/source/tutorial/static_pages.rst index 55ab782e0..e3868826c 100755 --- a/source/tutorial/static_pages.rst +++ b/source/tutorial/static_pages.rst @@ -7,11 +7,42 @@ .. note:: 本教程假设你已经下载了 CodeIgniter 并在开发环境中 :doc:`安装了框架 <../installation/index>`。 -你要做的第一件事是设置一个 **控制器** 来处理静态页面。控制器只是一个帮助委派工作的类。它是你的 Web 应用程序的粘合剂。 +首先,你需要设置路由规则来处理静态页面。 + +设置路由规则 +********************* + +路由将 URI 关联到控制器的方法。控制器只是一个帮助委派工作的类。我们稍后将创建一个控制器。 + +让我们设置路由规则。打开位于 **app/Config/Routes.php** 的路由文件。 + +开始时,唯一的路由指令应该是: + +.. literalinclude:: static_pages/003.php + +该指令表示任何没有指定内容的传入请求应由 ``Home`` 控制器内的 ``index()`` 方法处理。 + +在 ``'/'`` 的路由指令之后,添加以下行。 + +.. literalinclude:: static_pages/004.php + :lines: 2- + +CodeIgniter 从上到下读取其路由规则,并将请求路由到第一个匹配的规则。每个规则都是一个正则表达式(左侧),映射到一个控制器和方法名称(右侧)。当请求到达时,CodeIgniter 查找第一个匹配项,并调用适当的控制器和方法,可能带有参数。 + +有关路由的更多信息,请参阅 :doc:`../incoming/routing`。 + +在这里,``$routes`` 对象中的第二个规则匹配到一个 GET 请求,URI 路径为 **/pages**,并映射到 ``Pages`` 类的 ``index()`` 方法。 + +``$routes`` 对象中的第三个规则匹配到一个 GET 请求,使用占位符 ``(:segment)``,并将参数传递给 ``Pages`` 类的 ``view()`` 方法。 让我们制作第一个控制器 ******************************* +接下来,你需要设置一个 **控制器** 来处理静态页面。控制器只是一个帮助委派工作的类,它是你的 Web 应用程序的粘合剂。 + +创建 Pages 控制器 +======================= + 在 **app/Controllers/Pages.php** 中创建一个带以下代码的文件。 .. important:: 你应该始终注意文件名的大小写。许多开发人员在 Windows 或 macOS 上的大小写不敏感的文件系统上开发。 @@ -29,6 +60,9 @@ **控制器将成为你的 Web 应用程序的每个请求的中心**。与任何 PHP 类一样,你可以在控制器中通过 ``$this`` 来引用它。 +创建视图 +============ + 既然你已经创建了第一个方法,是时候制作一些基本的页面模板了。我们将创建两个“视图”(页面模板)作为我们的页面页脚和页眉。 在 **app/Views/templates/header.php** 中创建页眉,并添加以下代码:: @@ -54,10 +88,18 @@ 向控制器添加逻辑 ****************************** -早些时候,你设置了一个带有 ``view()`` 方法的控制器。该方法接受一个参数,即要加载的页面的名称。静态页面正文将位于 **app/Views/pages/** 目录中。 +创建 home.php 和 about.php +============================= + +早些时候,你设置了一个带有 ``view()`` 方法的控制器。该方法接受一个参数,即要加载的页面的名称。 + +静态页面正文将位于 **app/Views/pages** 目录中。 在该目录中,创建两个名为 **home.php** 和 **about.php** 的文件。在这些文件中输入一些文本(任何你想要的),然后保存它们。如果你想特别原创,可以试试“Hello World!”。 +完成 Pages::view() 方法 +============================= + 为了加载这些页面,你将不得不检查请求的页面是否确实存在。这将是在上面创建的 ``Pages`` 控制器中的 ``view()`` 方法的主体: .. literalinclude:: static_pages/002.php @@ -78,44 +120,20 @@ .. note:: 传递给 :php:func:`view()` 函数的任何文件和目录名称必须匹配实际目录和文件本身的情况,否则系统将在区分大小写的平台上抛出错误。你可以在 :doc:`../outgoing/views` 中了解更多信息。 -路由 -******* - -我们已经制作了控制器。下一件事是设置路由规则。路由将 URI 与控制器的方法相关联。 - -让我们这样做。打开位于 **app/Config/Routes.php** 的路由文件,并查看配置文件中的“路由定义”部分。 - -唯一的未注释的行应该是: - -.. literalinclude:: static_pages/003.php - -此指令表示,任何不包含任何内容的传入请求都应由 ``Home`` 控制器中的 ``index()`` 方法处理。 - -在 '/' 的路由指令 **之后** 添加以下行。 - -.. literalinclude:: static_pages/004.php - :lines: 2- - -CodeIgniter 从上到下读取其路由规则,并将请求路由到第一个匹配的规则。每个规则都是正则表达式(左侧)映射到控制器和方法名(右侧)。当请求进来时,CodeIgniter 查找第一个匹配项,并调用适当的控制器和方法,可能带有参数。 - -有关路由的更多信息,请参阅 :doc:`../incoming/routing`。 - -这里, ``$routes`` 对象中的第二条规则匹配到 ``/pages`` URI 路径的 GET 请求,并将其映射到 ``Pages`` 类的 ``index()`` 方法。 - -``$routes`` 对象中的第三条规则匹配使用占位符 ``(:segment)`` 的 URI 段的 GET 请求,并将参数传递给 ``Pages`` 类的 ``view()`` 方法。 - 运行应用程序 *************** -准备测试了吗?你不能使用 PHP 的内置服务器运行应用程序,因为它不会正确处理 ``public`` 中提供的 ``.htaccess`` 规则,这些规则消除了在 URL 中指定 "**index.php/**" 的需要。不过 CodeIgniter 有自己的命令可以使用。 +准备测试了吗?你不能使用 PHP 的内置服务器运行应用程序,因为它不会正确处理 **public** 中提供的 **.htaccess** 规则,这些规则消除了在 URL 中指定 "**index.php/**" 的需要。不过 CodeIgniter 有自己的命令可以使用。 + +在项目的根目录下,在命令行中: -在项目的根目录下,在命令行中:: +.. code-block:: console - > php spark serve + php spark serve -将启动一个网页服务器,可以在 8080 端口上访问。如果你将浏览器的 location 字段设置为 ``localhost:8080``,则应该会看到 CodeIgniter 欢迎页面。 +将启动一个网页服务器,可以在 8080 端口上访问。如果你将浏览器的 location 字段设置为 **localhost:8080**,则应该会看到 CodeIgniter 欢迎页面。 -现在访问 ``localhost:8080/home``。是否正确路由到 ``Pages`` 控制器中的 ``view()`` 方法?太棒了! +现在访问 **localhost:8080/home**。是否正确路由到 ``Pages`` 控制器中的 ``view()`` 方法?太棒了! 你应该看到类似以下内容: diff --git a/source/tutorial/static_pages/003.php b/source/tutorial/static_pages/003.php index 956c097d3..fc4914a69 100644 --- a/source/tutorial/static_pages/003.php +++ b/source/tutorial/static_pages/003.php @@ -1,9 +1,8 @@ get('/', 'Home::index'); - -// ...