williarin / wordpress-interop
与 WordPress 数据库协同工作的互操作性库
Requires
- php: >=8.0
- doctrine/annotations: ^2.0
- doctrine/dbal: ^3.8 || ^4.0
- phpdocumentor/reflection-docblock: ^5.3
- phpstan/phpdoc-parser: ^1.27
- symfony/config: ^6.0 || ^7.0
- symfony/dependency-injection: ^6.0 || ^7.0
- symfony/options-resolver: ^6.0 || ^7.0
- symfony/property-access: ^6.0 || ^7.0
- symfony/property-info: ^6.0 || ^7.0
- symfony/serializer: ^6.0 || ^7.0
- symfony/string: ^6.0 || ^7.0
- symfony/translation-contracts: ^3.0
Requires (Dev)
- ergebnis/composer-normalize: ^2.23
- jetbrains/phpstorm-stubs: ^2022.3
- kubawerlos/php-cs-fixer-custom-fixers: ^3.7
- maglnet/composer-require-checker: ^4.4
- nikic/php-parser: ^4.19
- phpro/grumphp: ^1.16 || ^2.0 <2.5
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^9.5
- rector/rector: ^0.12.13
- roave/security-advisories: dev-latest
- symfony/dotenv: ^6.0 || ^7.0
- symfony/var-dumper: ^6.0 || ^7.0
- symplify/coding-standard: ^11.0
- symplify/easy-coding-standard: ^11.0
This package is auto-updated.
Last update: 2024-09-26 11:38:57 UTC
README
简介
此库旨在通过第三方应用程序简化与 WordPress 数据库的交互。它依赖于 Doctrine DBAL,看起来像 Doctrine ORM。
它可以执行一些简单任务,例如查询帖子、检索附件数据等。
您可以通过添加自己的存储库和查询方法来扩展它。
警告! 虽然它看起来像是一个 ORM,但它不是一个 ORM 库。它没有双向数据操作功能。将其视为一个简单的 WordPress 数据库操作辅助库。
安装
此库可以作为独立库使用
composer require williarin/wordpress-interop
或与 Symfony 一起使用
composer require williarin/wordpress-interop-bundle
在专用存储库页面上找到 Symfony 扩展的文档。
使用方法
概述
$post = $manager->getRepository(Post::class)->find(15);
详细说明
首先,需要创建一个与您的 DBAL 连接相关联的实体管理器,目标为您的 WordPress 数据库。
$connection = DriverManager::getConnection(['url' => 'mysql://user:pass@localhost:3306/wp_mywebsite?serverVersion=8.0']); $objectNormalizer = new ObjectNormalizer( new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())), new CamelCaseToSnakeCaseNameConverter(), null, new ReflectionExtractor() ); $serializer = new Serializer([ new DateTimeNormalizer(), new ArrayDenormalizer(), new SerializedArrayDenormalizer($objectNormalizer), $objectNormalizer, ]); $manager = new EntityManager($connection, $serializer);
然后您可以查询数据库
/** @var PostRepository $postRepository */ $postRepository = $manager->getRepository(Post::class); $myPost = $postRepository->find(15); $allPosts = $postRepository->findAll();
文档
基本查询
这适用于从 BaseEntity
继承的任何实体。内置实体包括 Post
、Page
、Attachment
和 Product
,但您可以 创建自己的。
// Fetch a post by ID $post = $manager->getRepository(Post::class)->find(1); // Fetch the latest published post $post = $manager->getRepository(Post::class) ->findOneByPostStatus('publish', ['post_date' => 'DESC']); // Fetch the latest published post which has 1 comment $post = $manager->getRepository(Post::class) ->findOneBy( ['post_status' => 'publish', 'comment_count' => 1], ['post_date' => 'DESC'], ); // Fetch the latest published post which has the most comments $post = $manager->getRepository(Post::class) ->findOneByPostStatus( 'publish', ['comment_count' => 'DESC', 'post_date' => 'DESC'], ); // Fetch all posts which have draft or private status $posts = $manager->getRepository(Post::class) ->findByPostStatus(new Operand(['draft', 'private'], Operand::OPERATOR_IN)); // Fetch all posts $posts = $manager->getRepository(Post::class)->findAll(); // Fetch all private posts $posts = $manager->getRepository(Post::class)->findByPostStatus('private'); // Fetch all products whose titles match regexp $products = $manager->getRepository(Product::class) ->findByPostTitle(new Operand('Hoodie.*Pocket|Zipper', Operand::OPERATOR_REGEXP));
EAV 查询
EAV 术语指的是 WordPress 通过“元”术语(如 wp_postmeta
、wp_termmeta
、wp_usermeta
等)使用的 实体-属性-值模型。这里我们谈论的是 wp_postmeta
。
查询系统支持直接查询 EAV 属性。
在下面的示例中,sku
和 stock_status
是 wp_postmeta
表的属性。
注意:字段名映射到它们的属性名。例如,_sku
变为 sku
,或 _wc_average_rating
变为 average_rating
。
// Fetch a product by its SKU $product = $manager->getRepository(Product::class)->findOneBySku('woo-vneck-tee'); // Fetch the latest published product which is in stock $product = $manager->getRepository(Product::class) ->findOneBy( ['stock_status' => 'instock', 'post_status' => 'publish'], ['post_date' => 'DESC'], ); // Fetch all published products which are in stock $products = $manager->getRepository(Product::class) ->findBy( ['stock_status' => 'instock', 'post_status' => 'publish'], ['post_date' => 'DESC'], ); // Fetch all products whose sku match regexp $products = $manager->getRepository(Product::class) ->findBySku(new Operand('hoodie.*logo|zipper', Operand::OPERATOR_REGEXP));
如果您查询了一个实体中不存在的 EAV 属性,将抛出 InvalidFieldNameException
异常。
为了允许查询额外的动态属性,在查询之前将 allow_extra_properties
选项设置为 true
。但请注意,这些选项适用于存储库而不是查询,这意味着它们将适用于所有后续查询。
$page = $manager->getRepository(Page::class) ->setOptions([ 'allow_extra_properties' => true, ]) ->findOneBy([ new SelectColumns(['id', select_from_eav('wp_page_template')]), 'post_status' => 'publish', 'wp_page_template' => 'default', ]) ; // $page->wpPageTemplate === 'default'
嵌套条件
对于更复杂的查询需求,您可以添加嵌套条件。
注意:它仅适用于列,而不是 EAV 属性。
// Fetch Hoodies as well as products with at least 30 comments, all of which are in stock $products = $manager->getRepository(Product::class) ->findBy([ new NestedCondition(NestedCondition::OPERATOR_OR, [ 'post_title' => new Operand('Hoodie%', Operand::OPERATOR_LIKE), 'comment_count' => new Operand(30, Operand::OPERATOR_GREATER_THAN_OR_EQUAL), ]), 'stock_status' => 'instock', ]); // Fetch two products by their SKU and two by their ID $products = $manager->getRepository(Product::class) ->findBy([ new NestedCondition(NestedCondition::OPERATOR_OR, [ 'sku' => new Operand(['woo-tshirt', 'woo-single'], Operand::OPERATOR_IN), 'id' => new Operand([19, 20], Operand::OPERATOR_IN), ]), ]); // count($products) === 4
EAV 关系条件
根据实体的 EAV 关系查询实体。
注意:EAV 字段必须使用其原始名称,这与直接 EAV 查询的映射字段不同。
// Fetch the featured image of the post with ID "4" $attachment = $manager->getRepository(Attachment::class) ->findOneBy([ new RelationshipCondition(4, '_thumbnail_id'), ]); // Get featured images of posts 4, 13, 18 and 23 at once $attachments = $manager->getRepository(Attachment::class) ->findBy([ new RelationshipCondition( new Operand([4, 13, 18, 23], Operand::OPERATOR_IN), '_thumbnail_id', ), ]); // Same as above example but include the original ID in the result $attachments = $manager->getRepository(Attachment::class) ->findBy([ new RelationshipCondition( new Operand([4, 13, 18, 23], Operand::OPERATOR_IN), '_thumbnail_id', 'original_post_id', ), ]); // $attachments[0]->originalPostId === 4
术语和分类关系条件
根据实体的术语和分类关系查询实体。
// Fetch products in the category "Hoodies" $products = $manager->getRepository(Product::class) ->findBy([ new TermRelationshipCondition([ 'taxonomy' => 'product_cat', 'name' => 'Hoodies', ]), ]);
此外,您还可以从联合实体查询术语,并指定术语表的名称。
在这个例子中,我们假设产品有一个 related_product
postmeta。
// Fetch a product's category and the category of its related product $product = $manager->getRepository(Product::class) ->findOneBy([ new SelectColumns([ 'id', 'main.name AS category', 'related.name AS related_category', select_from_eav( fieldName: 'related_product', metaKey: 'related_product', // needed as it's not starting with an underscore ), ]), new TermRelationshipCondition( ['taxonomy' => 'product_cat'], termTableAlias: 'main', ), new TermRelationshipCondition( ['taxonomy' => 'product_cat'], joinConditionField: 'related_product', termTableAlias: 'related', ), 'id' => 22, ]); // $product->category === 'Hoodies' // $product->relatedCategory === 'Accessories'
如果没有指定,术语表别名默认为 t_0
、t_1
等。
还提供了一个特殊运算符 Operand::OPERATOR_IN_ALL
来匹配数组中的所有值。
// Fetch products that have both 'featured' and 'accessories' terms $products = $manager->getRepository(Product::class) ->findBy([ new TermRelationshipCondition([ 'slug' => new Operand(['featured', 'accessories'], Operand::OPERATOR_IN_ALL), ]), ]);
此运算符不仅限于术语查询,但它是最明显的用例。
帖子关系条件
根据其帖子关系查询术语。
// Fetch all terms of the product with SKU "super-forces-hoodie" // belonging to all taxonomies except "product_tag", "product_type", "product_visibility". $terms = $manager->getRepository(Term::class) ->findBy([ new SelectColumns(['taxonomy', 'name']), new PostRelationshipCondition(Product::class, [ 'post_status' => new Operand(['publish', 'private'], Operand::OPERATOR_IN), 'sku' => 'super-forces-hoodie', ]), 'taxonomy' => new Operand( ['product_tag', 'product_type', 'product_visibility'], Operand::OPERATOR_NOT_IN, ), ]);
限制所选列
一次性查询所有列会变慢,尤其是当你有大量实体需要检索时。你可以像以下示例中一样限制查询的列。
它适用于基本列以及EAV属性。
// Fetch only products title and SKU $products = $manager->getRepository(Product::class) ->findBy([ new SelectColumns(['post_title', 'sku']), 'sku' => new Operand('hoodie.*logo|zipper', Operand::OPERATOR_REGEXP), ]); // Product entities are filled with null values except $postTitle and $sku
你还可以选择一个在你的实体中没有映射属性的字段。
$product = $manager->getRepository(Product::class) ->findOneBy([ new SelectColumns(['id', 'post_title', 'name AS category']), new TermRelationshipCondition([ 'taxonomy' => 'product_cat' ]), ]); // $product->category will have the corresponding category name
扩展生成的查询
对于更高级的需求,也可以检索查询构建器并修改它以满足你的需求。
注意:使用select_from_eav()
函数查询EAV属性。
// Fetch all products but override SELECT clause with only tree columns $repository = $manager->getRepository(Product::class); $result = $repository->createFindByQueryBuilder([], ['sku' => 'ASC']) ->select('id', 'post_title', select_from_eav('sku')) ->executeQuery() ->fetchAllAssociative(); $products = $repository->denormalize($result, Product::class . '[]');
创建一个新的术语
如果已经存在,术语不会重复。
// Create a new product category $repository = $manager->getRepository(Term::class); $term = $repository->createTermForTaxonomy('Jewelry', 'product_cat');
将术语添加到实体中
// Add all existing product tags to a product $repository = $manager->getRepository(Term::class); $repository->addTermsToEntity($product, $repository->findByTaxonomy('product_tag'));
从实体中移除术语
// Remove all existing product tags from a product $repository = $manager->getRepository(Term::class); $repository->removeTermsFromEntity($product, $repository->findByTaxonomy('product_tag'));
字段更新
在更新之前有一个类型验证。你不能将字符串分配给日期字段、整数字段等。
$repository = $manager->getRepository(Post::class); $repository->updatePostTitle(4, 'New title'); $repository->updatePostContent(4, 'New content'); $repository->updatePostDate(4, new \DateTime()); // Alternative $repository->updateSingleField(4, 'post_status', 'publish');
实体创建或更新
一次性创建或更新实体及其所有字段。
限制
- 只有基本字段(在
wp_posts
表中的列)被持久化,不是EAV。 - 在对象创建或更新之前必须填写所有属性,因为架构不支持NULL值。
- 没有更改跟踪
$repository = $manager->getRepository(Post::class); $post = $repository->findOneByPostTitle('My post'); $post->postTitle = 'A new title for my post'; $post->postStatus = 'publish'; $repository->persist($post); // or directly calling the EntityManager $manager->persist($post);
实体重复
使用DuplicationService
复制一个具有所有EAV属性和术语的实体。生成的实体已经持久化并具有新的ID。
$duplicationService = $registry->get(DuplicationService::class); // or $duplicationService = DuplicationService::create($manager); // Duplicate by ID $newProduct = $duplicationService->duplicate(23, Product::class); // Duplicate by object $product = $manager->getRepository(Product::class)->findOneBySku('woo-hoodie-with-zipper'); $newProduct = $duplicationService->duplicate($product);
可用的实体和存储库
Post
和PostRepository
Page
和PageRepository
Attachment
和AttachmentRepository
Option
和OptionRepository
PostMeta
和PostMetaRepository
Comment
和CommentRepository
Term
和TermRepository
TermTaxonomy
和TermTaxonomyRepository
User
和UserRepository
Product
和ProductRepository
(WooCommerce)ShopOrder
和ShopOrderRepository
(WooCommerce)ShopOrderItem
和ShopOrderItemRepository
(WooCommerce)
获取选项值
为了检索WordPress选项,你有几种选择
// Query the option name yourself $blogName = $manager->getRepository(Option::class)->find('blogname'); // Use a predefined getter $blogName = $manager->getRepository(Option::class)->findBlogName(); // If there isn't a predefined getter, use a magic method. // Here we get the 'active_plugins' option, automatically unserialized. $plugins = $manager->getRepository(Option::class)->findActivePlugins();
创建自己的实体和存储库
假设你有一个自定义帖子类型名为project
。
首先创建一个简单的实体
// App/Wordpress/Entity/Project.php namespace App\Wordpress\Entity; use App\Wordpress\Repository\ProjectRepository; use Williarin\WordpressInterop\Attributes\RepositoryClass; use Williarin\WordpressInterop\Bridge\Entity\BaseEntity; #[RepositoryClass(ProjectRepository::class)] final class Project extends BaseEntity { }
然后创建一个存储库
// App/Wordpress/Repository/ProjectRepository.php namespace App\Wordpress\Repository; use App\Wordpress\Entity\Project; use Symfony\Component\Serializer\SerializerInterface; use Williarin\WordpressInterop\Bridge\Repository\AbstractEntityRepository; use Williarin\WordpressInterop\EntityManagerInterface; /** * @method Project|null find($id) * @method Project[] findAll() */ final class ProjectRepository extends AbstractEntityRepository { public function __construct(/* inject additional services if you need them */) { parent::__construct(Project::class); } protected function getPostType(): string { return 'project'; } // Add your own methods here }
然后这样使用它
$allProjects = $manager->getRepository(Project::class)->findAll();
如果你的实体在单独的表中,也适用,只需一些额外的配置。以ShopOrderItemRepository为例。
你必须覆盖一些常量
final class ShopOrderItemRepository extends AbstractEntityRepository { protected const TABLE_NAME = 'woocommerce_order_items'; protected const TABLE_META_NAME = 'woocommerce_order_itemmeta'; protected const TABLE_IDENTIFIER = 'order_item_id'; protected const TABLE_META_IDENTIFIER = 'order_item_id'; protected const FALLBACK_ENTITY = ShopOrderItem::class; public function __construct() { parent::__construct(ShopOrderItem::class); } }
实体和存储库继承
你可能为现有的实体(如Post
)有一些自定义属性。
- 创建一个新的实体,它通过新字段扩展了
Post
- 创建一个新的存储库,它通过扩展
PostRepository
并覆盖getEntityClassName()
方法来返回你的新MyPost
实体类名 - 向你的
PostRepository
添加映射字段 - 向你的
MyPost
实体添加#[RepositoryClass(MyPostRepository::class)]
贡献
所有贡献都受欢迎。
如何贡献
- 在这个存储库上分叉
- 在你的分叉上创建一个新分支
- 做一些更改,然后运行
make test
以确保一切正常,运行make fix
以修复ECS和composer.json
错误 - 使用常规提交语法提交
- 在这个存储库的
master
分支上创建一个拉取请求
许可
版权(c)2022,William Arin