oop 类中定义 的函数

This article on advanced OOP for WordPress was originally published by Torque Magazine, and is reproduced here with permission.

这篇有关适用于WordPress的高级OOP的文章最初是由Torque Magazine发行的,经许可在此处转载。

I’ve written a lot about object-oriented PHP and the WordPress REST API for Torque over the past few years. I’ve also touched on using Composer for dependency management and to provide an autoloader, as well as covered unit testing. The basic message of everything I’ve written is that by applying the established best practices of software development to how we develop for WordPress, we can create better plugins.

在过去的几年中,我写了很多有关面向对象PHP和适用于Torque的WordPress REST API的文章。 我还谈到了使用Composer进行依赖性管理,提供自动加载器以及涵盖的单元测试。 我写的所有文章的基本信息是,通过将既定的软件开发最佳实践应用于我们为WordPress开发的方式,我们可以创建更好的插件。

This is the first of a series of articles that will pull together these concepts in a practical, functional example. I’ll be walking through creating a WordPress plugin to modify the capabilities of WordPress REST API endpoints so they can be better optimized for search. The plugin is available on GitHub. You may want to browse the commit log to see how I put it together.

这是一系列文章的第一篇,它将在一个实用的功能示例中将这些概念融合在一起。 我将逐步创建一个WordPress插件,以修改WordPress REST API端点的功能,以便可以针对搜索进行更好的优化。 该插件在GitHub上可用 。 您可能要浏览提交日志,以了解如何将其组合在一起。

In this series, I’ll cover structuring plugins and classes using modern object-oriented PHP and not only how to make it testable, but also how to write automated tests for it. I will cover the difference between unit tests, integration tests, and acceptance tests and show you how to write and automate running each type. This article begins the series by showing how to use filters to modify the WordPress REST API using an object-oriented approach.

在本系列中,我将介绍使用现代的面向对象PHP构建插件和类的结构,不仅介绍如何使其可测试,还包括如何为其编写自动测试。 我将介绍单元测试,集成测试和验收测试之间的区别,并向您展示如何编写和自动运行每种类型。 本文通过展示如何使用过滤器通过面向对象的方法来修改WordPress REST API,来开始本系列文章。

使用REST API改善WordPress搜索 (Improving WordPress Search Using the REST API)

Plugins like SearchWP or Relevansi, or integrations with ElasticSearch — a technology that uses a totally different stack than WordPress — using Jetpack or ElasticPress, are often used to improve WordPress search. These types of plugins provide better search results and often pair well with faceted-search interface, which is great for eCommerce apps.

诸如JetWP或Relevansi之类的插件,或与JetSearch或ElasticPress集成的ElasticSearch(一种使用与WordPress完全不同的堆栈的技术)的集成,通常用于改善WordPress搜索。 这些类型的插件提供了更好的搜索结果,并且通常与多面搜索界面搭配得很好,这对于电子商务应用程序非常有用。

Search via the WordPress REST API inherits all of these same problems and the same solution. In this post, I’m going to start by looking at how search works by default, and what the limitations are. Then we’ll look at how to modify the search using two different methods and integrate with SearchWP.

通过WordPress REST API进行搜索将继承所有这些相同的问题和相同的解决方案。 在本文中,我将首先探讨默认情况下搜索的工作方式以及局限性。 然后,我们将研究如何使用两种不同的方法修改搜索并与SearchWP集成。

WordPress’s built-in search capabilities often need to be improved using outside services. While this article is about an object-oriented approach to modifying how WordPress REST API routes for posts work, the practical example will be improving search.

经常需要使用外部服务来改进WordPress的内置搜索功能。 尽管本文是有关修改面向对象的WordPress REST API路由方式的面向对象方法,但实际示例将改善搜索。

When WordPress is used as the back end for a decoupled front end such as a native mobile app or web app, probably built using Vue or React or Angular, having quality search via the REST API is important. The code this article covers will help you if your app’s users need to find the right product variation or search content by a complex algorithm based on multiple taxonomies, and you’re writing custom code, not just installing a plugin.

当WordPress用作解耦的前端(例如本地移动应用程序或Web应用程序,可能使用Vue或React或Angular构建)的后端时,通过REST API进行高质量搜索非常重要。 如果您的应用程序用户需要基于多种分类法的复杂算法来查找正确的产品变体或搜索内容,那么本文介绍的代码将为您提供帮助,并且您正在编写自定义代码,而不仅仅是安装插件。

使用WordPress REST API搜索帖子 (Searching Posts with the WordPress REST API)

If you wanted to search for all posts that were of the post type “product” on a site, using the search terms “Taco Shirts” you would make a request to the /wp/v2/product?s=Taco+Shirt endpoint. If you wanted to improve the quality of the results, the solutions I listed above would help.

