RichWeber
10 years ago
65 changed files with 1249 additions and 81 deletions
@ -0,0 +1,11 @@ |
|||||||
|
include: |
||||||
|
- common |
||||||
|
- console |
||||||
|
- backend |
||||||
|
- frontend |
||||||
|
|
||||||
|
paths: |
||||||
|
log: tests/_log |
||||||
|
|
||||||
|
settings: |
||||||
|
colors: true |
@ -0,0 +1,124 @@ |
|||||||
|
Аутентификация |
||||||
|
============== |
||||||
|
|
||||||
|
В отличие от Web-приложений, RESTful API обычно не сохраняют информацию о состоянии, а это означает, что сессии и куки |
||||||
|
использовать не следует. Следовательно, раз состояние аутентификации пользователя не может быть сохранено в сессиях или куках, |
||||||
|
каждый запрос должен приходить вместе с определенным видом параметров аутентификации. Общепринятая практика состоит в том, |
||||||
|
что для аутентификации пользователя с каждый запросом отправляется секретный токен доступа. Так как токен доступа |
||||||
|
может использоваться для уникальной идентификации и аутентификации пользователя, **запросы к API всегда должны отсылаться |
||||||
|
через протокол HTTPS, чтобы предотвратить атаки «человек посередине» (англ. "man-in-the-middle", MitM)**. |
||||||
|
|
||||||
|
Есть различные способы отправки токена доступа: |
||||||
|
|
||||||
|
* [HTTP Basic Auth](http://en.wikipedia.org/wiki/Basic_access_authentication): токен доступа |
||||||
|
отправляется как имя пользователя. Такой подход следует использовать только в том случае, когда токен доступа может быть безопасно сохранен |
||||||
|
на стороне абонента API. Например, если API используется программой, запущенной на сервере. |
||||||
|
* Параметр запроса: токен доступа отправляется как параметр запроса в URL-адресе API, т.е. примерно таким образом: |
||||||
|
`https://example.com/users?access-token=xxxxxxxx`. Так как большинство Web-серверов сохраняют параметры запроса в своих логах, |
||||||
|
такой подход следует применять только при работе с `JSONP`-запросами, которые не могут отправлять токены доступа |
||||||
|
в HTTP-заголовках. |
||||||
|
* [OAuth 2](http://oauth.net/2/): токен доступа выдается абоненту API сервером авторизации |
||||||
|
и отправляется API-серверу через [HTTP Bearer Tokens](http://tools.ietf.org/html/rfc6750), |
||||||
|
в соответствии с протоколом OAuth2. |
||||||
|
|
||||||
|
Yii поддерживает все выше перечисленные методы аутентификации. Вы также можете легко создавать новые методы аутентификации. |
||||||
|
|
||||||
|
Чтобы включить аутентификацию для ваших API, выполните следующие шаги: |
||||||
|
|
||||||
|
1. У компонента приложения `user` установите свойство [[yii\web\User::enableSession|enableSession]] равным false. |
||||||
|
2. Укажите, какие методы аутентификации вы планируете использовать, настроив поведение `authenticator` |
||||||
|
в ваших классах REST-контроллеров. |
||||||
|
3. Реализуйте метод [[yii\web\IdentityInterface::findIdentityByAccessToken()]] *в вашем [[yii\web\User::identityClass|классе UserIdentity]]*. |
||||||
|
|
||||||
|
Шаг 1 не обязателен, но рекомендуется его все-таки выполнить, так как RESTful API не должны сохранять информацию о состоянии клиента. Когда свойство [[yii\web\User::enableSession|enableSession]] |
||||||
|
установлено в false, состояние аутентификации пользователя НЕ БУДЕТ постоянно |
||||||
|
сохраняться между запросами с использованием сессий. Вместо этого аутентификация будет выполняться для каждого запроса, что достигается шагами 2 и 3. |
||||||
|
|
||||||
|
> Подсказка: если вы разрабатываете RESTful API в пределах приложения, вы можете настроить свойство |
||||||
|
[[yii\web\User::enableSession|enableSession]] компонента приложения `user` в конфигурации приложения. Если вы разрабатываете |
||||||
|
RESTful API как модуль, можете добавить следующую строчку в метод `init()` модуля: |
||||||
|
> ```php |
||||||
|
public function init() |
||||||
|
{ |
||||||
|
parent::init(); |
||||||
|
\Yii::$app->user->enableSession = false; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Например, для использования HTTP Basic Auth, вы можете настроить свойство `authenticator` следующим образом: |
||||||
|
|
||||||
|
```php |
||||||
|
use yii\filters\auth\HttpBasicAuth; |
||||||
|
|
||||||
|
public function behaviors() |
||||||
|
{ |
||||||
|
$behaviors = parent::behaviors(); |
||||||
|
$behaviors['authenticator'] = [ |
||||||
|
'class' => HttpBasicAuth::className(), |
||||||
|
]; |
||||||
|
return $behaviors; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Если вы хотите включить поддержку всех трех описанных выше методов аутентификации, можете использовать `CompositeAuth`: |
||||||
|
|
||||||
|
```php |
||||||
|
use yii\filters\auth\CompositeAuth; |
||||||
|
use yii\filters\auth\HttpBasicAuth; |
||||||
|
use yii\filters\auth\HttpBearerAuth; |
||||||
|
use yii\filters\auth\QueryParamAuth; |
||||||
|
|
||||||
|
public function behaviors() |
||||||
|
{ |
||||||
|
$behaviors = parent::behaviors(); |
||||||
|
$behaviors['authenticator'] = [ |
||||||
|
'class' => CompositeAuth::className(), |
||||||
|
'authMethods' => [ |
||||||
|
HttpBasicAuth::className(), |
||||||
|
HttpBearerAuth::className(), |
||||||
|
QueryParamAuth::className(), |
||||||
|
], |
||||||
|
]; |
||||||
|
return $behaviors; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Каждый элемент в массиве `authMethods` должен быть названием класса метода аутентификации или массивом настроек. |
||||||
|
|
||||||
|
|
||||||
|
Реализация метода `findIdentityByAccessToken()` определяется особенностями приложения. Например, в простом варианте, |
||||||
|
когда у каждого пользователя есть только один токен доступа, вы можете хранить этот токен в поле `access_token` |
||||||
|
таблицы пользователей. В этом случае метод `findIdentityByAccessToken()` может быть легко реализован в классе `User` следующим образом: |
||||||
|
|
||||||
|
```php |
||||||
|
use yii\db\ActiveRecord; |
||||||
|
use yii\web\IdentityInterface; |
||||||
|
|
||||||
|
class User extends ActiveRecord implements IdentityInterface |
||||||
|
{ |
||||||
|
public static function findIdentityByAccessToken($token, $type = null) |
||||||
|
{ |
||||||
|
return static::findOne(['access_token' => $token]); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
После включения аутентификации описанным выше способом при каждом запросе к API запрашиваемый контроллер |
||||||
|
будет пытаться аутентифицировать пользователя в своем методе `beforeAction()`. |
||||||
|
|
||||||
|
Если аутентификация прошла успешно, контроллер выполнит другие проверки (ограничение на количество запросов, авторизация) |
||||||
|
и затем выполнит действие. *Информация о подлинности аутентифицированного пользователя может быть получена из объекта `Yii::$app->user->identity`*. |
||||||
|
|
||||||
|
Если аутентификация прошла неудачно, будет возвращен ответ с HTTP-кодом состояния 401 вместе с другими необходимыми заголовками |
||||||
|
(такими, как заголовок `WWW-Authenticate` для HTTP Basic Auth). |
||||||
|
|
||||||
|
|
||||||
|
## Авторизация <a name="authorization"></a> |
||||||
|
|
||||||
|
После аутентификации пользователя вы, вероятно, захотите проверить, есть ли у него или у нее разрешение на выполнение запрошенного |
||||||
|
действия с запрошенным ресурсом. Этот процесс называется *авторизацией* и подробно описан |
||||||
|
в разделе [Авторизация](security-authorization.md). |
||||||
|
|
||||||
|
Если ваши контроллеры унаследованы от [[yii\rest\ActiveController]], вы можете переопределить |
||||||
|
метод [[yii\rest\Controller::checkAccess()|checkAccess()]] для выполнения авторизации. Этот метод будет вызываться |
||||||
|
встроенными действиями, предоставляемыми контроллером [[yii\rest\ActiveController]]. |
@ -0,0 +1,152 @@ |
|||||||
|
Контроллеры |
||||||
|
=========== |
||||||
|
|
||||||
|
После создания классов ресурсов и настройки способа форматирования ресурсных данных следующим шагом |
||||||
|
является создание действий контроллеров для предоставления ресурсов конечным пользователям через RESTful API. |
||||||
|
|
||||||
|
В Yii есть два базовых класса контроллеров для упрощения вашей работы по созданию RESTful-действий: |
||||||
|
[[yii\rest\Controller]] и [[yii\rest\ActiveController]]. Разница между этими двумя контроллерами в том, |
||||||
|
что у последнего есть набор действий по умолчанию, который специально создан для работы с ресурсами, |
||||||
|
представленными [Active Record](db-active-record.md). Так что если вы используете [Active Record](db-active-record.md) |
||||||
|
и вас устраивает предоставленный набор встроенных действий, вы можете унаследовать классы ваших контроллеров |
||||||
|
от [[yii\rest\ActiveController]], что позволит вам создать полноценные RESTful API, написав минимум кода. |
||||||
|
|
||||||
|
[[yii\rest\Controller]] и [[yii\rest\ActiveController]] имеют следующие возможности, некоторые из которых |
||||||
|
будут подробно описаны в следующих нескольких разделах: |
||||||
|
|
||||||
|
* Проверка HTTP-метода; |
||||||
|
* [Согласование содержимого и форматирование данных](rest-response-formatting.md); |
||||||
|
* [Аутентификация](rest-authentication.md); |
||||||
|
* [Ограничение частоты запросов](rest-rate-limiting.md). |
||||||
|
|
||||||
|
[[yii\rest\ActiveController]], кроме того, предоставляет следующие возможности: |
||||||
|
|
||||||
|
* Набор наиболее употребительных действий: `index`, `view`, `create`, `update`, `delete`, `options`; |
||||||
|
* Авторизация пользователя для запрашиваемых действия и ресурса. |
||||||
|
|
||||||
|
|
||||||
|
## Создание классов контроллеров <a name="creating-controller"></a> |
||||||
|
|
||||||
|
При создании нового класса контроллера в имени класса обычно используется |
||||||
|
название типа ресурса в единственном числе. Например, контроллер, отвечающий за предоставление информации о пользователях, |
||||||
|
можно назвать `UserController`. |
||||||
|
|
||||||
|
Создание нового действия похоже на создание действия для Web-приложения. Единственное отличие в том, |
||||||
|
что в RESTful-действиях вместо рендера результата в представлении с помощью вызова метода `render()` |
||||||
|
вы просто возвращает данные. Выполнение преобразования исходных данных в запрошенный формат ложится на |
||||||
|
[[yii\rest\Controller::serializer|сериализатор]] и [[yii\web\Response|объект ответа]]. |
||||||
|
Например: |
||||||
|
|
||||||
|
```php |
||||||
|
public function actionView($id) |
||||||
|
{ |
||||||
|
return User::findOne($id); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
## Фильтры <a name="filters"></a> |
||||||
|
|
||||||
|
Большинство возможностей RESTful API, предоставляемых [[yii\rest\Controller]], реализовано на основе [фильтров](structure-filters.md). |
||||||
|
В частности, следующие фильтры будут выполняться в том порядке, в котором они перечислены: |
||||||
|
|
||||||
|
* [[yii\filters\ContentNegotiator|contentNegotiator]]: обеспечивает согласование содержимого, более подробно описан |
||||||
|
в разделе [Форматирование ответа](rest-response-formatting.md); |
||||||
|
* [[yii\filters\VerbFilter|verbFilter]]: обеспечивает проверку HTTP-метода; |
||||||
|
* [[yii\filters\AuthMethod|authenticator]]: обеспечивает аутентификацию пользователя, более подробно описан |
||||||
|
в разделе [Аутентификация](rest-authentication.md); |
||||||
|
* [[yii\filters\RateLimiter|rateLimiter]]: обеспечивает ограничение частоты запросов, более подробно описан |
||||||
|
в разделе [Ограничение частоты запросов](rest-rate-limiting.md). |
||||||
|
|
||||||
|
Эти именованные фильтры объявлены в методе [[yii\rest\Controller::behaviors()|behaviors()]]. |
||||||
|
Вы можете переопределить этот метод для настройки отдельных фильтров, отключения каких-то из них или для добавления ваших собственных фильтров. |
||||||
|
Например, если вы хотите использовать только базовую HTTP-аутентификацию, вы можете написать такой код: |
||||||
|
|
||||||
|
```php |
||||||
|
use yii\filters\auth\HttpBasicAuth; |
||||||
|
|
||||||
|
public function behaviors() |
||||||
|
{ |
||||||
|
$behaviors = parent::behaviors(); |
||||||
|
$behaviors['authenticator'] = [ |
||||||
|
'class' => HttpBasicAuth::className(), |
||||||
|
]; |
||||||
|
return $behaviors; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
## Наследование от `ActiveController` <a name="extending-active-controller"></a> |
||||||
|
|
||||||
|
Если ваш класс контроллера наследуется от [[yii\rest\ActiveController]], вам следует установить |
||||||
|
значение его свойства [[yii\rest\ActiveController::modelClass||modelClass]] равным имени класса ресурса, |
||||||
|
который вы планируете обслуживать с помощью этого контроллера. Класс ресурса должен быть унаследован от [[yii\db\ActiveRecord]]. |
||||||
|
|
||||||
|
|
||||||
|
### Настройка действий <a name="customizing-actions"></a> |
||||||
|
|
||||||
|
По умолчанию [[yii\rest\ActiveController]] предоставляет набор из следующих действий: |
||||||
|
|
||||||
|
* [[yii\rest\IndexAction|index]]: постраничный список ресурсов; |
||||||
|
* [[yii\rest\ViewAction|view]]: возвращает подробную информацию об указанном ресурсе; |
||||||
|
* [[yii\rest\CreateAction|create]]: создание нового ресурса; |
||||||
|
* [[yii\rest\UpdateAction|update]]: обновление существующего ресурса; |
||||||
|
* [[yii\rest\DeleteAction|delete]]: удаление указанного ресурса; |
||||||
|
* [[yii\rest\OptionsAction|options]]: возвращает поддерживаемые HTTP-методы. |
||||||
|
|
||||||
|
Все эти действия объявляются в методе [[yii\rest\ActiveController::actions()|actions()]]. |
||||||
|
Вы можете настроить эти действия или отключить какие-то из них, переопределив метод `actions()`, как показано ниже: |
||||||
|
|
||||||
|
```php |
||||||
|
public function actions() |
||||||
|
{ |
||||||
|
$actions = parent::actions(); |
||||||
|
|
||||||
|
// отключить действия "delete" и "create" |
||||||
|
unset($actions['delete'], $actions['create']); |
||||||
|
|
||||||
|
// настроить подготовку провайдера данных с помощью метода "prepareDataProvider()" |
||||||
|
$actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider']; |
||||||
|
|
||||||
|
return $actions; |
||||||
|
} |
||||||
|
|
||||||
|
public function prepareDataProvider() |
||||||
|
{ |
||||||
|
// подготовить и вернуть провайдер данных для действия "index" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Чтобы узнать, какие опции доступны для настройки классов отдельных действий, обратитесь к соответствующим разделам справочника классов. |
||||||
|
|
||||||
|
|
||||||
|
### Выполнение контроля доступа <a name="performing-access-check"></a> |
||||||
|
|
||||||
|
При предоставлении ресурсов через RESTful API часто бывает нужно проверять, имеет ли текущий пользователь разрешение |
||||||
|
на доступ к запрошенному ресурсу (или ресурсам) и манипуляцию им (ими). Для [[yii\rest\ActiveController]] эта задача может быть решена |
||||||
|
переопределением метода [[yii\rest\ActiveController::checkAccess()|checkAccess()]] следующим образом: |
||||||
|
|
||||||
|
```php |
||||||
|
/** |
||||||
|
* Проверяет права текущего пользователя. |
||||||
|
* |
||||||
|
* Этот метод должен быть переопределен, чтобы проверить, имеет ли текущий пользователь |
||||||
|
* право выполнения указанного действия над указанной моделью данных. |
||||||
|
* Если у пользователя нет доступа, следует выбросить исключение [[ForbiddenHttpException]]. |
||||||
|
* |
||||||
|
* @param string $action ID действия, которое надо выполнить |
||||||
|
* @param \yii\base\Model $model модель, к которой нужно получить доступ. Если null, это означает, что модель, к которой нужно получить доступ, отсутствует. |
||||||
|
* @param array $params дополнительные параметры |
||||||
|
* @throws ForbiddenHttpException если у пользователя нет доступа |
||||||
|
*/ |
||||||
|
public function checkAccess($action, $model = null, $params = []) |
||||||
|
{ |
||||||
|
// проверить, имеет ли пользователь доступ к $action и $model |
||||||
|
// выбросить ForbiddenHttpException, если доступ следует запретить |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Метод `checkAccess()` будет вызван действиями по умолчанию контроллера [[yii\rest\ActiveController]]. Если вы создаете |
||||||
|
новые действия и хотите в них выполнять контроль доступа, вы должны вызвать этот метод явно в своих новых действиях. |
||||||
|
|
||||||
|
> Подсказка: вы можете реализовать метод `checkAccess()` с помощью ["Контроля доступа на основе ролей" (RBAC)](security-authorization.md). |
@ -0,0 +1,78 @@ |
|||||||
|
Маршрутизация |
||||||
|
============= |
||||||
|
|
||||||
|
Имея готовые классы ресурсов и контроллеров, можно получить доступ к ресурсам, используя URL вроде |
||||||
|
`http://localhost/index.php?r=user/create`, подобно тому, как вы это делаете с обычными Web-приложениями. |
||||||
|
|
||||||
|
На деле вам обычно хочется включить «красивые» URL-адреса и использовать все преимущества HTTP-методов (HTTP-verbs). |
||||||
|
Например, чтобы запрос `POST /users` означал обращение к действию `user/create`. |
||||||
|
Это может быть легко сделано с помощью настройки компонента приложения `urlManager` в |
||||||
|
конфигурации приложения следующим образом: |
||||||
|
|
||||||
|
```php |
||||||
|
'urlManager' => [ |
||||||
|
'enablePrettyUrl' => true, |
||||||
|
'enableStrictParsing' => true, |
||||||
|
'showScriptName' => false, |
||||||
|
'rules' => [ |
||||||
|
['class' => 'yii\rest\UrlRule', 'controller' => 'user'], |
||||||
|
], |
||||||
|
] |
||||||
|
``` |
||||||
|
|
||||||
|
Главная новинка в коде выше по сравнению с управлением URL-адресами в Web-приложениях состоит в использовании |
||||||
|
[[yii\rest\UrlRule]] для маршрутизации запросов к RESTful API. Этот особый класс URL-правил будет |
||||||
|
создавать целый набор дочерних URL-правил для поддержки маршрутизации и создания URL-адресов для указанного контроллера (или контроллеров). |
||||||
|
Например, приведенный выше код является приближенным аналогом следующего набора правил: |
||||||
|
|
||||||
|
```php |
||||||
|
[ |
||||||
|
'PUT,PATCH users/<id>' => 'user/update', |
||||||
|
'DELETE users/<id>' => 'user/delete', |
||||||
|
'GET,HEAD users/<id>' => 'user/view', |
||||||
|
'POST users' => 'user/create', |
||||||
|
'GET,HEAD users' => 'user/index', |
||||||
|
'users/<id>' => 'user/options', |
||||||
|
'users' => 'user/options', |
||||||
|
] |
||||||
|
``` |
||||||
|
|
||||||
|
Этим правилом поддерживаются следующие точки входа в API: |
||||||
|
|
||||||
|
* `GET /users`: разбитый на страницы список всех пользователей; |
||||||
|
* `HEAD /users`: общая информация по списку пользователей; |
||||||
|
* `POST /users`: создание нового пользователя; |
||||||
|
* `GET /users/123`: подробная информация о пользователе 123; |
||||||
|
* `HEAD /users/123`: общая информация о пользователе 123; |
||||||
|
* `PATCH /users/123` и `PUT /users/123`: обновление пользователя 123; |
||||||
|
* `DELETE /users/123`: удаление пользователя 123; |
||||||
|
* `OPTIONS /users`: список HTTP-методов, поддерживаемые точкой входа `/users`; |
||||||
|
* `OPTIONS /users/123`: список HTTP-методов, поддерживаемые точкой входа `/users/123`. |
||||||
|
|
||||||
|
Вы можете настроить опции `only` и `except`, явно указав для них список действий, которые поддерживаются или |
||||||
|
которые должны быть отключены, соответственно. Например: |
||||||
|
|
||||||
|
```php |
||||||
|
[ |
||||||
|
'class' => 'yii\rest\UrlRule', |
||||||
|
'controller' => 'user', |
||||||
|
'except' => ['delete', 'create', 'update'], |
||||||
|
], |
||||||
|
``` |
||||||
|
|
||||||
|
Вы также можете настроить опции `patterns` или `extraPatterns` для переопределения существующих шаблонов или добавления новых шаблонов, поддерживаемых этим правилом. |
||||||
|
Например, для включения нового действия `search` в точке входа `GET /users/search` настройте опцию `extraPatterns` следующим образом: |
||||||
|
|
||||||
|
```php |
||||||
|
[ |
||||||
|
'class' => 'yii\rest\UrlRule', |
||||||
|
'controller' => 'user', |
||||||
|
'extraPatterns' => [ |
||||||
|
'GET search' => 'search', |
||||||
|
], |
||||||
|
``` |
||||||
|
|
||||||
|
Как вы могли заметить, ID контроллера `user` в этих точках входа используется в форме множественного числа (как `users`). |
||||||
|
Это происходит потому, что [[yii\rest\UrlRule]] автоматически приводит идентификаторы контроллеров к множественной форме для использования в точках входа. |
||||||
|
Вы можете отключить такое поведение, назначив свойству [[yii\rest\UrlRule::pluralize]] значение false, или, если вы хотите использовать |
||||||
|
какие-то особые имена, вы можете настроить свойство [[yii\rest\UrlRule::controller]]. |
@ -0,0 +1,86 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yii\caching; |
||||||
|
|
||||||
|
/** |
||||||
|
* ArrayCache provides caching for the current request only by storing the values in an array. |
||||||
|
* |
||||||
|
* See [[Cache]] for common cache operations that ArrayCache supports. |
||||||
|
* |
||||||
|
* Unlike the [[Cache]], ArrayCache allows the expire parameter of [[set]], [[add]], [[mset]] and [[madd]] to |
||||||
|
* be a floating point number, so you may specify the time in milliseconds (e.g. 0.1 will be 100 milliseconds). |
||||||
|
* |
||||||
|
* @author Carsten Brandt <mail@cebe.cc> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class ArrayCache extends Cache |
||||||
|
{ |
||||||
|
private $_cache; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* @inheritdoc |
||||||
|
*/ |
||||||
|
public function exists($key) |
||||||
|
{ |
||||||
|
$key = $this->buildKey($key); |
||||||
|
return isset($this->_cache[$key]) && ($this->_cache[$key][1] === 0 || $this->_cache[$key][1] > microtime(true)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @inheritdoc |
||||||
|
*/ |
||||||
|
protected function getValue($key) |
||||||
|
{ |
||||||
|
if (isset($this->_cache[$key]) && ($this->_cache[$key][1] === 0 || $this->_cache[$key][1] > microtime(true))) { |
||||||
|
return $this->_cache[$key][0]; |
||||||
|
} else { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @inheritdoc |
||||||
|
*/ |
||||||
|
protected function setValue($key, $value, $duration) |
||||||
|
{ |
||||||
|
$this->_cache[$key] = [$value, $duration === 0 ? 0 : microtime(true) + $duration]; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @inheritdoc |
||||||
|
*/ |
||||||
|
protected function addValue($key, $value, $duration) |
||||||
|
{ |
||||||
|
if (isset($this->_cache[$key]) && ($this->_cache[$key][1] === 0 || $this->_cache[$key][1] > microtime(true))) { |
||||||
|
return false; |
||||||
|
} else { |
||||||
|
$this->_cache[$key] = [$value, $duration === 0 ? 0 : microtime(true) + $duration]; |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @inheritdoc |
||||||
|
*/ |
||||||
|
protected function deleteValue($key) |
||||||
|
{ |
||||||
|
unset($this->_cache[$key]); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @inheritdoc |
||||||
|
*/ |
||||||
|
protected function flushValues() |
||||||
|
{ |
||||||
|
$this->_cache = []; |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
@ -1 +1,2 @@ |
|||||||
runtime/cache/* |
/runtime/cache/* |
||||||
|
/data/config.local.php |
@ -0,0 +1,193 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\framework\behaviors; |
||||||
|
|
||||||
|
use Yii; |
||||||
|
use yiiunit\TestCase; |
||||||
|
use yii\db\Connection; |
||||||
|
use yii\db\ActiveRecord; |
||||||
|
use yii\behaviors\SluggableBehavior; |
||||||
|
|
||||||
|
/** |
||||||
|
* Unit test for [[\yii\behaviors\SluggableBehavior]]. |
||||||
|
* @see SluggableBehavior |
||||||
|
* |
||||||
|
* @group behaviors |
||||||
|
*/ |
||||||
|
class SluggableBehaviorTest extends TestCase |
||||||
|
{ |
||||||
|
/** |
||||||
|
* @var Connection test db connection |
||||||
|
*/ |
||||||
|
protected $dbConnection; |
||||||
|
|
||||||
|
public static function setUpBeforeClass() |
||||||
|
{ |
||||||
|
if (!extension_loaded('pdo') || !extension_loaded('pdo_sqlite')) { |
||||||
|
static::markTestSkipped('PDO and SQLite extensions are required.'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function setUp() |
||||||
|
{ |
||||||
|
$this->mockApplication([ |
||||||
|
'components' => [ |
||||||
|
'db' => [ |
||||||
|
'class' => '\yii\db\Connection', |
||||||
|
'dsn' => 'sqlite::memory:', |
||||||
|
] |
||||||
|
] |
||||||
|
]); |
||||||
|
|
||||||
|
$columns = [ |
||||||
|
'id' => 'pk', |
||||||
|
'name' => 'string', |
||||||
|
'slug' => 'string', |
||||||
|
'category_id' => 'integer', |
||||||
|
]; |
||||||
|
Yii::$app->getDb()->createCommand()->createTable('test_slug', $columns)->execute(); |
||||||
|
} |
||||||
|
|
||||||
|
public function tearDown() |
||||||
|
{ |
||||||
|
Yii::$app->getDb()->close(); |
||||||
|
parent::tearDown(); |
||||||
|
} |
||||||
|
|
||||||
|
// Tests : |
||||||
|
|
||||||
|
public function testSlug() |
||||||
|
{ |
||||||
|
$model = new ActiveRecordSluggable(); |
||||||
|
$model->name = 'test name'; |
||||||
|
$model->validate(); |
||||||
|
|
||||||
|
$this->assertEquals('test-name', $model->slug); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @depends testSlug |
||||||
|
*/ |
||||||
|
public function testSlugSeveralAttributes() |
||||||
|
{ |
||||||
|
$model = new ActiveRecordSluggable(); |
||||||
|
$model->getBehavior('sluggable')->attribute = array('name', 'category_id'); |
||||||
|
|
||||||
|
$model->name = 'test'; |
||||||
|
$model->category_id = 10; |
||||||
|
|
||||||
|
$model->validate(); |
||||||
|
$this->assertEquals('test-10', $model->slug); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @depends testSlug |
||||||
|
*/ |
||||||
|
public function testUniqueByIncrement() |
||||||
|
{ |
||||||
|
$name = 'test name'; |
||||||
|
|
||||||
|
$model = new ActiveRecordSluggableUnique(); |
||||||
|
$model->name = $name; |
||||||
|
$model->save(); |
||||||
|
|
||||||
|
$model = new ActiveRecordSluggableUnique(); |
||||||
|
$model->sluggable->uniqueSlugGenerator = 'increment'; |
||||||
|
$model->name = $name; |
||||||
|
$model->save(); |
||||||
|
|
||||||
|
$this->assertEquals('test-name-2', $model->slug); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @depends testUniqueByIncrement |
||||||
|
*/ |
||||||
|
public function testUniqueByCallback() |
||||||
|
{ |
||||||
|
$name = 'test name'; |
||||||
|
|
||||||
|
$model = new ActiveRecordSluggableUnique(); |
||||||
|
$model->name = $name; |
||||||
|
$model->save(); |
||||||
|
|
||||||
|
$model = new ActiveRecordSluggableUnique(); |
||||||
|
$model->sluggable->uniqueSlugGenerator = function($baseSlug, $iteration) {return $baseSlug . '-callback';}; |
||||||
|
$model->name = $name; |
||||||
|
$model->save(); |
||||||
|
|
||||||
|
$this->assertEquals('test-name-callback', $model->slug); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @depends testSlug |
||||||
|
*/ |
||||||
|
public function testUpdateUnique() |
||||||
|
{ |
||||||
|
$name = 'test name'; |
||||||
|
|
||||||
|
$model = new ActiveRecordSluggableUnique(); |
||||||
|
$model->name = $name; |
||||||
|
$model->save(); |
||||||
|
|
||||||
|
$model->save(); |
||||||
|
$this->assertEquals('test-name', $model->slug); |
||||||
|
|
||||||
|
$model = ActiveRecordSluggableUnique::find()->one(); |
||||||
|
$model->save(); |
||||||
|
$this->assertEquals('test-name', $model->slug); |
||||||
|
|
||||||
|
$model->name = 'test-name'; |
||||||
|
$model->save(); |
||||||
|
$this->assertEquals('test-name', $model->slug); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Test Active Record class with [[SluggableBehavior]] behavior attached. |
||||||
|
* |
||||||
|
* @property integer $id |
||||||
|
* @property string $name |
||||||
|
* @property string $slug |
||||||
|
* @property integer $category_id |
||||||
|
* |
||||||
|
* @property SluggableBehavior $sluggable |
||||||
|
*/ |
||||||
|
class ActiveRecordSluggable extends ActiveRecord |
||||||
|
{ |
||||||
|
public function behaviors() |
||||||
|
{ |
||||||
|
return [ |
||||||
|
'sluggable' => [ |
||||||
|
'class' => SluggableBehavior::className(), |
||||||
|
'attribute' => 'name', |
||||||
|
], |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
public static function tableName() |
||||||
|
{ |
||||||
|
return 'test_slug'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return SluggableBehavior |
||||||
|
*/ |
||||||
|
public function getSluggable() |
||||||
|
{ |
||||||
|
return $this->getBehavior('sluggable'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class ActiveRecordSluggableUnique extends ActiveRecordSluggable |
||||||
|
{ |
||||||
|
public function behaviors() |
||||||
|
{ |
||||||
|
return [ |
||||||
|
'sluggable' => [ |
||||||
|
'class' => SluggableBehavior::className(), |
||||||
|
'attribute' => 'name', |
||||||
|
'ensureUnique' => true, |
||||||
|
], |
||||||
|
]; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\framework\caching; |
||||||
|
|
||||||
|
use yii\caching\ArrayCache; |
||||||
|
|
||||||
|
/** |
||||||
|
* Class for testing file cache backend |
||||||
|
* @group caching |
||||||
|
*/ |
||||||
|
class ArrayCacheTest extends CacheTestCase |
||||||
|
{ |
||||||
|
private $_cacheInstance = null; |
||||||
|
|
||||||
|
/** |
||||||
|
* @return ArrayCache |
||||||
|
*/ |
||||||
|
protected function getCacheInstance() |
||||||
|
{ |
||||||
|
if ($this->_cacheInstance === null) { |
||||||
|
$this->_cacheInstance = new ArrayCache(); |
||||||
|
} |
||||||
|
return $this->_cacheInstance; |
||||||
|
} |
||||||
|
|
||||||
|
public function testExpire() |
||||||
|
{ |
||||||
|
$cache = $this->getCacheInstance(); |
||||||
|
|
||||||
|
static::$microtime = \microtime(true); |
||||||
|
$this->assertTrue($cache->set('expire_test', 'expire_test', 2)); |
||||||
|
static::$microtime++; |
||||||
|
$this->assertEquals('expire_test', $cache->get('expire_test')); |
||||||
|
static::$microtime++; |
||||||
|
$this->assertFalse($cache->get('expire_test')); |
||||||
|
} |
||||||
|
|
||||||
|
public function testExpireAdd() |
||||||
|
{ |
||||||
|
$cache = $this->getCacheInstance(); |
||||||
|
|
||||||
|
static::$microtime = \microtime(true); |
||||||
|
$this->assertTrue($cache->add('expire_testa', 'expire_testa', 2)); |
||||||
|
static::$microtime++; |
||||||
|
$this->assertEquals('expire_testa', $cache->get('expire_testa')); |
||||||
|
static::$microtime++; |
||||||
|
$this->assertFalse($cache->get('expire_testa')); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue