Cache total subscribers count, add tests
[MAILPOET-4416]
This commit is contained in:
@@ -219,6 +219,7 @@ class WP {
|
||||
$this->updateFirstNameIfMissing();
|
||||
$this->insertUsersToSegment();
|
||||
$this->removeOrphanedSubscribers();
|
||||
$this->subscribersRepository->invalidateTotalSubscribersCache();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@@ -188,6 +188,7 @@ class WooCommerce {
|
||||
$this->updateGlobalStatus();
|
||||
}
|
||||
|
||||
$this->subscribersRepository->invalidateTotalSubscribersCache();
|
||||
return $lastCheckedOrderId;
|
||||
}
|
||||
|
||||
|
@@ -512,6 +512,8 @@ class Import {
|
||||
}
|
||||
}
|
||||
if (empty($createdOrUpdatedSubscribers)) return null;
|
||||
|
||||
$this->subscriberRepository->invalidateTotalSubscribersCache();
|
||||
$createdOrUpdatedSubscribersIds = array_column($createdOrUpdatedSubscribers, 'id');
|
||||
if ($subscribersCustomFields) {
|
||||
$this->createOrUpdateCustomFields(
|
||||
@@ -588,7 +590,9 @@ class Import {
|
||||
* @return array
|
||||
*/
|
||||
public function synchronizeWPUsers(array $wpUsers): array {
|
||||
return array_map([$this->wpSegment, 'synchronizeUser'], $wpUsers);
|
||||
$users = array_map([$this->wpSegment, 'synchronizeUser'], $wpUsers);
|
||||
$this->subscriberRepository->invalidateTotalSubscribersCache();
|
||||
return $users;
|
||||
}
|
||||
|
||||
public function addSubscribersToSegments(array $subscribersIds, array $segmentsIds): void {
|
||||
|
@@ -7,6 +7,7 @@ use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Entities\SubscriberCustomFieldEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Entities\SubscriberSegmentEntity;
|
||||
use MailPoet\Util\License\Features\Subscribers;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
use MailPoetVendor\Carbon\CarbonImmutable;
|
||||
@@ -41,10 +42,7 @@ class SubscribersRepository extends Repository {
|
||||
return SubscriberEntity::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getTotalSubscribers() {
|
||||
public function getTotalSubscribers(): int {
|
||||
$query = $this->entityManager
|
||||
->createQueryBuilder()
|
||||
->select('count(n.id)')
|
||||
@@ -59,6 +57,10 @@ class SubscribersRepository extends Repository {
|
||||
return (int)$query->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function invalidateTotalSubscribersCache(): void {
|
||||
$this->wp->deleteTransient(Subscribers::SUBSCRIBERS_COUNT_CACHE_KEY);
|
||||
}
|
||||
|
||||
public function findBySegment(int $segmentId): array {
|
||||
return $this->entityManager
|
||||
->createQueryBuilder()
|
||||
@@ -116,6 +118,7 @@ class SubscribersRepository extends Repository {
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()->execute();
|
||||
|
||||
$this->invalidateTotalSubscribersCache();
|
||||
return count($ids);
|
||||
}
|
||||
|
||||
@@ -135,6 +138,7 @@ class SubscribersRepository extends Repository {
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()->execute();
|
||||
|
||||
$this->invalidateTotalSubscribersCache();
|
||||
return count($ids);
|
||||
}
|
||||
|
||||
@@ -171,6 +175,7 @@ class SubscribersRepository extends Repository {
|
||||
->getQuery()->execute();
|
||||
});
|
||||
|
||||
$this->invalidateTotalSubscribersCache();
|
||||
return $count;
|
||||
}
|
||||
|
||||
@@ -282,6 +287,7 @@ class SubscribersRepository extends Repository {
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()->execute();
|
||||
|
||||
$this->invalidateTotalSubscribersCache();
|
||||
return count($ids);
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,7 @@ namespace MailPoet\Util\License\Features;
|
||||
use MailPoet\Services\Bridge;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class Subscribers {
|
||||
const SUBSCRIBERS_OLD_LIMIT = 2000;
|
||||
@@ -18,6 +19,9 @@ class Subscribers {
|
||||
const PREMIUM_EMAIL_VOLUME_LIMIT_SETTING_KEY = 'premium.premium_key_state.data.email_volume_limit';
|
||||
const PREMIUM_EMAILS_SENT_SETTING_KEY = 'premium.premium_key_state.data.emails_sent';
|
||||
const PREMIUM_SUPPORT_SETTING_KEY = 'premium.premium_key_state.data.support_tier';
|
||||
const SUBSCRIBERS_COUNT_CACHE_KEY = 'mailpoet_subscribers_count';
|
||||
const SUBSCRIBERS_COUNT_CACHE_EXPIRATION_MINUTES = 60;
|
||||
const SUBSCRIBERS_COUNT_CACHE_MIN_VALUE = 1000;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
@@ -25,15 +29,20 @@ class Subscribers {
|
||||
/** @var SubscribersRepository */
|
||||
private $subscribersRepository;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
public function __construct(
|
||||
SettingsController $settings,
|
||||
SubscribersRepository $subscribersRepository
|
||||
SubscribersRepository $subscribersRepository,
|
||||
WPFunctions $wp
|
||||
) {
|
||||
$this->settings = $settings;
|
||||
$this->subscribersRepository = $subscribersRepository;
|
||||
$this->wp = $wp;
|
||||
}
|
||||
|
||||
public function check() {
|
||||
public function check(): bool {
|
||||
$limit = $this->getSubscribersLimit();
|
||||
if ($limit === false) return false;
|
||||
$subscribersCount = $this->getSubscribersCount();
|
||||
@@ -49,11 +58,21 @@ class Subscribers {
|
||||
return $emailsSent > $emailVolumeLimit;
|
||||
}
|
||||
|
||||
public function getSubscribersCount() {
|
||||
return $this->subscribersRepository->getTotalSubscribers();
|
||||
public function getSubscribersCount(): int {
|
||||
$count = $this->wp->getTransient(self::SUBSCRIBERS_COUNT_CACHE_KEY);
|
||||
if (is_numeric($count)) {
|
||||
return (int)$count;
|
||||
}
|
||||
$count = $this->subscribersRepository->getTotalSubscribers();
|
||||
|
||||
// cache only when number of subscribers exceeds minimum value
|
||||
if ($count > self::SUBSCRIBERS_COUNT_CACHE_MIN_VALUE) {
|
||||
$this->wp->setTransient(self::SUBSCRIBERS_COUNT_CACHE_KEY, $count, self::SUBSCRIBERS_COUNT_CACHE_EXPIRATION_MINUTES * 60);
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
public function hasValidApiKey() {
|
||||
public function hasValidApiKey(): bool {
|
||||
return $this->hasValidMssKey() || $this->hasValidPremiumKey();
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,137 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Subscribers;
|
||||
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoet\Util\License\Features\Subscribers;
|
||||
use Codeception\Stub;
|
||||
|
||||
class SubscribersTest extends \MailPoetTest {
|
||||
/** @var Subscribers */
|
||||
private $subscribers;
|
||||
|
||||
/** @var SubscribersRepository */
|
||||
private $subscribersRepository;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
public function _before() {
|
||||
parent::_before();
|
||||
$this->subscribers = $this->diContainer->get(Subscribers::class);
|
||||
$this->subscribersRepository = $this->diContainer->get(SubscribersRepository::class);
|
||||
$this->wp = $this->diContainer->get(WPFunctions::class);
|
||||
$this->wp->deleteTransient(Subscribers::SUBSCRIBERS_COUNT_CACHE_KEY);
|
||||
}
|
||||
|
||||
public function testItComputesSubscribersCount() {
|
||||
// no subscribers
|
||||
$count = $this->subscribers->getSubscribersCount();
|
||||
expect($count)->same(0);
|
||||
|
||||
// create some subscribers (unconfirmed, subscribed, and inactive should be counted)
|
||||
$this->createSubscriber('unconfirmed@fake.loc', SubscriberEntity::STATUS_UNCONFIRMED);
|
||||
$this->createSubscriber('subscribed@fake.loc', SubscriberEntity::STATUS_SUBSCRIBED);
|
||||
$this->createSubscriber('inactive@fake.loc', SubscriberEntity::STATUS_INACTIVE);
|
||||
|
||||
// check count
|
||||
$count = $this->subscribers->getSubscribersCount();
|
||||
expect($count)->same(3);
|
||||
|
||||
// add more subscribers (bounced, unsubscribed, and trashed should not be counted)
|
||||
$this->createSubscriber('bounced@fake.loc', SubscriberEntity::STATUS_BOUNCED);
|
||||
$this->createSubscriber('unsubscribed@fake.loc', SubscriberEntity::STATUS_UNSUBSCRIBED);
|
||||
$trashed = $this->createSubscriber('trashed@fake.loc', SubscriberEntity::STATUS_SUBSCRIBED);
|
||||
$trashed->setDeletedAt(new \DateTimeImmutable());
|
||||
$this->subscribersRepository->flush();
|
||||
|
||||
// check count
|
||||
$count = $this->subscribers->getSubscribersCount();
|
||||
expect($count)->same(3);
|
||||
}
|
||||
|
||||
public function testItDoesntCacheSubscribersCountForLowValues() {
|
||||
// no subscribers
|
||||
$count = $this->subscribers->getSubscribersCount();
|
||||
expect($count)->same(0);
|
||||
|
||||
// add subscriber
|
||||
$this->createSubscriber('one@fake.loc', SubscriberEntity::STATUS_SUBSCRIBED);
|
||||
$count = $this->subscribers->getSubscribersCount();
|
||||
expect($count)->same(1);
|
||||
|
||||
// add another subscriber (count updates without cache purging)
|
||||
$this->createSubscriber('two@fake.loc', SubscriberEntity::STATUS_SUBSCRIBED);
|
||||
$count = $this->subscribers->getSubscribersCount();
|
||||
expect($count)->same(2);
|
||||
}
|
||||
|
||||
public function testItCachesSubscribersCountForHighValues() {
|
||||
$subscribers = $this->getServiceWithOverrides(Subscribers::class, [
|
||||
'subscribersRepository' => Stub::make(SubscribersRepository::class, [
|
||||
'getTotalSubscribers' => 123456,
|
||||
]),
|
||||
]);
|
||||
|
||||
$count = $subscribers->getSubscribersCount();
|
||||
expect($count)->same(123456);
|
||||
|
||||
$subscribers = $this->getServiceWithOverrides(Subscribers::class, [
|
||||
'subscribersRepository' => Stub::make(SubscribersRepository::class, [
|
||||
'getTotalSubscribers' => 999999,
|
||||
]),
|
||||
]);
|
||||
|
||||
// check count (cached value)
|
||||
$count = $subscribers->getSubscribersCount();
|
||||
expect($count)->same(123456);
|
||||
|
||||
// check count (uncached value)
|
||||
$this->wp->deleteTransient(Subscribers::SUBSCRIBERS_COUNT_CACHE_KEY);
|
||||
$count = $subscribers->getSubscribersCount();
|
||||
expect($count)->same(999999);
|
||||
}
|
||||
|
||||
public function testItInvalidatesSubscribersCountCache() {
|
||||
$subscribers = $this->getServiceWithOverrides(Subscribers::class, [
|
||||
'subscribersRepository' => Stub::make(SubscribersRepository::class, [
|
||||
'getTotalSubscribers' => 123456,
|
||||
]),
|
||||
]);
|
||||
$subscribers->getSubscribersCount();
|
||||
|
||||
$subscribers = $this->getServiceWithOverrides(Subscribers::class, [
|
||||
'subscribersRepository' => Stub::make(SubscribersRepository::class, [
|
||||
'getTotalSubscribers' => 999999,
|
||||
]),
|
||||
]);
|
||||
|
||||
// check count (cached value)
|
||||
$count = $subscribers->getSubscribersCount();
|
||||
expect($count)->same(123456);
|
||||
|
||||
// modify timestamp, check count (-> uncached value)
|
||||
$this->wp->updateOption(
|
||||
'_transient_timeout_' . Subscribers::SUBSCRIBERS_COUNT_CACHE_KEY,
|
||||
$this->wp->currentTime('timestamp') - 1
|
||||
);
|
||||
$count = $subscribers->getSubscribersCount();
|
||||
expect($count)->same(999999);
|
||||
}
|
||||
|
||||
public function _after() {
|
||||
parent::_after();
|
||||
$this->truncateEntity(SubscriberEntity::class);
|
||||
$this->wp->deleteTransient(Subscribers::SUBSCRIBERS_COUNT_CACHE_KEY);
|
||||
}
|
||||
|
||||
private function createSubscriber(string $email, string $status): SubscriberEntity {
|
||||
$subscriber = new SubscriberEntity();
|
||||
$subscriber->setEmail($email);
|
||||
$subscriber->setStatus($status);
|
||||
$this->subscribersRepository->persist($subscriber);
|
||||
$this->subscribersRepository->flush();
|
||||
return $subscriber;
|
||||
}
|
||||
}
|
@@ -6,6 +6,7 @@ use Codeception\Util\Stub;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class SubscribersTest extends \MailPoetUnitTest {
|
||||
public function testCheckReturnsTrueIfOldUserReachedLimit() {
|
||||
@@ -178,12 +179,18 @@ class SubscribersTest extends \MailPoetUnitTest {
|
||||
if ($name === SubscribersFeature::PREMIUM_SUPPORT_SETTING_KEY) return isset($specs['support_tier']) ? $specs['support_tier'] : 'free';
|
||||
},
|
||||
]);
|
||||
|
||||
$subscribersRepository = Stub::make(SubscribersRepository::class, [
|
||||
'getTotalSubscribers' => function() use($specs) {
|
||||
return $specs['subscribers_count'];
|
||||
},
|
||||
]);
|
||||
|
||||
return new SubscribersFeature($settings, $subscribersRepository);
|
||||
$wpFunctions = Stub::make(WPFunctions::class, [
|
||||
'getTransient' => false,
|
||||
'setTransient' => false,
|
||||
]);
|
||||
|
||||
return new SubscribersFeature($settings, $subscribersRepository, $wpFunctions);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user