如果要搜索站点上所有职位类型为“产品”的职位,则使用搜索词“ Taco Shirts”,您将请求/wp/v2/product?s=Taco+Shirt端点。 如果您想提高结果的质量,我上面列出的解决方案将有所帮助。

As we discussed above, WP_Query, what the post endpoints of the WordPress REST API use, is not a great tool for search. More specifically, WP_Query, probably due to its dependence on MySQL, is inferior to specialized search tools that tend to be built using NoSQL databases.

正如我们上面所讨论的, WP_Query WordPress REST API的post终结点使用的工具,不是一个很好的搜索工具。 更具体地说, WP_Query可能是由于其对MySQL的依赖,不如倾向于使用NoSQL数据库构建的专用搜索工具。

First, let’s look at how we can bypass WP_Query’s interactions with WordPress’s database if a REST API request is being made.

首先,让我们看一下如果发出REST API请求,如何绕过WP_Query与WordPress数据库的交互。

This is the strategy many search plugins take to substitute the results of their own search systems, for what WP_Query would have generated by default. The search system may use the same database. It may also connect to some other database, possibly via an API request, for example to an ElasticSearch or Apache Solr server.

这是许多搜索插件采用的策略来替代其自己的搜索系统的结果,以替代WP_Query默认情况下会生成的结果。 搜索系统可以使用相同的数据库。 它还可能通过API请求连接到其他数据库,例如,连接到ElasticSearch或Apache Solr服务器。

If you look in WordPress core, you’ll find the filter “posts_pre_query” runs right before WP_Query queries the database, but after the SQL query has been prepared. This filter returns null by default. If that value is null, WordPress continues with its default behavior: querying the WordPress database and returning the results as a simple array of WP_Post objects.

如果您查看WordPress核心 ,则会发现过滤器“ posts_pre_query”在WP_Query查询数据库之前,但在准备SQL查询之后立即运行。 默认情况下,此过滤器返回null。 如果该值为null,则WordPress继续其默认行为:查询WordPress数据库并以WP_Post对象的简单数组形式返回结果。

On the other hand, if the return value of this filter is an array — hopefully containing WP_Post objects — then WordPress’s default behavior is not used.

另一方面,如果此过滤器的返回值是一个数组(希望包含WP_Post对象),则不使用WordPress的默认行为。

Let’s look at how we can use posts_pre_query to return a mock WP_Post. This strategy is very useful for testing, but a more complex version of the same pattern can be used to integrate a separate database with your WordPress site:

让我们看看如何使用posts_pre_query返回模拟WP_Post 。 此策略对于测试非常有用,但是可以使用同一模式的更复杂版本将单独的数据库与WordPress网站集成:

/**
* Replace all WP_Query results with mock posts
*/
add_filter('posts_pre_query',
function ($postsOrNull, \WP_Query $query) {
//Don't run if posts are already sent
if (is_null($postsOrNull)) {
//Create 4 mock posts with different titles
$mockPosts = [];
for ($i = 0; $i <= 3; $i++) {
$mockPosts[$i] = (new \WP_Post((new \stdClass())));  //Fake post for demonstration, could be any WP_Post
$mockPosts[$i]->post_title = "Mock Post $i"; //Fake title will be different for each post, useful for testing.
$mockPosts[$i]->filter = "raw"; //Bypass sanitzation in get_post, to prevent our mock data from being sanitized out.
}
//Return a mock array of mock posts
return $mockPosts;
}
//Always return something, even if its unchanged
return $postsOrNull;
},
//Default priority, 2 arguments
10, 2
);

In this example, we’re using mock data, but we could be using SearchWP’s query class, or anything else. One other thing to keep in mind about this code is it will run on any WP_Query, not just a WP_Query object created by the WordPress REST API. Let’s modify that so we don’t use the filter unless it is a WordPress REST API request by adding conditional logic:

在此示例中,我们使用的是模拟数据,但我们可能使用的是SearchWP的查询类或其他任何类。 关于此代码,需要记住的另一件事是,它将在任何WP_Query上运行,而不仅仅是在WordPress REST API创建的WP_Query对象上运行。 让我们对其进行修改,以便通过添加条件逻辑来使用过滤器,除非它是WordPress REST API请求:

<?php
/**
* Replace all WP_Query results with mock posts, for WordPress REST API requests
*/
add_filter('posts_pre_query',
function ($postsOrNull, \WP_Query $query) {
//Only run during WordPress REST API requests
if (defined('REST_REQUEST') && REST_REQUEST) {
//Don't run if posts are already sent
if (is_null($postsOrNull)) {
//Create 4 mock posts with different titles
$mockPosts = [];
for ($i = 0; $i <= 3; $i++) {
$mockPosts[$i] = (new \WP_Post((new \stdClass())));  //Fake post for demonstration, could be any WP_Post
$mockPosts[$i]->post_title = "Mock Post $i"; //Fake title will be different for each post, useful for testing.
$mockPosts[$i]->filter = "raw"; //Bypass sanitzation in get_post, to prevent our mock data from being sanitized out.
}
//Return a mock array of mock posts
return $mockPosts;
}
}
//Always return something, even if its unchanged
return $postsOrNull;
},
//Default priority, 2 arguments
10, 2
);

