plummer/calendarful

支持重复事件的日历生成器。

v0.2.1 2016-01-10 13:09 UTC

README

Author Build Status Packagist Packagist

Calendarful 是一个简单且易于扩展的 PHP 解决方案,允许生成重复事件的实例,从而消除在数据库或其他存储方法中存储数百或数千个实例的需求。

尽管它非常简单,但此包附带默认接口实现,可即插即用。如果需要,您也可以轻松提供自己的实现。

它符合 PSR-2 规范。

安装

此包可以通过 Composer 安装。

$ composer require plummer/calendarful

它需要 PHP >= 5.3.0

必需的设置

在使用此包之前,需要执行一些步骤。

事件模型

此包要求您拥有 EventRecurrentEvent 模型。为了使这些模型与该包兼容,它们 必须 实现相关的包接口。

Event 模型代表标准非重复事件,并遵循 EventInterface

<?php

use Plummer\Calendarful\Event\EventInterface;

class Event implements EventInterface
{
    
}

RecurrentEvent 模型代表重复事件,并遵循 RecurrentEventInterface

<?php

use Plummer\Calendarful\Event\RecurrentEventInterface;

class RecurrentEvent implements RecurrentEventInterface
{
    
}

RecurrentEventInterface 实际上扩展了 EventInterface,以便其实现可以提供更多针对重复事件的功能。

理想情况下,模型上应具有与接口的每个获取器和设置器方法相关的属性,以便该包能够最有效地运行。例如,应有一个 '开始日期' 属性,该属性由 getStartDate()setStartDate() 应用。

位于 tests 目录下的 MockEventMockRecurrentEvent 类提供了要使用的方法和相关属性的示例实现。

EventInterfaceRecurrentEventInterface 文件中每个方法的文档也提供了对每个属性目的的简要说明。

如果您不喜欢有两个 Event 模型的想法,您可以使用一个实现 RecurrentEventInterface 的模型。这意味着它将包含标准非重复事件以及重复事件的所有功能,尽管这不是最佳实践,因为与重复事件相关的方 法对于非重复事件将是冗余的。如果您决定采取此路径(不推荐),您的 EventRegistry 可以在两种方法中都处理相同的模型,但只返回适用的事件。

事件注册表

一旦完全设置好模型,您需要创建一个 EventRegistry 类,该类应实现 EventRegistryInterface

它应包含两个方法;一个用于检索重复事件(遵循 RecurrentEventInterface),另一个用于检索标准非重复事件(遵循 EventInterface)。Calendarful 对这两种不同类型执行不同的操作。

这是使用 Laravel 的 Eloquent ORM 的示例事件注册表。

<?php

use \Plummer\Calendarful\Event\EventRegistryInterface;

class EventRegistry implements EventRegistryInterface
{
	public function getEvents(array $filters = array())
	{
		$events = [];

		$results = \TestEvent::where('startDate', '<=', $filters['toDate']->format('Y-m-d'))
					->where('endDate', '>=', $filters['fromDate']->format('Y-m-d'))
					->whereNull('type')->get();

		foreach($results as $event) {
			$events[$event->getId()] = $event;
		}

		return $events;
	}

	public function getRecurrentEvents(array $filters = array())
	{
		$recurrentEvents = [];

		$results = \TestRecurrentEvent::whereNotNull('type')
				->where(
					function ($query) use ($filters) {
						$query->whereNull('until')
							->where('until', '>=', $filters['fromDate']->format('Y-m-d'), 'or');
					})->get();

		foreach($results as $event) {
			$recurrentEvents[$event->getId()] = $event;
		}

		return $recurrentEvents;
	}
}

当您使用事件填充默认的 Calendar 类时,您传递的参数将作为可以见到的上述 $filters 传递给 EventRegistry。这些传递的 $filters 允许 EventRegistry 在早期过程中使用相关事件,从而优化性能。

上面的 EventRegistry 使用日期过滤器来确定哪些事件落在给定的日期范围内。如果没有提供过滤器并返回所有事件,则 Calendar 类将确定哪些事件是相关的。

将过滤器传递给事件注册表的唯一原因是通过在早期过程中使用相关事件来优化性能。

用法

在设置好模型和注册表后,您只需要实例化 EventRegistryCalendar 并填充 Calendar

populate 方法接收 EventRegistryCalendar 应覆盖的日期范围(一个 'from' 和 'to' 日期)以及如果有事件数量最大限制则需提供的限制。

$eventsRegistry = new EventRegistry();

$calendar = Plummer\Calendarful\Calendar\Calendar::create()
			->populate($eventsRegistry, new \DateTime('2014-04-01'), new \DateTime('2014-04-30'));

