adt / base-query
Doctrine 组件
Requires
- php: >=8.1
- doctrine/orm: ^2.18
- nette/utils: ^3.2 | ^4.0
- dev-master
- v2.10.2
- v2.10.1
- v2.10
- v2.9.4
- v2.9.3
- v2.9.2
- v2.9.1
- v2.9
- v2.8.5
- v2.8.4
- v2.8.3
- v2.8.2
- v2.8.1
- v2.8
- v2.7.1
- v2.7
- v2.6.1
- v2.6
- v2.5
- v2.4
- v2.3
- v2.2.2
- v2.2.1
- v2.2
- v2.1.1
- v2.1
- v2.0.1
- v2.0
- v1.11
- v1.10.1
- v1.10
- v1.9.2
- v1.9.1
- v1.9
- v1.8.1
- v1.8
- v1.7.1
- v1.7
- v1.6.2
- v1.6.1
- v1.6
- v1.5.3
- v1.5.2
- v1.5.1
- v1.5
- v1.4.3
- v1.4.2
- v1.4.1
- v1.4
- v1.3.5
- v1.3.4
- v1.3.3
- v1.3.2
- v1.3.1
- v1.3
- v1.2
- v1.1
- v1.0.1
- v1.0
- dev-callable-post-fetch
- dev-query-object-fetch-support-offset
This package is auto-updated.
Last update: 2024-09-23 06:54:48 UTC
README
安装
composer require adt/doctrine-components
创建 QueryObject 类
/** * Annotations "extends" and "implements" and interface "FetchInterface" are used for PhpStorm code completion and PHPStan. * * @extends QueryObject<Profile> * @implements FetchInterface<Profile> */ class ProfileQueryObject extends QueryObject implements FetchInterface { const FILTER_SECURITY = 'filter_security'; const FILTER_IS_ACTIVE = 'filter_is_active'; private SecurityUser $securityUser; protected function getEntityClass(): string { return Profile::class; } protected function init(): void { parent::init(); $this->filter[self::FILTER_SECURITY] = function (QueryBuilder $qb) { if (!$this->securityUser->isAllowed('global.users')) { $qb->andWhere('e.id = :init_id') ->setParameter('id', $this->securityUser->getId()) } }; $this->byIsActive(true); } protected function setDefaultOrder(): void { $this->orderBy(['identity.lastName' => 'ASC', 'identity.firstName' => 'ASC', 'id' => 'ASC']); } public function byIsActive(bool $isActive): static { $this->filter[self::FILTER_IS_ACTIVE] = function(QueryBuilder $qb) use ($isActive) { $qb->andWhere('e.isActive = :isActive') ->setParameter('isActive', $isActive); }; return $this; } public function byQuery(string $query): static { $this->by(['identity.firstName', 'identity.lastName', 'identity.email', 'identity.phone'], $query); return $this; } public function setSecurityUser(SecurityUser $securityUser): static { $this->securityUser = $securityUser; return $this; } }
方法 getEntityClass
getEntityClass
方法必须指定并返回您的实体类。
方法 setDefaultOrder
setDefaultOrder
方法必须指定并设置默认排序。
方法 init
init 方法用于指定默认过滤器和排序。您在使用它时必须始终调用 parent::init()
。
回调数组 filter
filter
是一个回调函数数组,将在创建 QueryBuilder
时应用。
同样,您可以使用 order
回调来设置查询对象排序。
方法 by
和 orderBy
方法 by
是创建 filter
回调的快捷方式。它提供了一些有用的功能
-
当有更多列时,在它们之间使用
orWhere
。 -
如果
$value
类型为 'string',则使用LIKE %$value%
。您可以通过参数filterType
使用值FilterTypeEnum::STRICT
来更改它。 -
如果您想获取某个范围内的所有值,您可以使用参数
filterType
使用值FilterTypeEnum::RANGE
。
方法 orderBy
是设置 order
回调的快捷方式。
- 如果您只需要按一列排序,可以使用列名作为第一个参数,ASC/DESC 作为第二个参数,而不是数组。
您可以使用点表示法来自动连接其他实体(使用左连接)。
基本用法
创建实例
$queryObject = (new ProfileQueryObject($entityManager))->setSecurityUser($securityUser);
或者使用工厂创建更好
// example of Nette framework factory interface ProfileQueryObjectFactory { /** * Annotation is used for PhpStorm code completion. * * @return FetchInterface<Profile> */ public function create(): ProfileQueryObject; }
与 neon 一起使用
decorator: ADT\DoctrineObjects\QueryObject: setup: - setEntityManager(@App\Model\Doctrine\EntityManager) - setSecurityUser(@security.user)
获取结果
// returns all active profiles $profiles = $this->profileQueryObjectFactory->create()->fetch(); // returns all active profiles with name, email or phone containing "Doe" $profiles = $this->profileQueryObjectFactory->create()->search('Doe')->fetch(); // returns all disabled profiles $profiles = $this->profileQueryObjectFactory->create()->byIsActive(false)->fetch(); // returns first 10 active profiles $profiles = $this->profileQueryObjectFactory->create()->fetch(limit: 10);
// returns an active profile by ID or throws your own error when a profile does not exist if (!$profile = $this->profileQueryObjectFactory->create()->byId($id)->fetchOneOrNull()) { return new \Exception('Profile not found.'); } // returns first active profile with name, name, email or phone containing "Doe", "strict: false" has to be specified, // otherwise NonUniqueResultException may be thrown $profile = $this->profileQueryObjectFactory->create()->search('Doe')->fetchOneOrNull(strict: false);
// returns an active profile by ID or throws NoResultException when profile does not exist $profile = $this->profileQueryObjectFactory->create()->byId(self::ADMIN_PROFILE_ID)->fetchOne();
// returns an active profile as an array of {Profile::getId(): Profile::getName()} $profiles = $this->profileQueryObjectFactory->create()->fetchPairs('name', 'id');
// returns array of active profile ids $profileIds = $this->profileQueryObjectFactory->create()->fetchField('id');
计数结果
// returns number of all active profiles $numberOfProfiles = $this->profileQueryObjectFactory->create()->count();
禁用默认过滤器
// returns both active and disabled profiles $profiles = $this->profileQueryObjectFactory->create()->disableFilter(ProfileQueryObject::FILTER_IS_ACTIVE)->fetch(); // returns all profiles without applying a default security filter, for example in console $profiles = $this->profileQueryObjectFactory->create()->disableFilter(ProfileQueryObject::FILTER_SECURITY)->fetch(); // disable both filters $profiles = $this->profileQueryObjectFactory->create()->disableFilter([ProfileQueryObject::FILTER_IS_ACTIVE, ProfileQueryObject::FILTER_SECURITY])->fetch();
分页
// returns ResultSet, suitable for pagination and for using in templates $profileResultSet = $this->profileQueryObjectFactory->create()->getResultSet(page: 1, itemsPerPage: 10); // ResultSet implements IteratorAggregate, so you can use it in foreach foreach ($profileResultSet as $_profile) { echo $_profile->getId(); } // or call getIterator $profiles = $profileResultSet->getIterator(); // returns Nette\Utils\Paginator $paginator = $profileResultSet->getPaginator(); // returns total count of profiles $numberOfProfiles = $profileResultSet->count();
高级功能
手动连接
对于手动连接,您应该使用 innerJoin
和 leftJoin
方法
public function joinArtificialConsultant(QueryBuilder $qb) { $this->leftJoin($qb, 'e.artificialConsultant', 'e_ac'); } public function byShowOnWeb(): static { $this->filter[] = function (QueryBuilder $qb) { $this->joinArtificialConsultant($qb); $qb->andWhere('e.showOnWeb = TRUE OR (e.artificialConsultant IS NOT NULL AND e_ac.showOnWeb = TRUE)'); }; return $this; }
与 QueryBuilder::innerJoin
和 QueryBuilder::leftJoin
不同,这确保了相同的连接不会多次使用,也不会引发错误。
更多列
不要在 filter
回调内部使用 addSelect
。请改用 initSelect
方法。
class OfficeMessageGridQuery extends OfficeMessageQuery { protected function initSelect(QueryBuilder $qb): void { parent::initSelect($qb); $qb->addSelect('e.id'); $adSub = $qb->getEntityManager() ->getRepository(Entity\MessageRecipient::class) ->createQueryBuilder('mr_read') ->select('COUNT(1)') ->where('e = mr_read.officeMessage AND mr_read.readAt IS NOT NULL'); $qb->addSelect('(' . $adSub->getDQL() . ') read'); } }
或创建您自己的获取方法
class IdentityStatisticsQueryObject extends IdentityQueryObject { /** * @return array{'LT25': int, 'BT25ND35': int, 'BT35N45': int, 'BT45N55': int, 'BT55N65': int, 'GT65': int} */ public function getAgeRange(): array { $qb = $this->createQueryBuilder(withSelectAndOrder: false); $qb->addSelect(' SUM(CASE WHEN TIMESTAMPDIFF(YEAR, ei.birthDate, CURRENT_DATE()) < 25 THEN 1 ELSE 0 END) AS ' . UserEnum::AGE_RANGE_LT25 . ', SUM(CASE WHEN TIMESTAMPDIFF(YEAR, ei.birthDate, CURRENT_DATE()) >= 25 AND TIMESTAMPDIFF(YEAR, ei.birthDate, CURRENT_DATE()) < 35 THEN 1 ELSE 0 END) AS ' . UserEnum::AGE_RANGE_BETWEEN_25N35 . ', SUM(CASE WHEN TIMESTAMPDIFF(YEAR, ei.birthDate, CURRENT_DATE()) >= 35 AND TIMESTAMPDIFF(YEAR, ei.birthDate, CURRENT_DATE()) < 45 THEN 1 ELSE 0 END) AS ' . UserEnum::AGE_RANGE_BETWEEN_35N45 . ', SUM(CASE WHEN TIMESTAMPDIFF(YEAR, ei.birthDate, CURRENT_DATE()) >= 45 AND TIMESTAMPDIFF(YEAR, ei.birthDate, CURRENT_DATE()) < 55 THEN 1 ELSE 0 END) AS ' . UserEnum::AGE_RANGE_BETWEEN_45N55 . ', SUM(CASE WHEN TIMESTAMPDIFF(YEAR, ei.birthDate, CURRENT_DATE()) >= 55 AND TIMESTAMPDIFF(YEAR, ei.birthDate, CURRENT_DATE()) < 65 THEN 1 ELSE 0 END) AS ' . UserEnum::AGE_RANGE_BETWEEN_55N65 . ', SUM(CASE WHEN TIMESTAMPDIFF(YEAR, ei.birthDate, CURRENT_DATE()) > 65 THEN 1 ELSE 0 END) AS ' . UserEnum::AGE_RANGE_GT_65 . ' '); return $this->getQuery($qb)->getSingleResult(); } }
当指定了更多列时,默认的 fetch*
方法将不起作用。
更复杂的排序
您可以使用自己的排序回调而不是使用 orderBy
方法
public function orderByClosestDistance($customerLongitude, $customerLatitude): static { $this->order = function (QueryBuilder $qb) use ($customerLongitude, $customerLatitude) { $qb->addSelect(' ( 6373 * acos( cos( radians(:obcd_latitude) ) * cos( radians( e.latitude ) ) * cos( radians( e.longitude ) - radians(:obcd_longitude) ) + sin( radians(:obcd_latitude) ) * sin( radians( e.latitude ) ) ) ) AS HIDDEN distance' ) ->addOrderBy('distance', 'ASC') ->setParameter('obcd_latitude', $customerLatitude) ->setParameter('obcd_longitude', $customerLongitude); }; return $this; }
不要忘记在您的 addSelect
方法中使用 AS HIDDEN
,否则 fetch*
方法将不起作用。
方法 orById
如果您想获取所有活动记录加上一个特定的记录,您可以使用 orById
方法来绕过默认过滤器
$profiles = $this->profileQueryObjectFactory->create()->orById($id)->fetch();
这对于 <select>
特别有用。
在批处理中使用
如果您想迭代大量记录,您可以使用 https://github.com/Ocramius/DoctrineBatchUtils 与查询对象一起使用
$em = EntityManager::create($this->connection, $this->config); $profile = SimpleBatchIteratorAggregate::fromTraversableResult( $this->profileQueryObjectFactory->create()->setEntityManager($em)->fetchIterable(), $em, 100 // flush/clear after 100 iterations ); foreach ($profiles as $_profile) { }
您应该始终使用新的 EntityManager
实例,而不是默认实例(因为 EntityManager::clear
)。
提示
-
始终将所有逻辑放在
filter
或order
回调内部。这将确保所有依赖项(如已登录用户等)都已设置。 -
始终在 QueryBuilder 上使用
and*
方法(andWhere
、andSelect
等),而不是使用where
、select
等。 -
不要使用
QueryBuilder::resetDQLPart
方法,因为这违背了 QueryObject 的基本思想 -
andWhere
方法中的参数应该按照方法名和参数名命名,以避免冲突。 -
by
和orderBy
是公共方法,但最好创建自己的by*
或orderBy*
方法。 -
您应该始终指定一个确定的顺序,理想情况下使用主键。