修改WordPress REST API端点参数 (Modifying WordPress REST API Endpoints Arguments)

We just looked at how to change how the search results are generated for WordPress REST API requests. That allows us to optimize our queries for better search, but it is likely to expose a need for a different schema for the endpoints.

我们只是研究了如何更改为WordPress REST API请求生成搜索结果的方式。 这使我们可以优化查询以进行更好的搜索,但是很可能需要为端点提供不同的架构。

For example, what if you wanted to allow the search on your products endpoint to optionally allow additional post type to be included in the search. I covered a different approach to the same problem last year.

例如,如果您希望允许在产品端点上进行搜索,以便有选择地允许其他帖子类型包含在搜索中,该怎么办。 去年我针对同一问题采用了不同的方法 。

横切关注点 (Cross-Cutting Concerns)

We are about to look at how to modify the allowed endpoint arguments as well as how they are used to create WP_Query arguments. That’s two separate concerns, and the single responsibility principle says we need one class for each concern. But both classes will have shared concerns.

我们将研究如何修改允许的端点参数以及如何使用它们创建WP_Query参数。 那是两个独立的关注点,单一责任原则说,我们需要为每个关注点分配一个类。 但是这两个班级都会有共同的担忧。

For example, if we want to allow querying by different post types, we need to know what are the public post types, and what their slugs and rest_base arguments are. This is all information we can get from the function get_post_types.

例如,如果要允许按不同的帖子类型进行查询,则需要知道什么是公共帖子类型,以及它们的子句和rest_base参数是什么。 这就是我们可以从函数get_post_types获得的所有信息。

The output of that function is not exactly what we need. So let’s design a class to format the data according to the needs I just listed and give us helper methods to access it.

该函数的输出并不完全符合我们的需求。 因此,让我们设计一个类来根据我刚刚列出的需求格式化数据,并为我们提供帮助方法来访问它。

Think of it as one common shape for all of the post type data we need in a useable container:

将其视为可用容器中所需的所有职位类型数据的一种通用形状:

<?php
/**
* Class PreparedPostTypes
*
* Prepares post types in the format we need for the UsesPreparedPostTypes trait
* @package ExamplePlugin
*/
class  PreparedPostTypes
{
/**
* Prepared post types
*
* @var array
*/
protected $postTypes;
/**
* PreparedPostTypes constructor.
* @param array $postTypes Array of post type objects `get_post_types([], 'objects')`
*/
public function __construct(array $postTypes)
{
$this->setPostTypes($postTypes);
}
/**
* Get an array of "rest_base" values for all public post types
*
* @return array
*/
public function getPostTypeRestBases(): array
{
return !empty($this->postTypes) ? array_keys($this->postTypes) : [];
}
/**
* Prepare the post types
*
* @param array $postTypes
*/
protected function setPostTypes(array $postTypes)
{
$this->postTypes = [];
/** @var \WP_Post_Type $postType */
foreach ($postTypes as $postType) {
if ($postType->show_in_rest) {
$this->postTypes[$postType->rest_base] = $postType->name;
}
}
}
/**
* Convert REST API base to post type slug
*
* @param string $restBase
* @return string|null
*/
public function restBaseToSlug(string $restBase)
{
if (in_array($restBase, $this->getPostTypeRestBases())) {
return $this->postTypes[$restBase];
}
return null;
}
}

Notice that we didn’t call get_post_types() in the class, instead, we used it as a dependency, injected through the constructor. As a result, this class can be tested without loading WordPress.

注意,我们没有在类中调用get_post_types() ,而是将其用作通过构造函数注入的依赖项。 因此,无需加载WordPress即可测试此类。

This is why I would describe this class as “unit testable”. It relies on no other APIs and we are not worried about side effects. We can test it as one single, isolated unit. Separating concerns and isolating functionality into small parts makes the code maintainable, once we have unit test coverage. I’ll look at how to test this kind of class in my next post.

这就是为什么我将此类描述为“可单元测试”的原因。 它不依赖其他API,我们也不担心副作用。 我们可以将其作为一个孤立的单元进行测试。 一旦我们进行了单元测试,就可以将关注点分离并将功能分成小部分,从而使代码可维护。 我将在下一篇文章中介绍如何测试此类课程。

Keep in mind that this class does rely on WP_Post_Type. My unit tests will not have that class defined, as only integration tests will have WordPress or any other external dependency available. That class is only used to represent data, not to perform any operations. We can, therefore, say its use creates no side effects. As a result, I am comfortable using a mock in place of the real WP_Post_Type in the unit tests.