由于日历实现了 CalendarInterface,该接口反过来扩展了 IteratorAggregate,因此它们是可遍历的。默认的 Calendar 使用 ArrayIterator,这意味着我们可以像这样访问事件

foreach($calendar as $event) {
    // Use event as necessary... 
}

周期性事件

为了识别周期性事件并为它们生成发生次数,一个 RecurrenceFactory 被引入到上述过程中。

$eventsRegistry = new EventRegistry();

$recurrenceFactory = new \Plummer\Calendarful\Recurrence\RecurrenceFactory();
$recurrenceFactory->addRecurrenceType('daily', 'Plummer\Calendarful\Recurrence\Type\Daily');
$recurrenceFactory->addRecurrenceType('weekly', 'Plummer\Calendarful\Recurrence\Type\Weekly');
$recurrenceFactory->addRecurrenceType('monthly', 'Plummer\Calendarful\Recurrence\Type\MonthlyDate');

$calendar = Plummer\Calendarful\Calendar\Calendar::create($recurrenceFactory)
			->populate($eventsRegistry, new \DateTime('2014-04-01'), new \DateTime('2014-04-30'));

我们可以看到,三个默认的包周期类型被注入到 RecurrenceFactory 中,并传递给 Calendar

为了生成发生次数,周期性事件的 getRecurrenceType() 返回值应与添加到 RecurrenceFactory 的 Recurrence 类型第一个参数值相匹配,例如上面的 'daily'、'weekly' 或 'monthly'。

当生成发生次数时,它们将是其父级的一个副本,除了更新它们的日期和与周期性相关的属性外。

覆盖发生次数

如果您使用此包的周期性功能,您可能会想要在某个时刻覆盖发生次数。

例如,您可能有一个每周重复的事件,该事件每周一运行,但您可能希望下周的发生次数在周二运行,然后再继续在周一运行。这就是发生次数覆盖出现的地方。

当您想要覆盖发生次数时,您需要创建一个新的 Event 并将其保存到您选择的存储方法中。为了使包能够识别覆盖,您需要更新由 getParentId()getOccurrenceDate 返回的 Event 模型上的那些属性值。

假设您的 Event 模型具有名为 parentIdoccurrenceDate 的属性,以及上述 EventInterface 方法中提到的那些。

要将下周一的发生次数覆盖为周二,您需要将 parentId 设置为每周一重复的父事件的 Id 值,并将 occurrenceDate 设置为发生次数原本会出现的日期,即周一。同时还需要更新 startDate 到周二的日期。一旦保存了新的事件,下周的周一发生次数将在 Calendar 中被覆盖。

如果父事件的开始日期发生变化,所有覆盖该事件发生次数的日期都需要按照相同的时间差进行更改,以确保覆盖仍然有效。

添加自己的周期类型

要添加自己的周期类型,您只需要创建一个新的类,该类实现了 RecurrenceInterface 及其方法。

然后,可以像上面展示的那样将新的周期类型添加到 RecurrenceFactory 中。

$recurrenceFactory = new \Plummer\Calendarful\Recurrence\RecurrenceFactory();
$recurrenceFactory->addRecurrenceType('ThisShouldMatchAnEventRecurrenceTypePropertyValue', 'Another\RecurrenceType\ClassPath');

不同类型的日历

只要它们实现了 CalendarInterface,此包支持不同类型的日历。

您可能需要同时使用多个日历,在这种情况下,您可以使用 CalendarFactory。您可以像使用 RecurrenceFactory 一样将日历添加到工厂中。

$calendarFactory = new \Plummer\Calendarful\Calendar\CalendarFactory();
$calendarFactory->addCalendarType('gregorian', 'Plummer\Calendarful\Calendar\Calendar');
$calendarFactory->addCalendarType('anotherType', 'Another\CalendarType\ClassPath');

接下来,您可以检索所需的日历类型。

$calendar = $calendarFactory->createCalendar('gregorian');

或者检索所有日历类型进行循环等。

foreach($calendarFactory->getCalendarTypes() as $type => $calendar) {
    // Use calendar...
}

扩展包

该包中的每个组件都有接口,因此如果默认实现不符合您的需求或您希望它们以略微不同的方式工作,构建自己的实现非常简单。这可能是针对一个组件或所有组件。

如果您使用了自己的组件,我强烈建议您查看现有默认组件的功能,因为您可能希望使用部分功能,例如确保发生覆盖等仍然有效。

测试

可以在包内运行单元测试

$ ./vendor/bin/phpunit

贡献

有关详细信息,请参阅CONTRIBUTING

许可协议

plummer/calendarful 采用MIT许可协议。有关更多详细信息,请参阅LICENSE文件。