请记住,此类确实依赖WP_Post_Type 。 我的单元测试不会定义该类,因为只有集成测试才会具有WordPress或任何其他外部依赖项。 该类仅用于表示数据,不执行任何操作。 因此,我们可以说它的使用不会产生副作用。 结果,我很WP_Post_Type在单元测试中使用模拟代替实际的WP_Post_Type

Speaking of dependency injection, the classes that require objects of this new class, we want to follow the same pattern. Instead of instantiating PreparedPostTypes inside of the classes that need them, we will pass in an instance. This means the classes consuming PreparedPostTypes and PreparedPostType remain isolated and can be tested separately.

说到依赖注入,即需要这个新类的对象的类,我们希望遵循相同的模式。 而不是在需要它们的类中实例化PreparedPostTypes ,我们将传入一个实例。 这意味着使用PreparedPostTypesPreparedPostType的类将保持隔离状态,并且可以分别进行测试。

It could also lead to code reuse as we have to make that dependency injection possible and have a property for that object. We could use cut and paste, or we could use a PHP Trait, which is a fancy more scalable way to copy methods and properties between classes.

这也可能导致代码重用,因为我们必须使依赖注入成为可能,并具有该对象的属性。 我们可以使用剪切和粘贴,也可以使用PHP特性,这是一种在类之间复制方法和属性的更理想的扩展方式。

Here is a Trait that establishes a pattern for how we inject the PreparedPostTypes object into other classes

这是一个特质,它为我们如何将PreparedPostTypes对象注入其他类建立了模式

<?php
/**
* Trait UsesPreparedPostTypes
* @package ExamplePlugin
*/
trait UsesPreparedPostTypes
{
/**
* Prepared post types
*
* @var PreparedPostTypes
*/
protected $preparedPostTypes;
/**
* UsesPreparedPostTypes constructor.
* @param PreparedPostTypes $preparedPostTypes
*/
public function __construct(PreparedPostTypes $preparedPostTypes)
{
$this->preparedPostTypes = $preparedPostTypes;
}
}

Our other concern is we need to know some things about a post type in multiple places. For example the post type’s slug. This is a different flavor of a cross-cutting concern than the previous one. The last problem we solved involved dynamic data. Now we just need a single place to change a string or two we use in multiple places.

我们的另一个关注点是我们需要了解多个地方的帖子类型的一些知识。 例如帖子类型的子弹。 与前一个问题相比,这是跨领域问题的另一种风味。 我们解决的最后一个问题涉及动态数据。 现在,我们只需要一个位置即可更改在多个位置使用的一两个字符串。

A class that has class constants solves this for us simply:

具有类常量的类可以为我们简单地解决此问题:

<?php
/**
* Class PostType
*
* Post type whose POST wp/v2/<post-type-rest_base> we are hijacking
*
*/
class PostType
{
/**
* Post type slug
*
* @TODO Change this to your post type's slug
*/
const SLUG = 'post';
/**
* Post type rest_base
*
* @TODO Change this to your post type's rest_base
*/
const RESTBASE = 'posts';
}

Now we can keep these strings consistent throughout our code. This may seem like an unnecessary step. But my example code works for the posts post type. If you want to change what post type is used this class needs to change and nothing else needs to change. This is following Tom McFarlin’s preferred definition of the single responsibility principle when he writes, “A class should have only one reason to change.”

现在,我们可以在整个代码中使这些字符串保持一致。 这似乎是不必要的步骤。 但是我的示例代码适用于posts帖子类型。 如果要更改使用的帖子类型,则需要更改此类,而无需更改其他任何内容。 这是遵循汤姆·麦克法兰(Tom McFarlin)对单一责任原则的首选定义:“ 班级应该只有一个改变的理由。 ”

修改REST API端点架构 (Modifying REST API Endpoint Schemas)

Now we need to modify the schema of a post type’s endpoints. By doing so, WordPress will communicate to REST API endpoints that the post_type argument is allowed, and when requests are parsed, the new endpoint argument is allowed.

现在,我们需要修改帖子类型端点的模式。 这样,WordPress将向REST API端点post_type允许post_type参数的信息,并且在解析请求时,将允许新的端点参数。

Here is our class to add the post_type attribute. Note that it uses the trait UsesPreparedPostTypes we just discussed:

这是我们添加post_type属性的类。 请注意,它使用了我们刚刚讨论的特征UsesPreparedPostTypes

<?php
/**
* Class ModifySchema
*
* Modifies the REST API route schema so it has an argument "post_type"
*
*
* @package ExamplePlugin
*/
class ModifySchema
{
use UsesPreparedPostTypes;
/**
* The name of the extra argument we are adding to post type routes
*/
const ARGNAME = 'post_type';
/**
* Add post_type to schema
*
* @uses ""rest_{$postType}_collection_params" action
*
* @param array $query_params JSON Schema-formatted collection parameters.
* @param \WP_Post_Type $post_type Post type object.
*
* @return array
*/
public function filterSchema($query_params, $post_type)
{
if ($this->shouldFilter($post_type)) {
$query_params[self::ARGNAME] = [
[
'default' => PostType::RESTBASE,
'description' => __('Post type(s) for search query'),
'type' => 'array',
//Limit to public post types and allow query by rest base
'items' =>
[
'enum' => $this->preparedPostTypes->getPostTypeRestBases(),
'type' => 'string',
],
]
];
}
return $query_params;
}
/**
* Check if this post type's schema should be filtered
*
* @param \WP_Post_Type $WP_Post_Type
* @return bool
*/
public function shouldFilter(\WP_Post_Type $WP_Post_Type): bool
{
return PostType::SLUG === $WP_Post_Type->name;
}
}

In the setting for this attribute, we tell WordPress that this attribute is an array attribute and we specify allowed values using the “enum” index of the array.

在此属性的设置中,我们告诉WordPress该属性是一个数组属性,并使用数组的“枚举”索引指定允许的值。

In “enum” we enumerate the allowed values. In this case, the PreparedPostTypes class provides that array of allowed values since this is a previously solved, cross-cutting concern.

在“枚举”中,我们枚举允许的值。 在这种情况下, PreparedPostTypes类提供了允许值的数组,因为这是以前解决的跨领域问题。

Note this class isn’t coupled to any post type or even this specific use case. We’ll cover how to make this work with our actual requirement. Decoupling this class from the WordPress plugins API makes it reusable and unit testable. We do need to test that interaction with WordPress, but we would cover that in an integration test. I will show you how to do that in a future post.

请注意,此类不与任何帖子类型甚至特定用例都耦合。 我们将介绍如何根据我们的实际要求进行这项工作。 将此类与WordPress插件API分离,使其可重用且可进行单元测试。 我们确实需要测试与WordPress的交互,但是我们将在集成测试中进行介绍。 我将在以后的文章中向您展示如何做到这一点。

We will return shortly to what hooks to use to make this class work for a specific post type.

我们将很快返回用于使此类适用于特定帖子类型的钩子。

修改REST API WP_Query参数 (Modifying REST API WP_Query Arguments)

The previous section showed how to make a new endpoint attribute post_type available. That doesn’t actually change the WP_Query arguments the WordPress REST API generates. We have everything we need except one last filter.

上一节显示了如何使新的端点属性post_type可用。 这实际上并没有改变WordPress REST API生成的WP_Query参数。 除了最后一个过滤器外,我们还有所有需要的东西。

Post type is one WP_Query argument that core specifically disallows changing via a REST API request. We have a dynamically named filter — rest_{$post_type}_query — that can override any WP_Query arguments.

帖子类型是一个WP_Query变量,核心明确不允许通过REST API请求进行更改 。 我们有一个动态命名的过滤器rest_{$post_type}_query ,它可以覆盖任何WP_Query参数。

Here is our class that injects our post_type args, which previously were not allowed:

这是我们的类,它注入了我们以前不允许的post_type args:

<?php
/**
* Class ModifyQuery
*
* Modify WP_Query Args
*
* @package ExamplePlugin
*/
class  ModifyQueryArgs
{
use UsesPreparedPostTypes;
/**
* Filter query args if needed
*
* @param array $args Key value array of query var to query value.
* @param \WP_REST_Request $request The request used.
*
* @return array
*/
public function filterQueryArgs($args, $request)
{
if ($this->shouldFilter($request)) {
add_filter('posts_pre_query', [FilterWPQuery::class, 'posts_pre_query'], 10, 2);
$args['post_type'] = $this->restBasesToPostTypeSlugs($request[ModifySchema::ARGNAME]);
}
return $args;
}
/**
* Check if we should filter request args
*
* @param \WP_REST_Request $request
* @return bool
*/
public function shouldFilter(\WP_REST_Request $request): bool
{
$attributes = $request->get_attributes();
if (isset($attributes['args'][ModifySchema::ARGNAME])) {
if ($request->get_param(ModifySchema::ARGNAME)) {
return true;
}
}
return false;
}
/**
* Convert an array of rest bases to post type slugs
*
* @param array $postTypes
* @return array
*/
public function restBasesToPostTypeSlugs(array $postTypes): array
{
$postTypeSlugs = [];
foreach ($postTypes as $postTypeRestBase) {
if ($this->preparedPostTypes->restBaseToSlug($postTypeRestBase)) {
$postTypeSlugs[] = $this->preparedPostTypes->restBaseToSlug($postTypeRestBase);
}
}
return $postTypeSlugs;
}
}

Most of this is just validating if we should make the change and then using the get_param method of WP_Rest_Request to get the value from the request. Most of it is automagical because we modify the schema to match first.

其中大多数只是验证我们是否应该进行更改,然后使用WP_Rest_Requestget_param方法从请求中获取值。 大多数情况是自动的,因为我们修改了架构以使其首先匹配。

修改WordPress REST API请求的WP_Query对象 (Modifying The WP_Query Object For The WordPress REST API Request)

I already showed how to do this in the first part of this post. Here is a class that implements the same pattern:

我已经在本文的第一部分中演示了如何执行此操作。 这是一个实现相同模式的类:

<?php
/**
* Class FilterWPQuery
*
* Changes WP_Query object
*
* @package ExamplePlugin
*/
class FilterWPQuery
{
/**
* Demonstrates how to use a different way to set the posts that WP_Query returns
* @uses "posts_pre_query"
*
* @param $postsOrNull
* @param \WP_Query $query
* @return mixed
*/
public static function posts_pre_query($postsOrNull, $query)
{
//Only run during WordPress API requests
if (defined('REST_REQUEST') && REST_REQUEST) {
//Prevent recursions
remove_filter('posts_pre_query', [FilterWPQuery::class, 'posts_pre_query'], 10);
//Don't run if posts are already sent
if (is_null($postsOrNull)) {
//Create 4 mock posts with different titles
$mockPosts = [];
for ($i = 0; $i <= 3; $i++) {
$mockPosts[$i] = (new \WP_Post((new \stdClass())));
$mockPosts[$i]->post_title = "Mock Post $i";
$mockPosts[$i]->filter = "raw";
}
//Return a mock array of mock posts
return $mockPosts;
}
//Always return something, even if its unchanged
return $postsOrNull;
}
}
}
init.php
<?php
/**
* Make this all work
*/
add_action('init', function () {
$postType = PostType::SLUG;
$preparedPostType = new PreparedPostTypes(get_post_types([], 'objects'));
$modifySchema = new ModifySchema($preparedPostType);
add_filter("rest_{$postType}_collection_params", [$modifySchema, 'filterSchema'], 25, 2);
$modifyQuery = new ModifyQueryArgs($preparedPostType);
add_filter("rest_{$postType}_query", [$modifyQuery, 'filterQueryArgs'], 25, 2);
});

I hope that you noticed that this code is very strongly tied to WordPress, and will not be testable. It is using WP_Post from WordPress, it’s checking a constant from WordPress, and interacting with WordPress’s Plugins API. We can mock WP_Post and we can set the constant ourselves. But the plugin’s API — that’s an essential feature to test. In my next few posts, I will cover how to refactor this class so we can use unit tests to cover everything except the effects of removing that filter, and integration tests to check that effect.

我希望您注意到此代码与WordPress紧密相关,并且无法测试。 它使用的是WordPress的WP_Post ,它正在检查WordPress的常量,并与WordPress的Plugins API进行交互。 我们可以模拟WP_Post ,我们可以自己设置常量。 但是插件的API –这是要测试的基本功能。 在接下来的几篇文章中,我将介绍如何重构此类,以便我们可以使用单元测试来涵盖除删除该过滤器的影响之外的所有内容,并使用集成测试来检查该影响。

I chose to use a static method for two reasons. First, it made it easy to add and remove it in multiple locations. For example, in the class ModifyQuery, I am hooking this filter only when needed:

我选择使用静态方法有两个原因。 首先,它使在多个位置添加和删除它变得容易。 例如,在类ModifyQuery ,仅在需要时才钩住此过滤器:

add_filter('posts_pre_query', [FilterWPQuery::class, 'posts_pre_query'], 10, 2);
$args['post_type'] = $this->restBasesToPostTypeSlugs($request[ModifySchema::ARGNAME]);
}
return $args;
}
/**
* Check if we should filter request args
*
* @param \WP_REST_Request $request
* @return bool
*/
public function shouldFilter(\WP_REST_Request $request): bool
{
$attributes = $request->get_attributes();
if (isset($attributes['args'][ModifySchema::ARGNAME])) {
if ($request->get_param(ModifySchema::ARGNAME)) {
return true;
}
}
return false;
}
/**
* Convert an array of rest bases to post type slugs
*
* @param array $postTypes
* @return array
*/
public function restBasesToPostTypeSlugs(array $postTypes): array
{
$postTypeSlugs = [];
foreach ($postTypes as $postTypeRestBase) {
if ($this->preparedPostTypes->restBaseToSlug($postTypeRestBase)) {
$postTypeSlugs[] = $this->preparedPostTypes->restBaseToSlug($postTypeRestBase);
}
}
return $postTypeSlugs;
}
}
ModifySchema.php
<?php
/**
* Class ModifySchema
*
* Modifies the REST API route schema so it has an argument "post_type"
*
*
* @package ExamplePlugin
*/
class ModifySchema
{
use UsesPreparedPostTypes;
/**
* The name of the extra argument we are adding to post type routes
*/
const ARGNAME = 'post_type';
/**
* Add post_type to schema
*
* @uses ""rest_{$postType}_collection_params" action
*
* @param array $query_params JSON Schema-formatted collection parameters.
* @param \WP_Post_Type $post_type Post type object.
*
* @return array
*/
public function filterSchema($query_params, $post_type)
{
if ($this->shouldFilter($post_type)) {
$query_params[self::ARGNAME] = [
[
'default' => PostType::RESTBASE,
'description' => __('Post type(s) for search query'),
'type' => 'array',
//Limit to public post types and allow query by rest base
'items' =>
[
'enum' => $this->preparedPostTypes->getPostTypeRestBases(),
'type' => 'string',
],
]
];
}
return $query_params;
}
/**
* Check if this post type's schema should be filtered
*
* @param \WP_Post_Type $WP_Post_Type
* @return bool
*/
public function shouldFilter(\WP_Post_Type $WP_Post_Type): bool
{
return PostType::SLUG === $WP_Post_Type->name;
}
}

Also, it is easy to create recursive loops when using this filter. Being able to easily remove it, like in this example code is very nice.

同样,使用此过滤器很容易创建递归循环。 像本示例代码中那样,能够轻松删除它非常好。

The other reason I chose to use a static method is that function interacts with other APIs. It will never be truly unit testable. This pattern, a class with static methods, makes it very easy to mock the class in integration tests, minimizing the effects of not having strong isolation in this one part of the system.

我选择使用静态方法的另一个原因是该函数与其他API交互。 它永远不会真正进行单元测试。 这种模式是带有静态方法的类,可以很轻松地在集成测试中模拟该类,从而最大程度地减少了在系统的这一部分中没有强烈隔离的影响。

使它们一起工作 (Making It All Work Together)

The code we’ve looked at so far is very decoupled from WordPress. That has a lot of benefits. But it means that as is, it doesn’t do anything. That’s fine. We’ve only handled the business logic requirements so far. Now we need to look at integration.

到目前为止,我们看过的代码已与WordPress脱钩。 那有很多好处。 但这意味着它什么也不做。 没关系。 到目前为止,我们仅处理了业务逻辑需求。 现在我们需要看一下集成。

That’s not too hard, it’s just a matter of adding some hooks. Which hooks? Exactly the same two hooks we designed our ModifyQuery and ModifySchema classes around. The desire for decoupled business logic doesn’t mean we can’t think of the actual reason we’re writing the code when we design its public interface. Otherwise, we’ll just be adding additional complexity to our code for no reason.

这并不难,只是添加一些钩子即可。 哪个钩? 我们设计了ModifyQueryModifySchema类的两个钩子ModifySchema 。 对分离的业务逻辑的渴望并不意味着我们在设计其公共接口时就无法想到编写代码的实际原因。 否则,我们将无缘无故地在代码中增加额外的复杂性。

In general, I try and only add software complexity when it makes life easier. I have strayed from this path in the past. We all have, it’s OK, practice forgiveness.

通常,我只会在使生活更轻松时才尝试增加软件的复杂性。 我过去曾走过这条路。 大家都可以练习宽恕。

The methods in the classes we’re about to hook to use exactly the same arguments and return types as the hooks. Their job is to dispatch business those values to the other components.

我们将要挂钩的类中的方法使用与挂钩完全相同的参数和返回类型。 他们的工作是将这些价值分配给其他组件。

<?php
/**
* Make this all work
*/
add_action('init', function () {
$postType = PostType::SLUG;
$preparedPostType = new PreparedPostTypes(get_post_types([], 'objects'));
$modifySchema = new ModifySchema($preparedPostType);
add_filter("rest_{$postType}_collection_params", [$modifySchema, 'filterSchema'], 25, 2);
$modifyQuery = new ModifyQueryArgs($preparedPostType);
add_filter("rest_{$postType}_query", [$modifyQuery, 'filterQueryArgs'], 25, 2);
});

下一步:测试 (Next Up: Testing)

That’s close enough for jazz. It will work. Lacking a formal system for adding hooks, this is as best as we can do for initialization. That’s fine. I’ll cover how to create a more sophisticated and extensible bootstrap process in a future post on integration testing with WordPress.

爵士乐已经足够接近了。 会的。 缺少用于添加钩子的正式系统,这是我们初始化时所能做的最好的事情。 没关系。 在以后的WordPress集成测试文章中,我将介绍如何创建更复杂和可扩展的引导过程。

In this post, we’ve looked at creating code to modify the schema, WP_Query argument generation and underlying WP_Query for a post type. I’d encourage you to turn this code into a plugin, using Composer for the autoloader. In my next post, we’ll look at unit tests to cover these classes.

在这篇文章中,我们研究了创建代码以修改架构, WP_Query参数生成以及帖子类型的基础WP_Query 。 我鼓励您使用Composer作为自动加载器,将此代码转换为插件。 在我的下一篇文章中 ,我们将讨论覆盖这些类的单元测试。

翻译自: https://www.sitepoint.com/wordpress-advanced-oop-customizing-rest-api-endpoints/

oop 类中定义 的函数

oop 类中定义 的函数_WordPress的高级OOP:自定义REST API端点相关推荐

  1. C++中模板类中的成员函数以及模板函数在类外定义

    在C++中,类中的成员函数可以在类外完成定义,从而显得类中的成员函数看起来简洁明了.但是模板类里的成员函数和模板函数与普通的成员函数在类外定义不同. 先定义一个模板类以及成员函数和模板函数: 接下我们 ...

  2. python 类装饰器和函数装饰器区别_python进阶之装饰器之4在类中定义装饰器,将装饰器定义为类,两者的区别与联系...

    # 把装饰器定义为类 # 定义中需要实现__call__(),__get__() 方法 import types from functools import wraps class Profiled: ...

  3. python 类中定义类_Python中的动态类定义

    python 类中定义类 Here's a neat Python trick you might just find useful one day. Let's look at how you ca ...

  4. 模板类中使用友元函数的方式,派生类友元函数对基类的成员使用情况

    在一般友元函数的前面加上 template<typename T),注意在函数的声明和定义处都要加这个模板 例如: //模板类,长方体类 template <typename Elemen ...

  5. 【Groovy】闭包 Closure ( 闭包调用 与 call 方法关联 | 接口中定义 call() 方法 | 类中定义 call() 方法 | 代码示例 )

    文章目录 总结 一.接口中定义 call() 方法 二.类中定义 call() 方法 三.完整代码示例 总结 在 实例对象后使用 " () " 括号符号 , 表示调用该实例对象的 ...

  6. C++学习笔记-----永远不要在派生类中改变虚函数的默认参数值

    提到虚函数,我们就会自然而然的想到多态,但是当虚函数中存有默认参数值的时候,在派生类中重定义这个虚函数时不可以改变这个参数的值. 请看下面的例子: #include "stdafx.h&qu ...

  7. php method_exists 检测类中是否包括函数

    2019独角兽企业重金招聘Python工程师标准>>> method_exists 检测类中是否包括函数 method_exists() 函数的语法如下: bool method_e ...

  8. caffe common 程序分析 类中定义类

    caffe中 有 common.hpp 和common.cpp // The main singleton of Caffe class and encapsulates the boost and ...

  9. 在类内定义成员函数、在类外定义成员函数、计算长方体的体积【C++面向对象编程类的使用经典案例】

    文章目录 一.在类内定义成员函数 二.在类外定义成员函数(使用符号::) 三.计算3个长方体的体积(class成员函数) 一.在类内定义成员函数 #include <iostream> u ...

最新文章

  1. 堡垒机的使用-及功能
  2. 游戏地图制作---Tiled使用教程
  3. 如何实现一根网线连接路由器,即能上网又可以使用ITV?
  4. java循环语句_Java十四天零基础入门-Java for循环语句
  5. 《我是一只IT小小鸟》会员书评
  6. GPS原始坐标转百度地图坐标(纯C代码)
  7. 非技术类:分享蓝牙音频接收器转音箱的使用
  8. 整洁代码--写好注释
  9. java jdbc 批处理_JDBC的批处理操作
  10. 外贸ERP软件之工贸一体企业解决方案
  11. entity framework 新手入门篇(3)-entity framework实现orderby,count,groupby,like,in,分页等...
  12. xboxone硬盘坏的表现_电脑硬盘用段时间如果损坏,我们该怎样修理硬盘???...
  13. uniapp,小程序,实现签名功能
  14. k8s、Deployment多副本资源详解、SERVICE通信、案例一nginx端口暴漏、案例二tomcat端口暴漏、案例三jenkins端口暴漏
  15. 深挖ThreadLocal
  16. 第十届蓝桥杯C语言B组题解
  17. 贝塞尔曲线 unity两点画曲线弧线三点
  18. 介绍几款可用的web应用防火墙
  19. 工作感悟之撤掉分公司的断臂之危
  20. 汉白玉产地在哪里_请问中国汉白玉产地在哪?

热门文章

  1. 华仪智能 嵌入式软件笔试题 (回忆版)
  2. 数据库相关的四位图灵奖获得者
  3. JSP设计猜数字游戏
  4. 泛式算法,仿函数和迭代器失效问题
  5. 成为优秀程序员的8种方法
  6. 内燃机汽车发动机工作原理-飞轮的作用
  7. sa登录时的各种错误
  8. 中国电信高管:下一步将建设五个全球流量转接中心
  9. 基于HTTP实现的小型web服务器
  10. java 门禁接口_java实现门禁系统