From 92cd13a913b03675ffec7f2d2e81c0c72abce917 Mon Sep 17 00:00:00 2001 From: RichWeber Date: Fri, 1 Aug 2014 00:32:08 +0300 Subject: [PATCH] pull origin --- .travis.yml | 3 +- README.md | 1 + apps/advanced/README.md | 4 +- apps/advanced/common/models/LoginForm.php | 7 +- apps/advanced/common/models/User.php | 4 +- .../common/tests/templates/fixtures/user.php | 4 +- apps/basic/models/LoginForm.php | 7 +- build/controllers/AppController.php | 2 +- build/controllers/MimeTypeController.php | 5 + build/controllers/PhpDocController.php | 144 +++++- docs/guide-ru/intro-upgrade-from-v1.md | 2 +- docs/guide-zh-CN/README.md | 13 +- docs/guide-zh-CN/caching-data.md | 79 ++- docs/guide-zh-CN/caching-fragment.md | 141 ++++++ docs/guide-zh-CN/caching-http.md | 108 +++++ docs/guide-zh-CN/caching-overview.md | 7 +- docs/guide-zh-CN/caching-page.md | 40 ++ docs/guide-zh-CN/concept-aliases.md | 51 +- docs/guide-zh-CN/concept-autoloading.md | 2 +- docs/guide-zh-CN/concept-behaviors.md | 264 +++++------ docs/guide-zh-CN/concept-di-container.md | 52 +- docs/guide-zh-CN/concept-events.md | 241 ++++++++++ docs/guide-zh-CN/concept-service-locator.md | 25 +- docs/guide-zh-CN/db-dao.md | 2 +- docs/guide-zh-CN/input-validation.md | 527 +++++++++++++++++++++ docs/guide-zh-CN/intro-yii.md | 2 +- docs/guide-zh-CN/start-installation.md | 42 +- docs/guide-zh-CN/tutorial-core-validators.md | 445 +++++++++++++++++ docs/guide/README.md | 6 +- docs/guide/caching-http.md | 2 +- docs/guide/input-validation.md | 106 ++++- docs/guide/intro-upgrade-from-v1.md | 2 +- docs/guide/security-authentication.md | 9 +- docs/guide/security-authorization.md | 2 +- docs/guide/security-passwords.md | 2 +- docs/guide/start-databases.md | 22 +- docs/guide/structure-applications.md | 10 +- docs/guide/structure-controllers.md | 13 +- docs/guide/test-acceptance.md | 11 + docs/guide/test-functional.md | 8 + docs/guide/test-overview.md | 35 +- docs/guide/test-unit.md | 19 + docs/guide/tutorial-core-validators.md | 12 +- docs/guide/tutorial-template-engines.md | 26 + docs/internals/getting-started.md | 61 ++- docs/internals/translation-teams.md | 4 +- extensions/apidoc/commands/ApiController.php | 2 +- extensions/apidoc/commands/GuideController.php | 1 + extensions/apidoc/components/BaseController.php | 1 + extensions/apidoc/helpers/ApiIndexer.php | 7 +- extensions/apidoc/helpers/ApiMarkdown.php | 1 + extensions/apidoc/helpers/ApiMarkdownLaTeX.php | 1 + extensions/apidoc/helpers/IndexFileAnalyzer.php | 1 + extensions/apidoc/models/BaseDoc.php | 5 +- extensions/apidoc/models/ClassDoc.php | 4 +- extensions/apidoc/models/ConstDoc.php | 1 + extensions/apidoc/models/Context.php | 2 +- extensions/apidoc/models/EventDoc.php | 1 + extensions/apidoc/models/FunctionDoc.php | 1 + extensions/apidoc/models/InterfaceDoc.php | 2 +- extensions/apidoc/models/MethodDoc.php | 4 +- extensions/apidoc/models/ParamDoc.php | 2 +- extensions/apidoc/models/PropertyDoc.php | 4 +- extensions/apidoc/models/TraitDoc.php | 2 +- extensions/apidoc/models/TypeDoc.php | 4 +- extensions/apidoc/renderers/BaseRenderer.php | 10 +- .../apidoc/templates/bootstrap/ApiRenderer.php | 1 + .../apidoc/templates/bootstrap/GuideRenderer.php | 1 + .../apidoc/templates/bootstrap/SideNavWidget.php | 1 + extensions/apidoc/templates/html/ApiRenderer.php | 2 + extensions/apidoc/templates/html/GuideRenderer.php | 1 + extensions/apidoc/templates/html/views/seeAlso.php | 8 +- extensions/apidoc/templates/online/ApiRenderer.php | 2 +- extensions/authclient/AuthAction.php | 2 + extensions/authclient/BaseClient.php | 1 + extensions/authclient/BaseOAuth.php | 14 +- extensions/authclient/CHANGELOG.md | 1 + extensions/authclient/Collection.php | 1 + extensions/authclient/InvalidResponseException.php | 1 + extensions/authclient/OAuth1.php | 3 +- extensions/authclient/OAuth2.php | 1 + extensions/authclient/OAuthToken.php | 2 + extensions/authclient/OpenId.php | 10 +- extensions/authclient/clients/Facebook.php | 1 + extensions/authclient/clients/GitHub.php | 1 + extensions/authclient/clients/GoogleOAuth.php | 1 + extensions/authclient/clients/GoogleOpenId.php | 1 + extensions/authclient/clients/LinkedIn.php | 1 + extensions/authclient/clients/Live.php | 1 + extensions/authclient/clients/Twitter.php | 1 + extensions/authclient/clients/VKontakte.php | 2 +- extensions/authclient/clients/YandexOAuth.php | 1 + extensions/authclient/clients/YandexOpenId.php | 1 + extensions/authclient/signature/RsaSha1.php | 2 + extensions/authclient/widgets/AuthChoice.php | 1 + extensions/authclient/widgets/assets/authchoice.js | 2 +- extensions/bootstrap/ActiveField.php | 12 +- extensions/bootstrap/ActiveForm.php | 2 +- extensions/bootstrap/Alert.php | 1 + extensions/bootstrap/Button.php | 1 + extensions/bootstrap/ButtonDropdown.php | 1 + extensions/bootstrap/ButtonGroup.php | 1 + extensions/bootstrap/Carousel.php | 1 + extensions/bootstrap/Collapse.php | 1 + extensions/bootstrap/Dropdown.php | 3 +- extensions/bootstrap/Modal.php | 1 + extensions/bootstrap/Nav.php | 1 + extensions/bootstrap/NavBar.php | 1 + extensions/bootstrap/Progress.php | 1 + extensions/bootstrap/Tabs.php | 1 + extensions/bootstrap/Widget.php | 1 + extensions/codeception/BasePage.php | 2 + extensions/codeception/TestCase.php | 1 + extensions/composer/Installer.php | 2 +- extensions/debug/LogTarget.php | 1 + extensions/debug/Module.php | 3 +- extensions/debug/Panel.php | 1 + extensions/debug/components/search/Filter.php | 1 + .../debug/components/search/matchers/Base.php | 1 + .../debug/components/search/matchers/SameAs.php | 1 + extensions/debug/controllers/DefaultController.php | 1 + extensions/debug/models/search/Db.php | 2 +- extensions/debug/models/search/Debug.php | 9 +- extensions/debug/models/search/Log.php | 3 +- extensions/debug/models/search/Mail.php | 10 +- extensions/debug/models/search/Profile.php | 2 +- extensions/debug/panels/DbPanel.php | 3 +- extensions/debug/panels/LogPanel.php | 1 + extensions/debug/panels/MailPanel.php | 2 + extensions/debug/panels/ProfilingPanel.php | 1 + extensions/elasticsearch/ActiveRecord.php | 1 + extensions/elasticsearch/Command.php | 2 +- extensions/elasticsearch/Connection.php | 2 +- extensions/elasticsearch/DebugAction.php | 1 + extensions/elasticsearch/DebugPanel.php | 1 + extensions/elasticsearch/Query.php | 1 - extensions/elasticsearch/QueryBuilder.php | 1 + extensions/faker/FixtureController.php | 1 + extensions/faker/README.md | 2 +- extensions/gii/CHANGELOG.md | 1 + extensions/gii/CodeFile.php | 3 +- extensions/gii/Generator.php | 1 + extensions/gii/components/ActiveField.php | 1 + extensions/gii/controllers/DefaultController.php | 1 + extensions/gii/generators/controller/Generator.php | 1 + extensions/gii/generators/crud/Generator.php | 3 +- .../gii/generators/crud/default/views/_form.php | 6 +- extensions/gii/generators/extension/Generator.php | 1 + .../extension/default/AutoloadExample.php | 10 +- extensions/gii/generators/form/Generator.php | 1 + extensions/gii/generators/model/Generator.php | 9 +- extensions/gii/generators/module/Generator.php | 3 +- extensions/imagine/BaseImage.php | 2 + extensions/jui/Accordion.php | 1 + extensions/jui/DatePicker.php | 1 + extensions/jui/InputWidget.php | 1 + extensions/jui/Menu.php | 1 + extensions/jui/Selectable.php | 1 + extensions/jui/Slider.php | 1 + extensions/jui/SliderInput.php | 12 +- extensions/jui/Sortable.php | 1 + extensions/jui/Spinner.php | 1 + extensions/jui/Tabs.php | 1 + extensions/jui/Widget.php | 1 + extensions/mongodb/ActiveFixture.php | 1 + extensions/mongodb/Cache.php | 1 + extensions/mongodb/Collection.php | 7 +- extensions/mongodb/Connection.php | 2 + extensions/mongodb/Database.php | 2 + extensions/mongodb/Migration.php | 1 + extensions/mongodb/Query.php | 1 + .../console/controllers/MigrateController.php | 1 + extensions/mongodb/file/Collection.php | 2 + extensions/mongodb/gii/model/Generator.php | 3 +- extensions/mongodb/log/MongoDbTarget.php | 1 + extensions/redis/ActiveQuery.php | 1 + extensions/redis/Cache.php | 1 + extensions/redis/Connection.php | 1 + extensions/smarty/ViewRenderer.php | 3 +- extensions/sphinx/ActiveRecord.php | 1 + extensions/sphinx/ColumnSchema.php | 1 + extensions/sphinx/Command.php | 1 + extensions/sphinx/Connection.php | 1 + extensions/sphinx/IndexSchema.php | 1 + extensions/sphinx/Query.php | 2 + extensions/sphinx/QueryBuilder.php | 1 + extensions/sphinx/Schema.php | 1 + extensions/sphinx/gii/model/Generator.php | 7 +- extensions/swiftmailer/Mailer.php | 2 + extensions/swiftmailer/Message.php | 1 + extensions/twig/CHANGELOG.md | 4 + extensions/twig/Extension.php | 9 +- extensions/twig/FileLoader.php | 72 --- extensions/twig/ViewRenderer.php | 16 +- extensions/twig/ViewRendererStaticClassProxy.php | 1 + framework/CHANGELOG.md | 24 +- framework/UPGRADE.md | 7 +- framework/assets/yii.activeForm.js | 117 +++-- framework/assets/yii.validation.js | 50 ++ framework/base/Action.php | 1 + framework/base/ActionEvent.php | 1 + framework/base/Application.php | 44 +- framework/base/Behavior.php | 1 + framework/base/Component.php | 17 +- framework/base/Controller.php | 3 + framework/base/DynamicModel.php | 1 + framework/base/Event.php | 1 + framework/base/Formatter.php | 27 +- framework/base/InlineAction.php | 1 + framework/base/Model.php | 3 +- framework/base/Module.php | 17 +- framework/base/Request.php | 1 + framework/base/Response.php | 1 + framework/base/Security.php | 527 +++++++++++++-------- framework/base/Theme.php | 1 + framework/base/Widget.php | 2 +- framework/behaviors/AttributeBehavior.php | 3 +- framework/behaviors/BlameableBehavior.php | 43 +- framework/behaviors/SluggableBehavior.php | 16 + framework/behaviors/TimestampBehavior.php | 45 +- framework/caching/ChainedDependency.php | 1 + framework/caching/DbCache.php | 1 + framework/caching/DbDependency.php | 1 + framework/caching/Dependency.php | 1 + framework/caching/ExpressionDependency.php | 1 + framework/caching/FileCache.php | 1 + framework/caching/FileDependency.php | 1 + framework/caching/MemCache.php | 15 + framework/caching/TagDependency.php | 2 +- framework/captcha/Captcha.php | 1 + framework/captcha/CaptchaAction.php | 2 + framework/captcha/CaptchaValidator.php | 1 + framework/classes.php | 4 +- framework/console/Application.php | 1 + framework/console/Controller.php | 2 +- framework/console/Markdown.php | 19 + framework/console/Request.php | 1 + framework/console/controllers/AssetController.php | 1 + .../console/controllers/BaseMigrateController.php | 7 +- framework/console/controllers/HelpController.php | 2 +- .../console/controllers/MessageController.php | 67 +-- .../console/controllers/MigrateController.php | 1 + framework/data/BaseDataProvider.php | 1 + framework/data/Pagination.php | 2 + framework/data/Sort.php | 1 - framework/data/SqlDataProvider.php | 1 + framework/db/ActiveQuery.php | 20 +- framework/db/ActiveRecord.php | 1 + framework/db/BaseActiveRecord.php | 4 +- framework/db/BatchQueryResult.php | 2 + framework/db/ColumnSchema.php | 1 + framework/db/Connection.php | 9 +- framework/db/DataReader.php | 1 + framework/db/Exception.php | 4 + framework/db/Expression.php | 1 + framework/db/Migration.php | 1 + framework/db/QueryBuilder.php | 17 + framework/db/QueryTrait.php | 6 + framework/db/Schema.php | 15 +- framework/db/TableSchema.php | 1 + framework/db/Transaction.php | 1 + framework/db/cubrid/QueryBuilder.php | 1 + framework/db/cubrid/Schema.php | 2 +- framework/db/mssql/PDO.php | 3 + framework/db/mssql/QueryBuilder.php | 9 +- framework/db/mssql/Schema.php | 7 +- framework/db/mysql/QueryBuilder.php | 1 + framework/db/mysql/Schema.php | 1 + framework/db/oci/QueryBuilder.php | 26 + framework/db/oci/Schema.php | 29 +- framework/db/pgsql/QueryBuilder.php | 1 + framework/db/pgsql/Schema.php | 1 + framework/db/sqlite/Schema.php | 1 + framework/di/Instance.php | 1 + framework/di/ServiceLocator.php | 1 + framework/filters/AccessRule.php | 1 + framework/filters/Cors.php | 2 +- framework/filters/VerbFilter.php | 1 + framework/filters/auth/HttpBearerAuth.php | 1 + framework/filters/auth/QueryParamAuth.php | 1 + framework/grid/CheckboxColumn.php | 3 +- framework/grid/GridView.php | 2 +- framework/grid/SerialColumn.php | 1 + framework/helpers/BaseConsole.php | 2 + framework/helpers/BaseFileHelper.php | 7 +- framework/helpers/BaseHtml.php | 5 +- framework/helpers/BaseInflector.php | 4 +- framework/helpers/BaseMarkdown.php | 1 + framework/helpers/BaseStringHelper.php | 7 +- framework/helpers/BaseVarDumper.php | 3 +- framework/i18n/DbMessageSource.php | 1 + framework/i18n/Formatter.php | 1 + framework/i18n/GettextMessageSource.php | 1 + framework/i18n/GettextMoFile.php | 4 +- framework/i18n/I18N.php | 1 + framework/i18n/MessageFormatter.php | 4 +- framework/i18n/MessageSource.php | 1 + framework/i18n/PhpMessageSource.php | 1 + framework/log/Logger.php | 1 - framework/log/SyslogTarget.php | 1 - framework/log/Target.php | 7 +- framework/mail/BaseMailer.php | 2 + framework/messages/config.php | 2 +- framework/messages/ru/yii.php | 2 +- framework/messages/zh-TW/yii.php | 80 ++++ framework/mutex/DbMutex.php | 1 + framework/mutex/FileMutex.php | 4 +- framework/mutex/Mutex.php | 2 + framework/rbac/BaseManager.php | 1 + framework/rbac/DbManager.php | 19 +- framework/rbac/ManagerInterface.php | 4 +- framework/rbac/PhpManager.php | 20 +- framework/rbac/Rule.php | 1 + .../rbac/migrations/m140506_102106_rbac_init.php | 11 + framework/rbac/migrations/schema-mssql.sql | 13 +- framework/rest/Action.php | 1 + framework/rest/ActiveController.php | 1 + framework/rest/CreateAction.php | 1 + framework/rest/DeleteAction.php | 1 + framework/rest/IndexAction.php | 1 + framework/rest/OptionsAction.php | 1 + framework/rest/Serializer.php | 1 + framework/rest/UpdateAction.php | 1 + framework/rest/UrlRule.php | 1 + framework/test/ActiveFixture.php | 2 + framework/test/BaseActiveFixture.php | 2 + framework/test/DbFixture.php | 1 + framework/test/Fixture.php | 1 + framework/test/FixtureTrait.php | 1 + framework/test/InitDbFixture.php | 1 + framework/validators/BooleanValidator.php | 1 + framework/validators/CompareValidator.php | 1 + framework/validators/DateValidator.php | 1 + framework/validators/DefaultValueValidator.php | 1 + framework/validators/EmailValidator.php | 1 + framework/validators/ExistValidator.php | 1 + framework/validators/FileValidator.php | 57 +++ framework/validators/FilterValidator.php | 1 + framework/validators/InlineValidator.php | 1 + framework/validators/NumberValidator.php | 1 + framework/validators/RangeValidator.php | 1 + .../validators/RegularExpressionValidator.php | 1 + framework/validators/RequiredValidator.php | 1 + framework/validators/StringValidator.php | 1 + framework/validators/UniqueValidator.php | 1 + framework/validators/Validator.php | 3 +- framework/views/messageConfig.php | 6 +- framework/web/Application.php | 5 +- framework/web/AssetConverter.php | 1 + framework/web/CacheSession.php | 1 + framework/web/CompositeUrlRule.php | 1 + framework/web/Controller.php | 1 + framework/web/Cookie.php | 1 + framework/web/CookieCollection.php | 1 + framework/web/DbSession.php | 1 + framework/web/ErrorAction.php | 6 + framework/web/HtmlResponseFormatter.php | 1 + framework/web/HttpException.php | 1 + framework/web/JsExpression.php | 1 + framework/web/JsonParser.php | 1 + framework/web/JsonResponseFormatter.php | 1 + framework/web/Link.php | 1 + framework/web/Request.php | 20 +- framework/web/Response.php | 1 + framework/web/Session.php | 2 + framework/web/SessionIterator.php | 1 + framework/web/UploadedFile.php | 4 +- framework/web/UrlManager.php | 33 +- framework/web/UrlRule.php | 2 +- framework/web/User.php | 1 + framework/web/View.php | 2 +- framework/web/ViewAction.php | 4 +- framework/web/XmlResponseFormatter.php | 1 + framework/widgets/ActiveField.php | 2 +- framework/widgets/ActiveForm.php | 23 +- framework/widgets/BaseListView.php | 1 + framework/widgets/Block.php | 1 + framework/widgets/Breadcrumbs.php | 1 + framework/widgets/ContentDecorator.php | 1 + framework/widgets/DetailView.php | 1 + framework/widgets/FragmentCache.php | 8 + framework/widgets/InputWidget.php | 1 + framework/widgets/LinkPager.php | 1 + framework/widgets/LinkSorter.php | 1 + framework/widgets/ListView.php | 1 + framework/widgets/Pjax.php | 1 + tests/unit/data/travis/sphinx-setup.sh | 6 - tests/unit/extensions/gii/GeneratorsTest.php | 105 ++++ tests/unit/extensions/gii/GiiTestCase.php | 49 ++ tests/unit/extensions/gii/Profile.php | 40 ++ .../console/controllers/MigrateControllerTest.php | 10 +- tests/unit/extensions/twig/ViewRendererTest.php | 24 + tests/unit/extensions/twig/views/changeTitle.twig | 3 + tests/unit/extensions/twig/views/extends1.twig | 5 + tests/unit/extensions/twig/views/extends2.twig | 5 + tests/unit/extensions/twig/views/extends3.twig | 5 + tests/unit/framework/base/ExposedSecurity.php | 27 ++ tests/unit/framework/base/FormatterTest.php | 26 + tests/unit/framework/base/SecurityTest.php | 297 ++++++++++-- .../controllers/BaseMessageControllerTest.php | 346 ++++++++++++++ .../console/controllers/MessageControllerTest.php | 387 --------------- .../controllers/PHPMessageControllerTest.php | 84 ++++ .../controllers/POMessageControllerTest.php | 80 ++++ tests/unit/framework/helpers/FileHelperTest.php | 2 +- tests/unit/framework/helpers/StringHelperTest.php | 28 ++ tests/unit/framework/rbac/DbManagerTestCase.php | 11 +- tests/unit/framework/rbac/ManagerTestCase.php | 43 ++ tests/unit/framework/rbac/MySQLManagerTest.php | 2 + tests/unit/framework/rbac/PgSQLManagerTest.php | 2 + tests/unit/framework/rbac/PhpManagerTest.php | 21 +- tests/unit/framework/rbac/SqliteManagerTest.php | 2 + 411 files changed, 4808 insertions(+), 1523 deletions(-) create mode 100644 docs/guide-zh-CN/caching-fragment.md create mode 100644 docs/guide-zh-CN/caching-http.md create mode 100644 docs/guide-zh-CN/caching-page.md create mode 100644 docs/guide-zh-CN/concept-events.md create mode 100644 docs/guide-zh-CN/input-validation.md create mode 100644 docs/guide-zh-CN/tutorial-core-validators.md create mode 100644 docs/guide/test-acceptance.md create mode 100644 docs/guide/test-functional.md create mode 100644 docs/guide/test-unit.md delete mode 100644 extensions/twig/FileLoader.php create mode 100644 framework/messages/zh-TW/yii.php create mode 100644 tests/unit/extensions/gii/GeneratorsTest.php create mode 100644 tests/unit/extensions/gii/GiiTestCase.php create mode 100644 tests/unit/extensions/gii/Profile.php create mode 100644 tests/unit/extensions/twig/views/changeTitle.twig create mode 100644 tests/unit/extensions/twig/views/extends1.twig create mode 100644 tests/unit/extensions/twig/views/extends2.twig create mode 100644 tests/unit/extensions/twig/views/extends3.twig create mode 100644 tests/unit/framework/base/ExposedSecurity.php create mode 100644 tests/unit/framework/console/controllers/BaseMessageControllerTest.php delete mode 100644 tests/unit/framework/console/controllers/MessageControllerTest.php create mode 100644 tests/unit/framework/console/controllers/PHPMessageControllerTest.php create mode 100644 tests/unit/framework/console/controllers/POMessageControllerTest.php diff --git a/.travis.yml b/.travis.yml index 230b50f..7217ac6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,11 @@ php: - hhvm - hhvm-nightly -# run build against PHP 5.6 and hhvm but allow them to fail +# run build against hhvm but allow them to fail # http://docs.travis-ci.com/user/build-configuration/#Rows-That-are-Allowed-To-Fail matrix: fast_finish: true allow_failures: - - php: 5.6 - php: hhvm - php: hhvm-nightly diff --git a/README.md b/README.md index 9157cf0..cc86ad1 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ which is the latest stable release of Yii. [![Code Coverage](https://scrutinizer-ci.com/g/yiisoft/yii2/badges/coverage.png?s=31d80f1036099e9d6a3e4d7738f6b000b3c3d10e)](https://scrutinizer-ci.com/g/yiisoft/yii2/) [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/yiisoft/yii2/badges/quality-score.png?s=b1074a1ff6d0b214d54fa5ab7abbb90fc092471d)](https://scrutinizer-ci.com/g/yiisoft/yii2/) [![Code Climate](https://codeclimate.com/github/yiisoft/yii2.png)](https://codeclimate.com/github/yiisoft/yii2) +[![Reference Status](https://www.versioneye.com/php/yiisoft:yii2/reference_badge.svg)](https://www.versioneye.com/php/yiisoft:yii2/references) DIRECTORY STRUCTURE ------------------- diff --git a/apps/advanced/README.md b/apps/advanced/README.md index ea67b5f..e20d73d 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -100,7 +100,7 @@ TESTING ------- Install additional composer packages: -* `php composer.phar require --dev "codeception/codeception: 2.0.*" "codeception/specify: *" "codeception/verify: *" "yiisoft/yii2-faker: *"` +* `php composer.phar require --dev codeception/codeception:2.0.* codeception/specify:* codeception/verify:* yiisoft/yii2-faker:*` This application boilerplate use database in testing, so you should create three databases that are used in tests: * `yii2_advanced_unit` - database for unit tests; @@ -112,7 +112,7 @@ if you are starting from `frontend` tests then you should run `yii migrate` in e it will upgrade your database to the last state according migrations. To be able to run acceptance tests you need a running webserver. For this you can use the php builtin server and run it in the directory where your main project folder is located. For example if your application is located in `/www/advanced` all you need to is: -`cd /www` and then `php -S 127.0.0.1:8080` because the default configuration of acceptance tests expects the url of the application to be `/advanced/`. +`cd /www` and then `php -S 127.0.0.1:8080 index-test.php` because the default configuration of acceptance tests expects the url of the application to be `/advanced/`. If you already have a server configured or your application is not located in a folder called `advanced`, you may need to adjust the `TEST_ENTRY_URL` in `frontend/tests/_bootstrap.php` and `backend/tests/_bootstrap.php`. After that is done you should be able to run your tests, for example to run `frontend` tests do: diff --git a/apps/advanced/common/models/LoginForm.php b/apps/advanced/common/models/LoginForm.php index e4ebe79..80a5f6a 100644 --- a/apps/advanced/common/models/LoginForm.php +++ b/apps/advanced/common/models/LoginForm.php @@ -33,13 +33,16 @@ class LoginForm extends Model /** * Validates the password. * This method serves as the inline validation for password. + * + * @param string $attribute the attribute currently being validated + * @param array $params the additional name-value pairs given in the rule */ - public function validatePassword() + public function validatePassword($attribute, $params) { if (!$this->hasErrors()) { $user = $this->getUser(); if (!$user || !$user->validatePassword($this->password)) { - $this->addError('password', 'Incorrect username or password.'); + $this->addError($attribute, 'Incorrect username or password.'); } } } diff --git a/apps/advanced/common/models/User.php b/apps/advanced/common/models/User.php index 45afd6f..76c0926 100644 --- a/apps/advanced/common/models/User.php +++ b/apps/advanced/common/models/User.php @@ -159,7 +159,7 @@ class User extends ActiveRecord implements IdentityInterface */ public function generateAuthKey() { - $this->auth_key = Yii::$app->security->generateRandomKey(); + $this->auth_key = Yii::$app->security->generateRandomString(); } /** @@ -167,7 +167,7 @@ class User extends ActiveRecord implements IdentityInterface */ public function generatePasswordResetToken() { - $this->password_reset_token = Yii::$app->security->generateRandomKey() . '_' . time(); + $this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time(); } /** diff --git a/apps/advanced/common/tests/templates/fixtures/user.php b/apps/advanced/common/tests/templates/fixtures/user.php index b91ba40..87d382d 100644 --- a/apps/advanced/common/tests/templates/fixtures/user.php +++ b/apps/advanced/common/tests/templates/fixtures/user.php @@ -3,7 +3,7 @@ return [ 'username' => 'userName', 'auth_key' => function ($fixture, $faker, $index) { - $fixture['auth_key'] = Yii::$app->getSecurity()->generateRandomKey(); + $fixture['auth_key'] = Yii::$app->getSecurity()->generateRandomString(); return $fixture; }, @@ -13,7 +13,7 @@ return [ return $fixture; }, 'password_reset_token' => function ($fixture, $faker, $index) { - $fixture['password_reset_token'] = Yii::$app->getSecurity()->generateRandomKey() . '_' . time(); + $fixture['password_reset_token'] = Yii::$app->getSecurity()->generateRandomString() . '_' . time(); return $fixture; }, diff --git a/apps/basic/models/LoginForm.php b/apps/basic/models/LoginForm.php index 31b814e..687d29d 100644 --- a/apps/basic/models/LoginForm.php +++ b/apps/basic/models/LoginForm.php @@ -34,14 +34,17 @@ class LoginForm extends Model /** * Validates the password. * This method serves as the inline validation for password. + * + * @param string $attribute the attribute currently being validated + * @param array $params the additional name-value pairs given in the rule */ - public function validatePassword() + public function validatePassword($attribute, $params) { if (!$this->hasErrors()) { $user = $this->getUser(); if (!$user || !$user->validatePassword($this->password)) { - $this->addError('password', 'Incorrect username or password.'); + $this->addError($attribute, 'Incorrect username or password.'); } } } diff --git a/build/controllers/AppController.php b/build/controllers/AppController.php index a835bd7..a9f907c 100644 --- a/build/controllers/AppController.php +++ b/build/controllers/AppController.php @@ -29,7 +29,7 @@ class AppController extends Controller */ protected function unlink($file) { - if (is_dir($file) && strncasecmp(PHP_OS, 'WIN', 3) === 0) { + if (is_dir($file) && DIRECTORY_SEPARATOR === '\\') { rmdir($file); } else { unlink($file); diff --git a/build/controllers/MimeTypeController.php b/build/controllers/MimeTypeController.php index f3606f6..028623c 100644 --- a/build/controllers/MimeTypeController.php +++ b/build/controllers/MimeTypeController.php @@ -9,6 +9,7 @@ namespace yii\build\controllers; use Yii; use yii\console\Controller; +use yii\helpers\Console; use yii\helpers\VarDumper; /** @@ -33,7 +34,10 @@ class MimeTypeController extends Controller if ($outFile === null) { $outFile = Yii::getAlias('@yii/helpers/mimeTypes.php'); } + $this->stdout('downloading mime-type file from apache httpd repository...'); if ($content = file_get_contents('http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/conf/mime.types?view=co')) { + $this->stdout("done.\n", Console::FG_GREEN); + $this->stdout("generating file $outFile..."); $mimeMap = []; foreach(explode("\n", $content) as $line) { $line = trim($line); @@ -65,6 +69,7 @@ return $array; EOD; file_put_contents($outFile, $content); + $this->stdout("done.\n", Console::FG_GREEN); } else { $this->stderr("Failed to download mime.types file from apache SVN.\n"); } diff --git a/build/controllers/PhpDocController.php b/build/controllers/PhpDocController.php index dca2156..7417e08 100644 --- a/build/controllers/PhpDocController.php +++ b/build/controllers/PhpDocController.php @@ -85,6 +85,7 @@ class PhpDocController extends Controller $this->fixFileDoc($lines); $this->fixDocBlockIndentation($lines); + $lines = array_values($this->fixLineSpacing($lines)); $newContent = implode("\n", $lines); if ($sha !== sha1($newContent)) { @@ -163,6 +164,9 @@ class PhpDocController extends Controller return FileHelper::findFiles($root, $options); } + /** + * Fix file PHPdoc + */ protected function fixFileDoc(&$lines) { // find namespace @@ -170,12 +174,15 @@ class PhpDocController extends Controller $namespaceLine = ''; $contentAfterNamespace = false; foreach($lines as $i => $line) { - if (substr(trim($line), 0, 9) === 'namespace') { - $namespace = $i; - $namespaceLine = trim($line); - } elseif ($namespace !== false && trim($line) !== '') { - $contentAfterNamespace = $i; - break; + $line = trim($line); + if (!empty($line)) { + if (strncmp($line, 'namespace', 9) === 0) { + $namespace = $i; + $namespaceLine = $line; + } elseif ($namespace !== false) { + $contentAfterNamespace = $i; + break; + } } } @@ -254,6 +261,116 @@ class PhpDocController extends Controller } } + /** + * Fixes line spacing code style for properties and constants + */ + protected function fixLineSpacing($lines) + { + $propertiesOnly = false; + // remove blank lines between properties + $skip = true; + foreach($lines as $i => $line) { + if (strpos($line, 'class ') !== false) { + $skip = false; + } + if ($skip) { + continue; + } + if (trim($line) === '') { + unset($lines[$i]); + } elseif (ltrim($line)[0] !== '*' && strpos($line, 'function ') !== false) { + break; + } elseif (trim($line) === '}') { + $propertiesOnly = true; + break; + } + } + $lines = array_values($lines); + + // add back some + $endofUse = false; + $endofConst = false; + $endofPublic = false; + $endofProtected = false; + $endofPrivate = false; + $skip = true; + $level = 0; // track array properties + $property = ''; + foreach($lines as $i => $line) { + if (strpos($line, 'class ') !== false) { + $skip = false; + } + if ($skip) { + continue; + } + + // check for multi line array + if ($level > 0) { + ${'endof'.$property} = $i; + } + + $line = trim($line); + if (strncmp($line, 'public $', 8) === 0 || strncmp($line, 'public static $', 15) === 0) { + $endofPublic = $i; + $property = 'Public'; + $level = 0; + } elseif (strncmp($line, 'protected $', 11) === 0 || strncmp($line, 'protected static $', 18) === 0) { + $endofProtected = $i; + $property = 'Protected'; + $level = 0; + } elseif (strncmp($line, 'private $', 9) === 0 || strncmp($line, 'private static $', 16) === 0) { + $endofPrivate = $i; + $property = 'Private'; + $level = 0; + } elseif (substr($line,0 , 6) === 'const ') { + $endofConst = $i; + $property = false; + } elseif (substr($line,0 , 4) === 'use ') { + $endofUse = $i; + $property = false; + } elseif (!empty($line) && $line[0] === '*') { + $property = false; + } elseif (!empty($line) && $line[0] !== '*' && strpos($line, 'function ') !== false || $line === '}') { + break; + } + + // check for multi line array + if ($property !== false && strncmp($line, "'SQLSTATE[", 10) !== 0) { + $level += substr_count($line, '[') - substr_count($line, ']'); + } + } + + $endofAll = false; + foreach(['Private', 'Protected', 'Public', 'Const', 'Use'] as $var) { + if (${'endof'.$var} !== false) { + $endofAll = ${'endof'.$var}; + break; + } + } + +// $this->checkPropertyOrder($lineInfo); + $result = []; + foreach($lines as $i => $line) { + $result[] = $line; + if (!($propertiesOnly && $i === $endofAll)) { + if ($i === $endofUse || $i === $endofConst || $i === $endofPublic || + $i === $endofProtected || $i === $endofPrivate) { + $result[] = ''; + } + if ($i === $endofAll) { + $result[] = ''; + } + } + } + + return $result; + } + + protected function checkPropertyOrder($lineInfo) + { + // TODO + } + protected function updateClassPropertyDocs($file, $className, $propertyDoc) { $ref = new \ReflectionClass($className); @@ -275,13 +392,15 @@ class PhpDocController extends Controller // TODO move these checks to different action $lines = explode("\n", $newDoc); - if (trim($lines[1]) == '*' || substr(trim($lines[1]), 0, 3) == '* @') { + $firstLine = trim($lines[1]); + if ($firstLine === '*' || strncmp($firstLine, '* @', 3) === 0) { $this->stderr("[WARN] Class $className has no short description.\n", Console::FG_YELLOW, Console::BOLD); } foreach ($lines as $line) { - if (substr(trim($line), 0, 9) == '* @since ') { + $line = trim($line); + if (strncmp($line, '* @since ', 9) === 0) { $seenSince = true; - } elseif (substr(trim($line), 0, 10) == '* @author ') { + } elseif (strncmp($line, '* @author ', 10) === 0) { $seenAuthor = true; } } @@ -350,13 +469,14 @@ class PhpDocController extends Controller $propertyPart = false; $propertyPosition = false; foreach ($lines as $i => $line) { - if (substr(trim($line), 0, 12) == '* @property ') { + $line = trim($line); + if (strncmp($line, '* @property ', 12) === 0) { $propertyPart = true; - } elseif ($propertyPart && trim($line) == '*') { + } elseif ($propertyPart && $line == '*') { $propertyPosition = $i; $propertyPart = false; } - if (substr(trim($line), 0, 10) == '* @author ' && $propertyPosition === false) { + if (strncmp($line, '* @author ', 10) === 0 && $propertyPosition === false) { $propertyPosition = $i - 1; $propertyPart = false; } diff --git a/docs/guide-ru/intro-upgrade-from-v1.md b/docs/guide-ru/intro-upgrade-from-v1.md index 41e28aa..0cf67cf 100644 --- a/docs/guide-ru/intro-upgrade-from-v1.md +++ b/docs/guide-ru/intro-upgrade-from-v1.md @@ -103,7 +103,7 @@ $object = Yii::createObject([ ], [$param1, $param2]); ``` -Более детальная информация о конфигурация представлена в разделе [Конфигурации объектов](concept-configurations.md). +Более детальная информация о конфигурации представлена в разделе [Конфигурации объектов](concept-configurations.md). События diff --git a/docs/guide-zh-CN/README.md b/docs/guide-zh-CN/README.md index b6c4887..d907542 100644 --- a/docs/guide-zh-CN/README.md +++ b/docs/guide-zh-CN/README.md @@ -39,7 +39,7 @@ Yii 2.0 权威指南 * **已定稿** [小部件(Widget)](structure-widgets.md) * **已定稿** [模块(Module)](structure-modules.md) * **编撰中** [前端资源(Asset)](structure-assets.md) -* **待定中** [扩展(extensions)](structure-extensions.md) +* **已定稿** [扩展(extensions)](structure-extensions.md) 请求处理 -------- @@ -144,16 +144,6 @@ RESTful Web 服务 * **待定中** [验收测试](test-acceptance.md) * **编撰中** [测试夹具](test-fixtures.md) -扩展 Yii --------- - -* **编撰中** [创建扩展](extend-creating-extensions.md) -* **编撰中** [定制核心代码](extend-customizing-core.md) -* **编撰中** [使用第三方库](extend-using-libs.md) -* **待定中** [在第三方系统使用 Yii](extend-embedding-in-others.md) -* **待定中** [Yii 1.1 和 2.0 共用](extend-using-v1-v2.md) -* **编撰中** [使用依赖包管理器 Composer](extend-using-composer.md) - 高级专题 -------- @@ -167,6 +157,7 @@ RESTful Web 服务 * **编撰中** [性能优化](tutorial-performance-tuning.md) * **待定中** [共享主机环境](tutorial-shared-hosting.md) * **编撰中** [模板引擎](tutorial-template-engines.md) +* **已定稿** [集成第三方代码](tutorial-yii-integration.md) 小部件 ------ diff --git a/docs/guide-zh-CN/caching-data.md b/docs/guide-zh-CN/caching-data.md index 38f4436..988e4b4 100644 --- a/docs/guide-zh-CN/caching-data.md +++ b/docs/guide-zh-CN/caching-data.md @@ -1,10 +1,9 @@ 数据缓存 ============ -数据缓存是指将一些 PHP 变量存储到缓存中,使用时再从缓存中取回。它也是更高级缓存特性的基础,例如 -[查询缓存](#query-caching) 和 [分页缓存](caching-page.md). +数据缓存是指将一些 PHP 变量存储到缓存中,使用时再从缓存中取回。它也是更高级缓存特性的基础,例如[查询缓存](#query-caching)和[内容缓存](caching-content.md)。 -如下代码是一个典型的数据缓存使用模式。其中 `$cache` 代表一个 [缓存组件](#cache-components): +如下代码是一个典型的数据缓存使用模式。其中 `$cache` 指向[缓存组件](#cache-components): ```php // 尝试从缓存中取回 $data @@ -24,10 +23,9 @@ if ($data === false) { ## 缓存组件 -数据缓存需要称作“*缓存组件*”的东西提供支持,它代表着各种缓存存储器,例如内存,文件,数据库。 +数据缓存需要**缓存组件**提供支持,它代表各种缓存存储器,例如内存,文件,数据库。 -缓存组件通常注册为应用程序组件,这样它们就可以接受全局性配置和调用。如下代码演示了如何配置 `cache` -应用程序组件使用两个 [memcached](http://memcached.org/) 服务器: +缓存组件通常注册为应用程序组件,这样它们就可以在全局进行配置与访问。如下代码演示了如何配置应用程序组件 `cache` 使用两个 [memcached](http://memcached.org/) 服务器: ```php 'components' => [ @@ -49,10 +47,9 @@ if ($data === false) { ], ``` -然后你就可以通过 `Yii::$app->cache` 访问上面的缓存组件了。 +然后就可以通过 `Yii::$app->cache` 访问上面的缓存组件了。 -由于所有缓存组件都支持同样的一系列 API -,你并不需要修改使用缓存的那些代码就能直接替换为其他低层缓存组件,只需在应用程序配置中重新配置一下就可以。例如,你可以将上述配置修改为使用 [[yii\caching\ApcCache|APC cache]]: +由于所有缓存组件都支持同样的一系列 API ,并不需要修改使用缓存的业务代码就能直接替换为其他底层缓存组件,只需在应用配置中重新配置一下就可以。例如,你可以将上述配置修改为使用 [[yii\caching\ApcCache|APC cache]]: ```php @@ -71,19 +68,14 @@ if ($data === false) { Yii 支持一系列缓存存储器,概况如下: * [[yii\caching\ApcCache]]: 使用 PHP [APC](http://php.net/manual/en/book.apc.php) 扩展。这个选项可以认为是集中式应用程序环境中(例如:单一服务器,没有独立的负载均衡器等)最快的缓存方案。 -* [[yii\caching\DbCache]]: 使用一个数据库的表存储缓存数据。要使用这个缓存,你必须创建一个 [[yii\caching\DbCache::cacheTable]] 对应的表。 -* [[yii\caching\DummyCache]]: 仅作为一个缓存占位符,不实现任何真正的缓存功能。 这个组件的目的是为了简化那些需要查询缓存有效性的代码。例如,在开发中如果服务器没有实际的缓存支持,你就可以用它配置一个缓存组件。 一个真正的缓存服务启用后,就可以切换为使用相应的缓存组件。两种条件下你都可以使用同样的代码 -`Yii::$app->cache->get($key)` 尝试从缓存中取回数据而不用担心 `Yii::$app->cache` 可能是 `null`。 -* [[yii\caching\FileCache]]: 使用标准文件存储缓存数据。这个特别适用于缓存大块数据,例如一个网页的内容。 -* [[yii\caching\MemCache]]: 使用 PHP [memcache](http://php.net/manual/en/book.memcache.php) 和 - [memcached](http://php.net/manual/en/book.memcached.php) 扩展。这个选项可以认为是分布式应用程序环境中(例如:多台服务器,有负载均衡等)最快的缓存方案。 +* [[yii\caching\DbCache]]: 使用一个数据库的表存储缓存数据。要使用这个缓存,你必须创建一个与 [[yii\caching\DbCache::cacheTable]] 对应的表。 +* [[yii\caching\DummyCache]]: 仅作为一个缓存占位符,不实现任何真正的缓存功能。这个组件的目的是为了简化那些需要查询缓存有效性的代码。例如,在开发中如果服务器没有实际的缓存支持,用它配置一个缓存组件。一个真正的缓存服务启用后,可以再切换为使用相应的缓存组件。两种条件下你都可以使用同样的代码 `Yii::$app->cache->get($key)` 尝试从缓存中取回数据而不用担心 `Yii::$app->cache` 可能是 `null`。 +* [[yii\caching\FileCache]]: 使用标准文件存储缓存数据。这个特别适用于缓存大块数据,例如一个整页的内容。 +* [[yii\caching\MemCache]]: 使用 PHP [memcache](http://php.net/manual/en/book.memcache.php) 和 [memcached](http://php.net/manual/en/book.memcached.php) 扩展。这个选项被看作分布式应用环境中(例如:多台服务器,有负载均衡等)最快的缓存方案。 * [[yii\redis\Cache]]: 实现了一个基于 [Redis](http://redis.io/) 键值对存储器的缓存组件(需要 redis 2.6.12 及以上版本的支持 )。 -* [[yii\caching\WinCache]]: 使用 PHP [WinCache](http://iis.net/downloads/microsoft/wincache-extension) - ([另可参考](http://php.net/manual/en/book.wincache.php)) 扩展. -* [[yii\caching\XCache]]: 使用 PHP [XCache](http://xcache.lighttpd.net/) 扩展。 -* [[yii\caching\ZendDataCache]]: 使用 - [Zend Data Cache](http://files.zend.com/help/Zend-Server-6/zend-server.htm#data_cache_component.htm) - 作为底层缓存介质。 +* [[yii\caching\WinCache]]: 使用 PHP [WinCache](http://iis.net/downloads/microsoft/wincache-extension)([另可参考](http://php.net/manual/en/book.wincache.php))扩展. +* [[yii\caching\XCache]]: 使用 PHP [XCache](http://xcache.lighttpd.net/)扩展。 +* [[yii\caching\ZendDataCache]]: 使用 [Zend Data Cache](http://files.zend.com/help/Zend-Server-6/zend-server.htm#data_cache_component.htm) 作为底层缓存媒介。 > Tip: 你可以在同一个应用程序中使用不同的缓存存储器。一个常见的策略是使用基于内存的缓存存储器存储小而常用的数据(例如:统计数据),使用基于文件或数据库的缓存存储器存储大而不太常用的数据(例如:网页内容)。 @@ -95,7 +87,7 @@ Yii 支持一系列缓存存储器,概况如下: * [[yii\caching\Cache::get()|get()]]: 通过一个指定的键(key)从缓存中取回一项数据。如果该项数据不存在于缓存中或者已经过期/失效,则返回值 false。 * [[yii\caching\Cache::set()|set()]]: 将一项数据指定一个键,存放到缓存中。 -* [[yii\caching\Cache::add()|add()]]: 将一项数据指定一个键,如果缓存中未找到该键,则将其存放到缓存中。 +* [[yii\caching\Cache::add()|add()]]: 如果缓存中未找到该键,则将指定数据存放到缓存中。 * [[yii\caching\Cache::mget()|mget()]]: 通过指定的多个键从缓存中取回多项数据。 * [[yii\caching\Cache::mset()|mset()]]: 将多项数据存储到缓存中,每项数据对应一个键。 * [[yii\caching\Cache::madd()|madd()]]: 将多项数据存储到缓存中,每项数据对应一个键。如果某个键已经存在于缓存中,则该项数据会被跳过。 @@ -103,10 +95,9 @@ Yii 支持一系列缓存存储器,概况如下: * [[yii\caching\Cache::delete()|delete()]]: 通过一个键,删除缓存中对应的值。 * [[yii\caching\Cache::flush()|flush()]]: 删除缓存中的所有数据。 -有些缓存存储器如 MemCache,APC 支持以批量模式取回缓存值,这样可以节省取回缓存数据的开支。 [[yii\caching\Cache::mget()|mget()]] 和 -[[yii\caching\Cache::madd()|madd()]] API提供对该特性的支持。如果底层缓存存储器不支持该特性,Yii 也会模拟实现。 +有些缓存存储器如 MemCache,APC 支持以批量模式取回缓存值,这样可以节省取回缓存数据的开支。 [[yii\caching\Cache::mget()|mget()]] 和 [[yii\caching\Cache::madd()|madd()]] API提供对该特性的支持。如果底层缓存存储器不支持该特性,Yii 也会模拟实现。 -由于 [[yii\caching\Cache]] 实现了 `ArrayAccess`,缓存组件也可以像数组那样使用,下面是几个例子: +由于 [[yii\caching\Cache]] 实现了 PHP `ArrayAccess` 接口,缓存组件也可以像数组那样使用,下面是几个例子: ```php $cache['var1'] = $value1; // 等价于: $cache->set('var1', $value1); @@ -116,7 +107,7 @@ $value2 = $cache['var2']; // 等价于: $value2 = $cache->get('var2'); ### 缓存键 -存储在缓存中的每项数据都通过键作唯一识别。当你在缓存中存储一项数据时,你必须为它指定一个键,稍后从缓存中取回数据时,也需要提供相应的键。 +存储在缓存中的每项数据都通过键作唯一识别。当你在缓存中存储一项数据时,必须为它指定一个键,稍后从缓存中取回数据时,也需要提供相应的键。 你可以使用一个字符串或者任意值作为一个缓存键。当键不是一个字符串时,它将会自动被序列化为一个字符串。 @@ -133,8 +124,7 @@ $value2 = $cache['var2']; // 等价于: $value2 = $cache->get('var2'); 如你所见,该键包含了可唯一指定一个数据库表所需的所有必要信息。 -当同一个缓存存储器被用于多个不同的应用程序时,你应该为每个应用程序指定一个唯一的缓存键前缀以避免缓存键冲突。 -这可以通过配置 [[yii\caching\Cache::keyPrefix]] 属性实现。例如,在应用程序配置中,你可以编写如下代码: +当同一个缓存存储器被用于多个不同的应用时,应该为每个应用指定一个唯一的缓存键前缀以避免缓存键冲突。可以通过配置 [[yii\caching\Cache::keyPrefix]] 属性实现。例如,在应用配置中可以编写如下代码: ```php 'components' => [ @@ -150,9 +140,7 @@ $value2 = $cache['var2']; // 等价于: $value2 = $cache->get('var2'); ### 缓存过期 -缓存中的一项数据会一直留在缓存中,除非它被某些缓存策略强制移除(例如:缓存空间已满,最老的数据会被移除)。要改变此特性,你可以在调用 [[yii\caching\Cache::set()|set()]] -存储一项数据时提供一个过期时间参数。该参数表明这项数据在缓存中可保持有效多少秒。当你调用 -[[yii\caching\Cache::get()|get()]] 取回数据时,如果它已经过了超时时间,该方法将返回 false,表明在缓存中找不到这项数据。例如: +默认情况下,缓存中的数据会永久存留,除非它被某些缓存策略强制移除(例如:缓存空间已满,最老的数据会被移除)。要改变此特性,你可以在调用 [[yii\caching\Cache::set()|set()]] 存储一项数据时提供一个过期时间参数。该参数代表这项数据在缓存中可保持有效多少秒。当你调用 [[yii\caching\Cache::get()|get()]] 取回数据时,如果它已经过了超时时间,该方法将返回 false,表明在缓存中找不到这项数据。例如: ```php // 将数据在缓存中保留 45 秒 @@ -169,12 +157,9 @@ if ($data === false) { ### 缓存依赖 -除了超时设置,缓存数据还可能受称作*缓存依赖*的影响而失效。例如,[[yii\caching\FileDependency]] 表示对一个文件修改时间的依赖。 -这个依赖条件发生变化也就意味着相应的文件已经被修改。因此,缓存中任何过期的文件内容都应该被置为失效状态且对 [[yii\caching\Cache::get()|get()]] -的调用都应该返回 false。 +除了超时设置,缓存数据还可能受到**缓存依赖**的影响而失效。例如,[[yii\caching\FileDependency]] 代表对一个文件修改时间的依赖。这个依赖条件发生变化也就意味着相应的文件已经被修改。因此,缓存中任何过期的文件内容都应该被置为失效状态,对 [[yii\caching\Cache::get()|get()]] 的调用都应该返回 false。 -缓存依赖是以 [[yii\caching\Dependency]] 的子孙类所代表的。当你调用 -[[yii\caching\Cache::set()|set()]] 在缓存中存储一项数据时,你可以同时传递一个关联的缓存依赖对象。例如: +缓存依赖用 [[yii\caching\Dependency]] 的派生类所表示。当调用 [[yii\caching\Cache::set()|set()]] 在缓存中存储一项数据时,可以同时传递一个关联的缓存依赖对象。例如: ```php // 创建一个对 example.txt 文件修改时间的缓存依赖 @@ -192,20 +177,19 @@ $data = $cache->get($key); 下面是可用的缓存依赖的概况: -- [[yii\caching\ChainedDependency]]: 如果链上任何一个依赖产生变化,则依赖改变。 +- [[yii\caching\ChainedDependency]]: 如果依赖链上任何一个依赖产生变化,则依赖改变。 - [[yii\caching\DbDependency]]: 如果指定 SQL 语句的查询结果发生了变化,则依赖改变。 - [[yii\caching\ExpressionDependency]]: 如果指定的 PHP 表达式执行结果发生变化,则依赖改变。 - [[yii\caching\FileDependency]]: 如果文件的最后修改时间发生变化,则依赖改变。 - [[yii\caching\TagDependency]]: 为一项缓存数据添加一个或多个标签。你可以通过调用 [[yii\caching\TagDependency::invalidate()]] - 一次性将具有指定标签的缓存数据全部置为失效状态。 +一次性将具有指定标签的缓存数据全部置为失效状态。 ## 查询缓存 查询缓存是一个建立在数据缓存之上的特殊缓存特性。它用于缓存数据库查询的结果。 -查询缓存需要一个 [[yii\db\Connection|数据库连接]] 和一个有效的 `cache` 应用程序组件。 -查询缓存的基本用法如下,假设 `$db` 是一个 [[yii\db\Connection]] 实例: +查询缓存需要一个 [[yii\db\Connection|数据库连接]] 和一个有效的 `cache` 应用组件。查询缓存的基本用法如下,假设 `$db` 是一个 [[yii\db\Connection]] 实例: ```php $duration = 60; // 缓存查询结果60秒 @@ -218,22 +202,19 @@ $db->beginCache($duration, $dependency); $db->endCache(); ``` -如你所见,`beginCache()` 和 `endCache()` 中间的任何查询结果都会被缓存起来。 -如果缓存中找到了同样查询的结果,则查询会被跳过,直接从缓存中提取结果。 +如你所见,`beginCache()` 和 `endCache()` 中间的任何查询结果都会被缓存起来。如果缓存中找到了同样查询的结果,则查询会被跳过,直接从缓存中提取结果。 -查询缓存可以用于 [DAO](db-dao.md) 和 [ActiveRecord](db-active-record.md)。 +查询缓存可以用于 [ActiveRecord](db-active-record.md) 和 [DAO](db-dao.md)。 -> Info: 有些 DBMS (例如:[MySQL](http://dev.mysql.com/doc/refman/5.1/en/query-cache.html)) - 也支持数据库服务器端的查询缓存。你可以选择使用任一查询缓存机制。上文所述的查询缓存的好处在于你可以指定更灵活的缓存依赖因此可能更加高效。 +> Info: 有些 DBMS (例如:[MySQL](http://dev.mysql.com/doc/refman/5.1/en/query-cache.html))也支持数据库服务器端的查询缓存。你可以选择使用任一查询缓存机制。上文所述的查询缓存的好处在于你可以指定更灵活的缓存依赖因此可能更加高效。 ### 配置 -查询缓存有两个通过 [[yii\db\Connection]] 设置的配置项: +查询缓存有两个通过 [[yii\db\Connection]] 设置的配置项: -* [[yii\db\Connection::queryCacheDuration|queryCacheDuration]]: 查询结果在缓存中的有效期,以秒表示。如果在调用 - [[yii\db\Connection::beginCache()]] 时传递了一个显式的时值参数,则配置中的有效期时值会被覆盖。 -* [[yii\db\Connection::queryCache|queryCache]]: 缓存应用程序组件的 ID。默认为 `'cache'`。只有在设置了一个有效的缓存应用程序组件时,查询缓存才会有效。 +* [[yii\db\Connection::queryCacheDuration|queryCacheDuration]]: 查询结果在缓存中的有效期,以秒表示。如果在调用 [[yii\db\Connection::beginCache()]] 时传递了一个显式的时值参数,则配置中的有效期时值会被覆盖。 +* [[yii\db\Connection::queryCache|queryCache]]: 缓存应用组件的 ID。默认为 `'cache'`。只有在设置了一个有效的缓存应用组件时,查询缓存才会有效。 ### 限制条件 diff --git a/docs/guide-zh-CN/caching-fragment.md b/docs/guide-zh-CN/caching-fragment.md new file mode 100644 index 0000000..c379b90 --- /dev/null +++ b/docs/guide-zh-CN/caching-fragment.md @@ -0,0 +1,141 @@ +片段缓存 +================ + +片段缓存指的是缓存页面内容中的某个片段。例如,一个页面显示了逐年销售额的摘要表格,可以把表格缓存下来,以消除每次请求都要重新生成表格的耗时。片段缓存是基于[数据缓存](caching-data.md)实现的。 + +在[视图](structure-views.md)中使用以下结构启用片段缓存: + +```php +if ($this->beginCache($id)) { + + // ... 在此生成内容 ... + + $this->endCache(); +} +``` + +调用 [[yii\base\View::beginCache()|beginCache()]] 和 [[yii\base\View::endCache()|endcache()]] 方法包裹内容生成逻辑。如果缓存中存在该内容,[[yii\base\View::beginCache()|beginCache()]] 方法将渲染内容并返回 false,因此将跳过内容生成逻辑。否则,内容生成逻辑被执行,一直执行到 [[yii\base\View::endCache()|endCache()]] 时,生成的内容将被捕获并存储在缓存中。 + +和[[数据缓存]](caching-data.md)一样,每个片段缓存也需要全局唯一的 `$id` 标记。 + + +## 缓存选项 + +如果要为片段缓存指定额外配置项,请通过向 [[yii\base\View::beginCache()|beginCache()]] 方法第二个参数传递配置数组。在框架内部,该数组将被用来配置一个 [[yii\widget\FragmentCache]] 小部件用以实现片段缓存功能。 + +### 过期时间(duration) + +或许片段缓存中最常用的一个配置选项就是 [[yii\widgets\FragmentCache::duration|duration]] 了。它指定了内容被缓存的秒数。以下代码缓存内容最多一小时: + +```php +if ($this->beginCache($id, ['duration' => 3600])) { + + // ... 在此生成内容 ... + + $this->endCache(); +} +``` + +如果该选项未设置,则默认为 0,永不过期。 + + +### 依赖 + +和[[数据缓存]](caching-data.md)一样,片段缓存的内容一样可以设置缓存依赖。例如一段被缓存的文章,是否重新缓存取决于它是否被修改过。 + +通过设置 [[yii\widgets\FragmentCache::dependency|dependency]] 选项来指定依赖,该选项的值可以是一个 [[yii\caching\Dependency]] 类的派生类,也可以是创建缓存对象的配置数组。以下代码指定了一个片段缓存,它依赖于 `update_at` 字段是否被更改过的。 + +```php +$dependency = [ + 'class' => 'yii\caching\DbDependency', + 'sql' => 'SELECT MAX(updated_at) FROM post', +]; + +if ($this->beginCache($id, ['dependency' => $dependency])) { + + // ... 在此生成内容 ... + + $this->endCache(); +} +``` + + +### 变化 + +缓存的内容可能需要根据一些参数的更改而变化。例如一个 Web 应用支持多语言,同一段视图代码也许需要生成多个语言的内容。因此可以设置缓存根据应用当前语言而变化。 + +通过设置 [[yii\widgets\FragmentCache::variations|variations]] 选项来指定变化,该选项的值应该是一个标量,每个标量代表不同的变化系数。例如设置缓存根据当前语言而变化可以用以下代码: + +```php +if ($this->beginCache($id, ['variations' => [Yii::$app->language]])) { + + // ... 在此生成内容 ... + + $this->endCache(); +} +``` + + +### 开关 + +有时你可能只想在特定条件下开启片段缓存。例如,一个显示表单的页面,可能只需要在初次请求时缓存表单(通过 GET 请求)。随后请求所显示(通过 POST 请求)的表单不该使用缓存,因为此时表单中可能包含用户输入内容。鉴于此种情况,可以使用 [[yii\widgets\FragmentCache::enabled|enabled]] 选项来指定缓存开关,如下所示: + +```php +if ($this->beginCache($id, ['enabled' => Yii::$app->request->isGet])) { + + // ... 在此生成内容 ... + + $this->endCache(); +} +``` + + +## 缓存嵌套 + +片段缓存可以被嵌套使用。一个片段缓存可以被另一个包裹。例如,评论被缓存在里层,同时整个评论的片段又被缓存在外层的文章中。以下代码展示了片段缓存的嵌套使用: + +```php +if ($this->beginCache($id1)) { + + // ...在此生成内容... + + if ($this->beginCache($id2, $options2)) { + + // ...在此生成内容... + + $this->endCache(); + } + + // ...在此生成内容... + + $this->endCache(); +} +``` + +可以为嵌套的缓存设置不同的配置项。例如,内层缓存和外层缓存使用不同的过期时间。甚至当外层缓存的数据过期失效了,内层缓存仍然可能提供有效的片段缓存数据。但是,反之则不然。如果外层片段缓存没有过期而被视为有效,此时即使内层片段缓存已经失效,它也将继续提供同样的缓存副本。因此,你必须谨慎处理缓存嵌套中的过期时间和依赖,否则外层的片段很有可能返回的是不符合你预期的失效数据。 + +> 译者注:外层的失效时间应该短于内层,外层的依赖条件应该低于内层,以确保最小的片段,返回的是最新的数据。 + + +## 动态内容 + +使用片段缓存时,可能会遇到一大段较为静态的内容中有少许动态内容的情况。例如,一个显示着菜单栏和当前用户名的页面头部。还有一种可能是缓存的内容可能包含每次请求都需要执行的 PHP 代码(例如注册资源包的代码)。这两个问题都可以使用**动态内容**功能解决。 + +动态内容的意思是这部分输出的内容不该被缓存,即便是它被包裹在片段缓存中。为了使内容保持动态,每次请求都执行 PHP 代码生成,即使这些代码已经被缓存了。 + +可以在片段缓存中调用 [[yii\base\View::renderDynamic()]] 去插入动态内容,如下所示: + +```php +if ($this->beginCache($id1)) { + + // ...在此生成内容... + + echo $this->renderDynamic('return Yii::$app->user->identity->name;'); + + // ...在此生成内容... + + $this->endCache(); +} +``` + +[[yii\base\View::renderDynamic()|renderDynamic()]] 方法接受一段 PHP 代码作为参数。代码的返回值被看作是动态内容。这段代码将在每次请求时都执行,无论其外层的片段缓存是否被存储。 diff --git a/docs/guide-zh-CN/caching-http.md b/docs/guide-zh-CN/caching-http.md new file mode 100644 index 0000000..bb29733 --- /dev/null +++ b/docs/guide-zh-CN/caching-http.md @@ -0,0 +1,108 @@ +HTTP 缓存 +============ + +除了前面章节讲到的服务器端缓存外, Web 应用还可以利用客户端缓存去节省相同页面内容的生成和传输时间。 + +通过配置 [[yii\filters\HttpCache]] 过滤器,控制器操作渲染的内容就能缓存在客户端。[[yii\filters\HttpCache|HttpCache]] 过滤器仅对 `GET` 和 `HEAD` 请求生效,它能为这些请求设置三种与缓存有关的 HTTP 头。 + +* [[yii\filters\HttpCache::lastModified|Last-Modified]] +* [[yii\filters\HttpCache::etagSeed|Etag]] +* [[yii\filters\HttpCache::cacheControlHeader|Cache-Control]] + + +## `Last-Modified` 头 + +`Last-Modified` 头使用时间戳标明页面自上次客户端缓存后是否被修改过。 + +通过配置 [[yii\filters\HttpCache::lastModified]] 属性向客户端发送 `Last-Modified` 头。该属性的值应该为 PHP callable 类型,返回的是页面修改时的 Unix 时间戳。该 callable 的参数和返回值应该如下: + +```php +/** + * @param Action $action 当前处理的操作对象 + * @param array $params “params” 属性的值 + * @return integer 页面修改时的 Unix 时间戳 + */ +function ($action, $params) +``` + +以下是使用 `Last-Modified` 头的示例: + +```php +public function behaviors() +{ + return [ + [ + 'class' => 'yii\filters\HttpCache', + 'only' => ['index'], + 'lastModified' => function ($action, $params) { + $q = new \yii\db\Query(); + return $q->from('post')->max('updated_at'); + }, + ], + ]; +} +``` + +上述代码表明 HTTP 缓存只在 `index` 操作时启用。它会基于页面最后修改时间生成一个 `Last-Modified` HTTP 头。当浏览器第一次访问 `index` 页时,服务器将会生成页面并发送至客户端浏览器。之后客户端浏览器在页面没被修改期间访问该页,服务器将不会重新生成页面,浏览器会使用之前客户端缓存下来的内容。因此服务端渲染和内容传输都将省去。 + + +## `ETag` 头 + +“Entity Tag”(实体标签,简称 ETag)使用一个哈希值表示页面内容。如果页面被修改过,哈希值也会随之改变。通过对比客户端的哈希值和服务器端生成的哈希值,浏览器就能判断页面是否被修改过,进而决定是否应该重新传输内容。 + +通过配置 [[yii\filters\HttpCache::etagSeed]] 属性向客户端发送 `ETag` 头。该属性的值应该为 PHP callable 类型,返回的是一段种子字符用来生成 ETag 哈希值。该 callable 的参数和返回值应该如下: + +```php +/** + * @param Action $action 当前处理的操作对象 + * @param array $params “params” 属性的值 + * @return string 一段种子字符用来生成 ETag 哈希值 + */ +function ($action, $params) +``` + +以下是使用 `ETag` 头的示例: + +```php +public function behaviors() +{ + return [ + [ + 'class' => 'yii\filters\HttpCache', + 'only' => ['view'], + 'etagSeed' => function ($action, $params) { + $post = $this->findModel(\Yii::$app->request->get('id')); + return serialize([$post->title, $post->content]); + }, + ], + ]; +} +``` + +上述代码表明 HTTP 缓存只在 `view` 操作时启用。它会基于用户请求的标题和内容生成一个 `ETag` HTTP 头。当浏览器第一次访问 `view` 页时,服务器将会生成页面并发送至客户端浏览器。之后客户端浏览器标题和内容没被修改在期间访问该页,服务器将不会重新生成页面,浏览器会使用之前客户端缓存下来的内容。因此服务端渲染和内容传输都将省去。 + +ETag 相比 `Last-Modified` 能实现更复杂和更精确的缓存策略。例如,当站点切换到另一个主题时可以使 ETag 失效。 + +复杂的 Etag 生成种子可能会违背使用 `HttpCache` 的初衷而引起不必要的性能开销,因为响应每一次请求都需要重新计算 Etag。请试着找出一个最简单的表达式去触发 Etag 失效。 + + +> 注意:为了遵循 [RFC 2616, section 13.3.4(HTTP 协议)](http://tools.ietf.org/html/rfc2616#section-13.3.4),如果同时配置了 `ETag` 和 `Last-Modified` 头,`HttpCache` 将会同时发送它们,因此它们将被同时用于客户端的缓存失效校验。 + + +## `Cache-Control` 头 + +`Cache-Control` 头指定了页面的常规缓存策略。可以通过配置 [[yii\filters\HttpCache::cacheControlHeader]] 属性发送相应的头信息。默认发送以下头: + +``` +Cache-Control: public, max-age=3600 +``` + +## 会话缓存限制器 + +当页面使 session 时,PHP 将会按照 PHP.INI 中所设置的 `session.cache_limiter` 值自动发送一些缓存相关的 HTTP 头。这些 HTTP 头有可能会干扰你原本设置的 `HttpCache` 或让其失效。为了避免此问题,默认情况下 `HttpCache` 禁止自动发送这些头。想改变这一行为,可以配置 [[yii\filters\HttpCache::sessionCacheLimiter]] 属性。该属性接受一个字符串值,包括 `public`,`private`,`private_no_expire`,和 `nocache`。请参考 PHP 手册中的[缓存限制器](http://www.php.net/manual/en/function.session-cache-limiter.php)了解这些值的含义。 + + +## SEO 影响 + +搜索引擎趋向于遵循站点的缓存头。因为一些爬虫的抓取频率有限制,启用缓存头可以可以减少重复请求数量,增加爬虫抓取效率(译者:大意如此,但搜索引擎的排名规则不了解,好的缓存策略应该是可以为用户体验加分的)。 + diff --git a/docs/guide-zh-CN/caching-overview.md b/docs/guide-zh-CN/caching-overview.md index 196087c..26f6c58 100644 --- a/docs/guide-zh-CN/caching-overview.md +++ b/docs/guide-zh-CN/caching-overview.md @@ -1,12 +1,9 @@ 缓存 ======= -缓存是提升 Web 应用性能的简便有效的方式。通过将相对静态的数据存储到缓存并在收到请求时取回缓存, -应用程序便节省了每次都要重新生成这些数据所需的时间。 +缓存是提升 Web 应用性能简便有效的方式。通过将相对静态的数据存储到缓存并在收到请求时取回缓存,应用程序便节省了每次重新生成这些数据所需的时间。 -缓存可以发生在 Web 应用程序的任何层级任何位置。在服务器端,在较的低层面,缓存可能用于存储基础数据, -例如从数据库中取出的最新文章列表;在较高的层面,缓存可能用于存储一段或整个 Web 页面,例如最新文章 -的渲染结果。在客户端,HTTP 缓存可能用于将最近访问的页面内容存储到浏览器缓存中。 +缓存可以应用在 Web 应用程序的任何层级任何位置。在服务器端,在较的低层面,缓存可能用于存储基础数据,例如从数据库中取出的最新文章列表;在较高的层面,缓存可能用于存储一段或整个 Web 页面,例如最新文章的渲染结果。在客户端,HTTP 缓存可能用于将最近访问的页面内容存储到浏览器缓存中。 Yii 支持如上所有缓存机制: diff --git a/docs/guide-zh-CN/caching-page.md b/docs/guide-zh-CN/caching-page.md new file mode 100644 index 0000000..a7275a2 --- /dev/null +++ b/docs/guide-zh-CN/caching-page.md @@ -0,0 +1,40 @@ +页面缓存 +============ + +页面缓存指的是在服务器端缓存整个页面的内容。随后当同一个页面被请求时,内容将从缓存中取出,而不是重新生成。 + +页面缓存由 [[yii\filters\PageCache]] 类提供支持,该类是一个[过滤器](structure-filters.md)。它可以像这样在控制器类中使用: + +```php +public function behaviors() +{ + return [ + [ + 'class' => 'yii\filters\PageCache', + 'only' => ['index'], + 'duration' => 60, + 'variations' => [ + \Yii::$app->language, + ], + 'dependency' => [ + 'class' => 'yii\caching\DbDependency', + 'sql' => 'SELECT COUNT(*) FROM post', + ], + ], + ]; +} +``` + +上述代码表示页面缓存只在 `index` 操作时启用,页面内容最多被缓存 60 秒,会随着当前应用的语言更改而变化。如果文章总数发生变化则缓存的页面会失效。 + +如你所见,页面缓存和[片段缓存](caching-fragment.md)极其相似。它们都支持 `duration`,`dependencies`,`variations` 和 `enabled` 配置选项。它们的主要区别是页面缓存是由[过滤器](structure-filters.md)实现,而片段缓存则是一个[小部件](structure-widgets.md)。 + +你可以在使用页面缓存的同时,使用[片段缓存](caching-fragment.md)和[动态内容](caching-fragment.md#dynamic-content)。 + + + + + + + + diff --git a/docs/guide-zh-CN/concept-aliases.md b/docs/guide-zh-CN/concept-aliases.md index 4d07628..e1045b3 100644 --- a/docs/guide-zh-CN/concept-aliases.md +++ b/docs/guide-zh-CN/concept-aliases.md @@ -1,15 +1,13 @@ 别名(Aliases) ======= -别名(译者注:指路径/URL 别名,简称别名)用作代表文件路径和 URL,主要为了避免在代码中硬编码一些绝对路径和 -URL。一个别名必须以 `@` 字符开头,以区别于传统的文件/目录路径或 URL。举栗,别名 `@yii` -指的是 Yii 框架本身的安装目录,而 `@web` 表示的是当前运行应用的根 URL(base URL)。 +别名用来表示文件路径和 URL,这样就避免了在代码中硬编码一些绝对路径和 URL。一个别名必须以 `@` 字符开头,以区别于传统的文件路径和 URL。Yii 预定义了大量可用的别名。例如,别名 `@yii` 指的是 Yii 框架本身的安装目录,而 `@web` 表示的是当前运行应用的根 URL。 定义别名 ---------------- -你可以调用 [[Yii::setAlias()]] 来给指定路径/URL 定义别名。栗子: +你可以调用 [[Yii::setAlias()]] 来给文件路径或 URL 定义别名: ```php // 文件路径的别名 @@ -19,22 +17,17 @@ Yii::setAlias('@foo', '/path/to/foo'); Yii::setAlias('@bar', 'http://www.example.com'); ``` -> 注意:别名所指向的文件路径或 URL 不一定是真实存在的文件或资源哦。 +> 注意:别名所指向的文件路径或 URL 不一定是真实存在的文件或资源。 -用一个别名,你能通过在后面接续斜杠 `/` 以及若干路径片段得到一个新的别名(无需调用 -[[Yii::setAlias()]])。我们把通过 [[Yii::setAlias()]] 定义的别名成为根别名 -*root aliases*,而用他们衍生出去的别名成为衍生别名 *derived aliases*。比如,`@foo` 就是跟别名,而 `@foo/bar/file.php` -是一个衍生别名。 +可以通过在一个别名后面加斜杠 `/` 和一至多个路径分段生成新别名(无需调用 [[Yii::setAlias()]])。我们把通过 [[Yii::setAlias()]] 定义的别名称为**根别名**,而用他们衍生出去的别名成为**衍生别名**。例如,`@foo` 就是跟别名,而 `@foo/bar/file.php` 是一个衍生别名。 -你还可以用别名定义新别名(根别名与衍生别名均可): +你还可以用别名去定义新别名(根别名与衍生别名均可): ```php Yii::setAlias('@foobar', '@foo/bar'); ``` -根别名通常在 [引导(bootstrapping)](runtime-bootstrapping.md) 阶段定义。比如你可以在 -[入口脚本](structure-entry-scripts.md) 里调用 [[Yii::setAlias()]]。为了方便起见呢,[应用主体(Application)](structure-applications.md) -提供了一个名为 `aliases` 的可写属性,你可以在应用[配置文件](concept-configurations.md)中设置它,就像这样: +根别名通常在[引导](runtime-bootstrapping.md)阶段定义。比如你可以在[入口脚本](structure-entry-scripts.md)里调用 [[Yii::setAlias()]]。为了方便起见,[应用](structure-applications.md)提供了一个名为 `aliases` 的可写属性,你可以在应用[配置](concept-configurations.md)中设置它,就像这样: ```php return [ @@ -50,25 +43,25 @@ return [ 解析别名 ----------------- -你可以调用 [[Yii::getAlias()]] 命令来解析一个根别名到他所对应的文件路径或 URL。同样的页面也可以用于解析衍生别名。比如: +你可以调用 [[Yii::getAlias()]] 命令来解析根别名到对应的文件路径或 URL。同样的页面也可以用于解析衍生别名。例如: ```php -echo Yii::getAlias('@foo'); // 显示:/path/to/foo -echo Yii::getAlias('@bar'); // 显示:http://www.example.com -echo Yii::getAlias('@foo/bar/file.php'); // 显示:/path/to/foo/bar/file.php +echo Yii::getAlias('@foo'); // 输出:/path/to/foo +echo Yii::getAlias('@bar'); // 输出:http://www.example.com +echo Yii::getAlias('@foo/bar/file.php'); // 输出:/path/to/foo/bar/file.php ``` -由衍生别名所代指的路径/URL 是通过替换掉衍生别名中的根别名部分得到的。 +由衍生别名所解析出的文件路径和 URL 是通过替换掉衍生别名中的根别名部分得到的。 -> 注意:[[Yii::getAlias()]] 不检查结果路径/URL 所指向的资源是否真实存在。 +> 注意:[[Yii::getAlias()]] 并不检查结果路径/URL 所指向的资源是否真实存在。 -根别名可能也会包含斜杠 `/` 字符。[[Yii::getAlias()]] 足够聪明,能知道一个别名中的哪个部分是根别名,因此能正确解析文件路径/URL。比如: +根别名可能也会包含斜杠 `/`。[[Yii::getAlias()]] 足够智能到判断一个别名中的哪部分是根别名,因此能正确解析文件路径/URL。例如: ```php Yii::setAlias('@foo', '/path/to/foo'); Yii::setAlias('@foo/bar', '/path2/bar'); -Yii::getAlias('@foo/test/file.php'); // 显示:/path/to/foo/test/file.php -Yii::getAlias('@foo/bar/file.php'); // 显示:/path2/bar/file.php +echo Yii::getAlias('@foo/test/file.php'); // 输出:/path/to/foo/test/file.php +echo Yii::getAlias('@foo/bar/file.php'); // 输出:/path2/bar/file.php ``` 若 `@foo/bar` 未被定义为根别名,最后一行语句会显示为 `/path/to/foo/bar/file.php`。 @@ -77,8 +70,7 @@ Yii::getAlias('@foo/bar/file.php'); // 显示:/path2/bar/file.php 使用别名 ------------- -别名在 Yii 的很多地方都会被正确识别,而无需调用 [[Yii::getAlias()]] -来把它们转换为路径/URL。比如,[[yii\caching\FileCache::cachePath]] 能同时接受文件路径或是代表文件路径的别名,多亏了 `@` 前缀,它区分开了文件路径与别名。 +别名在 Yii 的很多地方都会被正确识别,无需调用 [[Yii::getAlias()]] 来把它们转换为路径/URL。例如,[[yii\caching\FileCache::cachePath]] 能同时接受文件路径或是指向文件路径的别名,因为通过 `@` 前缀能区分它们。 ```php use yii\caching\FileCache; @@ -88,14 +80,13 @@ $cache = new FileCache([ ]); ``` -请关注下 API 文档了解属性或方法参数是否支持别名。 +请关注 API 文档了解特定属性或方法参数是否支持别名。 预定义的别名 ------------------ -Yii 预定义了一系列别名来简化频繁引用常用路径和 URL的需求。 -在核心框架中已经预定义有以下别名: +Yii 预定义了一系列别名来简化常用路径和 URL的使用: - `@yii` - `BaseYii.php` 文件所在的目录(也被称为框架安装目录) - `@app` - 当前运行的应用 [[yii\base\Application::basePath|根路径(base path)]] @@ -104,15 +95,13 @@ Yii 预定义了一系列别名来简化频繁引用常用路径和 URL的需求 - `@webroot` - 当前运行应用的 Web 入口目录 - `@web` - 当前运行应用的根 URL -`@yii` 别名是在[入口脚本](structure-entry-scripts.md)里包含 `Yii.php` 文件时定义的,其他的别名都是在[配置应用](concept-configurations.md)的时候,于应用的构造器内定义的。 +`@yii` 别名是在[入口脚本](structure-entry-scripts.md)里包含 `Yii.php` 文件时定义的,其他的别名都是在[配置应用](concept-configurations.md)的时候,于应用的构造方法内定义的。 扩展的别名 ----------------- -每一个通过 Composer 安装的 [扩展](structure-extensions.md) 都自动添加了一个别名。该别名会以该扩展在 `composer.json` -文件中所声明的根命名空间为名,且他直接代指该包的根目录。比如,如果你安装有 `yiisoft/yii2-jui` 扩展,你会自动得到 -`@yii/jui` 别名,它定义于[引导启动](runtime-bootstrapping.md)阶段: +每一个通过 Composer 安装的 [扩展](structure-extensions.md) 都自动添加了一个别名。该别名会以该扩展在 `composer.json` 文件中所声明的根命名空间为名,且他直接代指该包的根目录。例如,如果你安装有 `yiisoft/yii2-jui` 扩展,会自动得到 `@yii/jui` 别名,它定义于[引导启动](runtime-bootstrapping.md)阶段: ```php Yii::setAlias('@yii/jui', 'VendorPath/yiisoft/yii2-jui'); diff --git a/docs/guide-zh-CN/concept-autoloading.md b/docs/guide-zh-CN/concept-autoloading.md index b2fd95f..ee6ca92 100644 --- a/docs/guide-zh-CN/concept-autoloading.md +++ b/docs/guide-zh-CN/concept-autoloading.md @@ -1,7 +1,7 @@ 类自动加载(Autoloading) ================= -Yii 依靠[类自动加载机制(http://www.php.net/manual/en/language.oop5.autoload.php)来定位和包含所需的类文件。它提供一个高性能且完美支持[PSR-4 标准](https://github.com/php-fig/fig-standards/blob/master/proposed/psr-4-autoloader/psr-4-autoloader.md)([中文汉化](https://github.com/hfcorriez/fig-standards/blob/zh_CN/%E6%8E%A5%E5%8F%97/PSR-4-autoloader.md))的自动加载器。该自动加载器会在引入框架文件 `Yii.php` 时安装好。 +Yii 依靠[类自动加载机制](http://www.php.net/manual/en/language.oop5.autoload.php)来定位和包含所需的类文件。它提供一个高性能且完美支持[PSR-4 标准](https://github.com/php-fig/fig-standards/blob/master/proposed/psr-4-autoloader/psr-4-autoloader.md)([中文汉化](https://github.com/hfcorriez/fig-standards/blob/zh_CN/%E6%8E%A5%E5%8F%97/PSR-4-autoloader.md))的自动加载器。该自动加载器会在引入框架文件 `Yii.php` 时安装好。 > 注意:为了简化叙述,本篇文档中我们只会提及类的自动加载。不过,要记得文中的描述同样也适用于接口和Trait(特质)的自动加载哦。 diff --git a/docs/guide-zh-CN/concept-behaviors.md b/docs/guide-zh-CN/concept-behaviors.md index 94cd550..f5e66cd 100644 --- a/docs/guide-zh-CN/concept-behaviors.md +++ b/docs/guide-zh-CN/concept-behaviors.md @@ -1,58 +1,97 @@ -行为(Behavior) +行为 ========= -行为(Behavior)是 [[yii\base\Behmixinsavior]] 或其子类的实例。 Behavior 也被称为 -[Mixin(Mix In可以理解为包含若干个类的部分方法和属性的混合类,英文维基)](http://en.wikipedia.org/wiki/Mixin),允许你增强已有 -[[yii\base\Component|组件]] 类的功能,而无需改变类的继承结构。当一个行为被配属到组件之上是,他会向组件“注入”他的属性与方法,就好像这些方法原本就定义在组件里一样。此外,行为能响应由组件所触发的[事件](basic-events.md),从而自定义或调整组件内默认的代码执行。 +行为是 [[yii\base\Behavior]] 或其子类的实例。行为,也称为 [mixins](http://en.wikipedia.org/wiki/Mixin),可以无须改变类继承关系即可增强一个已有的 [[yii\base\Component|组件]] 类功能。当行为附加到组件后,它将“注入”它的方法和属性到组件,然后可以像访问组件内定义的方法和属性一样访问它们。此外,行为通过组件能响应被触发的[事件](basic-events.md),从而自定义或调整组件正常执行的代码。 -> 译者注:mixin直译为‘混入’,trait直译为‘特质’,为避免翻译上的问题,今后我们还是采用英文术语。 +定义行为 +----------- -使用行为 ---------------- +要定义行为,通过继承 [[yii\base\Behavior]] 或其子类来建立一个类。如: -要使用行为,你首先需要把它配属到某个[[yii\base\Component|组件]]上。我们会在接下来的章节内讲解如何配属一个行为。 +```php +namespace app\components; -行为被配属到组件之后,它的用法是很直截了当的。 +use yii\base\Model; +use yii\base\Behavior; -可以通过行为所配属的组件,访问它的 *public* 成员变量或由 getter 和/或 setter 定义的[属性](concept-properties.md),就像这样: +class MyBehavior extends Behavior +{ + public $prop1; -```php -// "prop1" 是一个定义在行为类中的属性 -echo $component->prop1; -$component->prop1 = $value; -``` + private $_prop2; -与之相似的,你也可以调用行为类的 *public* 方法, + public function getProp2() + { + return $this->_prop2; + } -```php -// foo() 是一个定义在行为类中的公共方法 -$component->foo(); + public function setProp2($value) + { + $this->_prop2 = $value; + } + + public function foo() + { + // ... + } +} ``` -如你所见,尽管 `$component` 并没有定义 `prop1` 和 `bar()`,它们依旧好像是组件自身定义的一部分一样。 +以上代码定义了行为类 `app\components\MyBehavior` 并为要附加行为的组件提供了两个属性 `prop1` 、 `prop2` 和一个方法 `foo()` 。注意属性 `prop2` 是通过 getter `getProp2()` 和 setter `setProp2()` 定义的。能这样用是因为 [[yii\base\Object]] 是 [[yii\base\Behavior]] 的祖先类,此祖先类支持用 getter 和 setter 方法定义[属性](basic-properties.md) -如果两个行为都定义了一样的属性或方法,并且它们都配属到同一个组件,那么先附加上的行为在属性或方法被访问时就有优先权。 +> 提示:在行为内部可以通过 [[yii\base\Behavior::owner]] 属性访问行为已附加的组件。 -当行为配属到组件时可以关联一个行为名。此时就能使用这个名称来访问行为对象,如下所示: + +处理事件 +------------- + +如果要让行为响应对应组件的事件触发,就应覆写 [[yii\base\Behavior::events()]] 方法,如: ```php -$behavior = $component->getBehavior('myBehavior'); +namespace app\components; + +use yii\db\ActiveRecord; +use yii\base\Behavior; + +class MyBehavior extends Behavior +{ + // 其它代码 + + public function events() + { + return [ + ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate', + ]; + } + + public function beforeValidate($event) + { + // 处理器方法逻辑 + } +} ``` -也能获取组件所配属的所有行为: +[[yii\base\Behavior::events()|events()]] 方法返回事件列表和相应的处理器。上例声明了 [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] 事件和它的处理器 `beforeValidate()` 。当指定一个事件处理器时,要使用以下格式之一: + +* 指向行为类的方法名的字符串,如上例所示; +* 对象或类名和方法名的数组,如 `[$object, 'methodName']`; +* 匿名方法。 + +处理器的格式如下,其中 `$event` 指向事件参数。关于事件的更多细节请参考[事件](basic-events.md): ```php -$behaviors = $component->getBehaviors(); +function ($event) { +} ``` -配属行为 -------------------- +附加行为 +---------- -可以选择静态或动态地配属行为到 [[yii\base\Component|组件]]。在具体实践中,前者更常见。 +可以静态或动态地附加行为到[[yii\base\Component|组件]]。前者在实践中更常见。 -要静态配属行为,重写目标组件类的 [[yii\base\Component::behaviors()|behaviors()]] 方法即可。如: +要静态附加行为,覆写行为要附加的组件类的 [[yii\base\Component::behaviors()|behaviors()]] 方法即可。[[yii\base\Component::behaviors()|behaviors()]] 方法应该返回行为[配置](basic-configs.md)列表。每个行为配置可以是行为类名也可以是配置数组。如: ```php namespace app\models; @@ -65,20 +104,20 @@ class User extends ActiveRecord public function behaviors() { return [ - // 匿名行为 => 行为类名 + // 匿名行为,只有行为类名 MyBehavior::className(), - // 命名行为 => 行为类名 + // 命名行为,只有行为类名 'myBehavior2' => MyBehavior::className(), - // 匿名行为 => 配置数组 + // 匿名行为,配置数组 [ 'class' => MyBehavior::className(), 'prop1' => 'value1', 'prop2' => 'value2', ], - // 命名行为 => 配置数组 + // 命名行为,配置数组 'myBehavior4' => [ 'class' => MyBehavior::className(), 'prop1' => 'value1', @@ -89,22 +128,20 @@ class User extends ActiveRecord } ``` -[[yii\base\Component::behaviors()|behaviors()]] 方法应该返回一个包含所有行为[配置信息](concept-configurations.md)的列表。每个行为的配置信息可以是行为的类名也可以是其配置数组。 - -通过为行为配置信息指定相应的键名,可以给行为关联一个名称。这种行为称为**命名行为**。在上例中存在两个命名行为:`myBehavior2` 和 `myBehavior4` 。同理如果行为没有关联名称就是**匿名行为**。 +通过指定行为配置数组相应的键可以给行为关联一个名称。这种行为称为**命名行为**。上例中,有两个命名行为:`myBehavior2` 和 `myBehavior4` 。如果行为没有指定名称就是**匿名行为**。 -要动态地配属行为,只需调用目标组件的 [[yii\base\Component::attachBehavior()]] 方法即可,如: +要动态附加行为,在对应组件里调用 [[yii\base\Component::attachBehavior()]] 方法即可,如: ```php use app\components\MyBehavior; -// 配属一个行为对象 +// 附加行为对象 $component->attachBehavior('myBehavior1', new MyBehavior); -// 配属行为类 +// 附加行为类 $component->attachBehavior('myBehavior2', MyBehavior::className()); -// 配属一个配置数组 +// 附加配置数组 $component->attachBehavior('myBehavior3', [ 'class' => MyBehavior::className(), 'prop1' => 'value1', @@ -112,16 +149,16 @@ $component->attachBehavior('myBehavior3', [ ]); ``` -你也可以通过 [[yii\base\Component::attachBehaviors()]] 方法一次性配属多个行为。比如: +可以通过 [[yii\base\Component::attachBehaviors()]] 方法一次附加多个行为: ```php $component->attachBehaviors([ - 'myBehavior1' => new MyBehavior, // 一个命名行为 - MyBehavior::className(), // 一个匿名行为 + 'myBehavior1' => new MyBehavior, // 命名行为 + MyBehavior::className(), // 匿名行为 ]); ``` -如下所示,你也可以通过[配置数组](concept-configurations.md)配属行为。更多细节,请参考[配置(Configs)](concept-configurations.md#configuration-format)章节。 +还可以通过[配置](concept-configurations.md)去附加行为: ```php [ @@ -135,107 +172,68 @@ $component->attachBehaviors([ ] ``` +详情请参考[配置](concept-configurations.md#configuration-format)章节。 -拆卸行为 -------------------- -要拆卸行为,可以用行为的键名调用 [[yii\base\Component::detachBehavior()]] 方法: +使用行为 +--------------- -```php -$component->detachBehavior('myBehavior1'); -``` +使用行为,必须像前文描述的一样先把它附加到 [[yii\base\Component|component]] 类或其子类。一旦行为附加到组件,就可以直接使用它。 -也可以一次性拆卸掉**所有**的行为: +行为附加到组件后,可以通过组件访问一个行为的**公共**成员变量或 getter 和 setter 方法定义的[属性](concept-properties.md): ```php -$component->detachBehaviors(); +// "prop1" 是定义在行为类的属性 +echo $component->prop1; +$component->prop1 = $value; ``` - -定义行为 ------------------- - -要定义一个行为,只需创建新类,继承 [[yii\base\Behavior]] 或其子类。比如, +类似地也可以调用行为的**公共*方法: ```php -namespace app\components; +// bar() 是定义在行为类的公共方法 +$component->bar(); +``` -use yii\base\Model; -use yii\base\Behavior; +如你所见,尽管 `$component` 未定义 `prop1` 和 `bar()` ,它们用起来也像组件自己定义的一样。 -class MyBehavior extends Behavior -{ - public $prop1; +如果两个行为都定义了一样的属性或方法,并且它们都附加到同一个组件,那么**首先**附加上的行为在属性或方法被访问时有优先权。 - private $_prop2; +附加行为到组件时的命名行为,可以使用这个名称来访问行为对象,如下所示: - public function getProp2() - { - return $this->_prop2; - } - - public function setProp2($value) - { - $this->_prop2 = $value; - } - - public function foo() - { - // ... - } -} +```php +$behavior = $component->getBehavior('myBehavior'); ``` -以上代码定义了行为类 `app\components\MyBehavior` 并为要附加行为的组件提供了两个属性 `prop1` 、 `prop2` 和一个方法 `foo()`。注意,属性 `prop2` 是通过 getter `getProp2()` 和 setter `setProp2()` 定义的。能这样用是因为 [[yii\base\Behavior]] 的祖先类是 [[yii\base\Object]],此祖先类支持用 getter 和 setter 方法定义[属性](concept-properties.md)。 - -在行为类之中,你可以通过 [[yii\base\Behavior::owner]] 属性访问行为的组件。 - -如果你的行为需要响应其所配属的组件中触发的事件,它需要重写 [[yii\base\Behavior::events()]] 方法。像这样, +也能获取附加到这个组件的所有行为: ```php -namespace app\components; +$behaviors = $component->getBehaviors(); +``` -use yii\db\ActiveRecord; -use yii\base\Behavior; -class MyBehavior extends Behavior -{ - // ... +移除行为 +------------ - public function events() - { - return [ - ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate', - ]; - } +要移除行为,可以调用 [[yii\base\Component::detachBehavior()]] 方法用行为相关联的名字实现: - public function beforeValidate($event) - { - // ... - } -} +```php +$component->detachBehavior('myBehavior1'); ``` -[[yii\base\Behavior::events()|events()]] 方法需返回一个事件列表和它们相应的处理器。上例声明了 [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] 事件和它的处理器 `beforeValidate()` 。当指定一个事件处理器时,要使用以下格式之一: - -* 一条指向行为类的方法名的字符串,如上例所示; -* 一个包含对象或类名,以及方法名的数组,如 `[$object, 'methodName']`; -* 一个匿名函数 - -事件处理器方法的特征格式如下,其中 `$event` 指向事件参数。关于事件的更多细节请参考[事件](concept-events.md)。 +也可以移除**全部**行为: ```php -function ($event) { -} +$component->detachBehaviors(); ``` -使用 `TimestampBehavior` -------------------------- +使用 `TimestampBehavior` +---------------------------- -最后让我们来看看 [[yii\behaviors\TimestampBehavior]] 作为具体实践案例,这个行为支持在 [[yii\db\ActiveRecord|Active Record]] 保存时自动更新它的时间戳类型的 attribute(特性)。 +最后以 [[yii\behaviors\TimestampBehavior]] 的讲解来结尾,这个行为支持在 [[yii\db\ActiveRecord|Active Record]] 存储时自动更新它的时间戳属性。 -首先,配属这个行为到目标 [[yii\db\ActiveRecord|Active Record]] 类: +首先,附加这个行为到计划使用该行为的 [[yii\db\ActiveRecord|Active Record]] 类: ```php namespace app\models\User; @@ -262,49 +260,51 @@ class User extends ActiveRecord } ``` -以上行为的配置指定了下面的两条规则: +以上指定的行为数组: -* 当记录插入时,行为应该要当前时间戳赋值给 `created_at` 和 `updated_at` 属性; -* 当记录更新时,行为应该要当前时间戳赋值给 `updated_at` 属性。 +* 当记录插入时,行为将当前时间戳赋值给 `created_at` 和 `updated_at` 属性; +* 当记录更新时,行为将当前时间戳赋值给 `updated_at` 属性。 -放置上述代码之后,如果有一个 `User` 对象且需要保存,你会发现它的 `created_at` 和 `updated_at` 属性已经自动填充了当前时间戳: -```php +保存 `User` 对象,将会发现它的 `created_at` 和 `updated_at` 属性自动填充了当前时间戳: + +``php $user = new User; $user->email = 'test@example.com'; $user->save(); -echo $user->created_at; // shows the current timestamp +echo $user->created_at; // 显示当前时间戳 ``` -[[yii\behaviors\TimestampBehavior|TimestampBehavior]] 同时提供一个很好用的名为 [[yii\behaviors\TimestampBehavior::touch()|touch()]] 的方法,该方法会把当前时间戳赋值给指定 attribute 并将其存入数据库: +[[yii\behaviors\TimestampBehavior|TimestampBehavior]] 行为还提供了一个有用的方法 [[yii\behaviors\TimestampBehavior::touch()|touch()]],这个方法能将当前时间戳赋值给指定属性并保存到数据库: ```php $user->touch('login_time'); ``` -与 Traits 的对比 ----------------------- +与 PHP traits 的比较 +------------------- 尽管行为在 "注入" 属性和方法到主类方面类似于 [traits](http://www.php.net/traits) ,它们在很多方面却不相同。如上所述,它们各有利弊。它们更像是互补的而不是相互替代。 -### Behavior 的优势 +### 行为的优势 + +行为类像普通类支持继承。另一方面,traits 可以视为 PHP 语言支持的复制粘贴功能,它不支持继承。 -Behavior 类像普通类支持继承。另一方面,Traits 可以视之为一种 PHP 提供语言级支持的复制粘贴功能,它不支持继承。 +行为无须修改组件类就可动态附加到组件或移除。要使用 traits,必须修改使用它的类。 -Behavior 无须修改组件类就可动态配属到组件或拆除。要使用 trait,就必须修改引用它的类本身。 +行为是可配置的而 traits 不能。 -Behavior 是可配置的而 traits 不行。 +行为以响应事件来自定义组件的代码执行。 -Behaviors 可以通过响应事件来自定义组件代码的执行。 +当不同行为附加到同一组件产生命名冲突时,这个冲突通过先附加行为的优先权自动解决。而由不同 traits 引发的命名冲突需要通过手工重命名冲突属性或方法来解决。 -当不同 Behavior 附加到同一组件产生命名冲突时,这个冲突会以“先附加行为的优先”的方式自动解决。而由不同 traits 引发的命名冲突需要通过手工重命名冲突属性或方法来解决。 +### traits 的优势 -### Traits 的优势 +traits 比起行为更高效,因为行为是对象,消耗时间和内存。 -Trait 比 behaviors 性能好很多很多,因为行为本身就是对象,他需要占用双倍的时间和内存。 +IDE 对 traits 更友好,因为它们是语言结构。 -作为语言架构的一部分,IDE 对 Trait 更加友好 diff --git a/docs/guide-zh-CN/concept-di-container.md b/docs/guide-zh-CN/concept-di-container.md index 620cb8d..2f8050f 100644 --- a/docs/guide-zh-CN/concept-di-container.md +++ b/docs/guide-zh-CN/concept-di-container.md @@ -1,9 +1,7 @@ 依赖注入容器 ============================== -依赖注入(Dependency Injection,缩写 DI)容器就是一个对象,它知道怎样初始化并配置对象及其依赖的所有对象。 -[Martin 的文章](http://martinfowler.com/articles/injection.html) 已经解释了 DI 容器为什么很有用。这里我们主要讲解 Yii 提供的 DI -容器的使用方法。 +依赖注入(Dependency Injection,DI)容器就是一个对象,它知道怎样初始化并配置对象及其依赖的所有对象。[Martin 的文章](http://martinfowler.com/articles/injection.html) 已经解释了 DI 容器为什么很有用。这里我们主要讲解 Yii 提供的 DI 容器的使用方法。 依赖注入 @@ -11,16 +9,14 @@ Yii 通过 [[yii\di\Container]] 类提供 DI 容器特性。它支持如下几种类型的依赖注入: -* 构造器注入; +* 构造方法注入; * Setter 和属性注入; * PHP 回调注入. -### 构造器注入 +### 构造方法注入 -在构造器参数类型提示的帮助下,DI 容器实现了构造器注入。 -当容器被用于创建一个新对象时,类型提示会告诉它要依赖什么类或接口。容器会尝试获取它所依赖的类或接口的实例,然后通过构造器将其注入新的对象。 -例如: +在参数类型提示的帮助下,DI 容器实现了构造方法注入。当容器被用于创建一个新对象时,类型提示会告诉它要依赖什么类或接口。容器会尝试获取它所依赖的类或接口的实例,然后通过构造器将其注入新的对象。例如: ```php class Foo @@ -39,8 +35,7 @@ $foo = new Foo($bar); ### Setter 和属性注入 -Setter 和属性注入是通过[配置](concept-configurations.md)提供支持的。当注册一个依赖或创建一个新对象时,你可以提供一个配置,该配置会 -提供给容器用于通过相应的 Setter 或属性注入依赖。例如: +Setter 和属性注入是通过[配置](concept-configurations.md)提供支持的。当注册一个依赖或创建一个新对象时,你可以提供一个配置,该配置会提供给容器用于通过相应的 Setter 或属性注入依赖。例如: ```php use yii\base\Object; @@ -71,7 +66,7 @@ $container->get('Foo', [], [ ### PHP 回调注入 -这种情况下,容器将使用一个注册过的 PHP 回调构建一个类的新实例。回调负责解决依赖并将其恰当地注入新创建的对象。例如: +这种情况下,容器将使用一个注册过的 PHP 回调创建一个类的新实例。回调负责解决依赖并将其恰当地注入新创建的对象。例如: ```php $container->set('Foo', function () { @@ -85,8 +80,7 @@ $foo = $container->get('Foo'); 注册依赖关系 ------------------------ -你可以用 [[yii\di\Container::set()]] 注册依赖关系。注册会用到一个依赖关系名称和一个依赖关系的定义。依赖关系名称可以是一个类名,一个接 -口名或者一个别名。依赖关系的定义可以是一个类名,一个配置数组,或者一个 PHP 回调。 +可以用 [[yii\di\Container::set()]] 注册依赖关系。注册会用到一个依赖关系名称和一个依赖关系的定义。依赖关系名称可以是一个类名,一个接口名或一个别名。依赖关系的定义可以是一个类名,一个配置数组,或者一个 PHP 回调。 ```php $container = new \yii\di\Container; @@ -134,8 +128,7 @@ $container->set('pageCache', new FileCache); > Tip: 如果依赖关系名称和依赖关系的定义相同,则不需要通过 DI 容器注册该依赖关系。 -每当依赖关系需要解决时,通过 `set()` 注册的依赖关系都会产生一个新实例。你可以使用 [[yii\di\Container::setSingleton()]] 注册一个依 -赖关系,它只会产生一个单例: +通过 `set()` 注册的依赖关系,在每次使用时都会产生一个新实例。可以使用 [[yii\di\Container::setSingleton()]] 注册一个单例的依赖关系: ```php $container->setSingleton('yii\db\Connection', [ @@ -150,12 +143,9 @@ $container->setSingleton('yii\db\Connection', [ 解决依赖关系 ---------------------- -依赖关系注册后,就可以使用 DI 容器创建新对象了。容器会自动解决依赖关系,将它们实例化并注入新创建的对象。依赖关系的解决是递归的,意味着如 -果一个依赖关系中还有其他依赖关系,则这些依赖关系都会被自动解决。 +注册依赖关系后,就可以使用 DI 容器创建新对象了。容器会自动解决依赖关系,将依赖实例化并注入新创建的对象。依赖关系的解决是递归的,如果一个依赖关系中还有其他依赖关系,则这些依赖关系都会被自动解决。 -你可以使用 [[yii\di\Container::get()]] 创建新的对象。该方法接收一个依赖关系名称,它可以是一个类名,一个接口名或一个别名。依赖关系名 -可能是也可能不是通过 `set()` 或 `setSingleton()` 注册的。你可以随意地提供一个类的构造器参数列表和一个 -[configuration](concept-configurations.md) 用于配置新创建的对象。例如: +可以使用 [[yii\di\Container::get()]] 创建新的对象。该方法接收一个依赖关系名称,它可以是一个类名,一个接口名或一个别名。依赖关系名或许是通过 `set()` 或 `setSingleton()` 注册的。你可以随意地提供一个类的构造器参数列表和一个[configuration](concept-configurations.md) 用于配置新创建的对象。例如: ```php // "db" 是前面定义过的一个别名 @@ -165,11 +155,9 @@ $db = $container->get('db'); $engine = $container->get('app\components\SearchEngine', [$apiKey], ['type' => 1]); ``` -代码背后,DI 容器做了比仅创建一个新对象还要多的多的工作。容器首先将检查类的构造器,找出依赖的类或接口名,然后自动递归解决这些依赖关系。 +代码背后,DI 容器做了比创建对象多的多的工作。容器首先将检查类的构造方法,找出依赖的类或接口名,然后自动递归解决这些依赖关系。 -如下代码展示了一个更复杂的示例。`UserLister` 类依赖一个实现了 `UserFinderInterface` 接口的对象;`UserFinder` 类实现了这个接口,并 -依赖于一个 `Connection` 对象。所有这些依赖关系都是通过类构造器参数的类型提示定义的。通过属性依赖关系的注册,DI 容器可以自动解决这些依赖 -关系并能通过一个简单的 `get('userLister')` 调用创建一个新的 `UserLister` 实例。 +如下代码展示了一个更复杂的示例。`UserLister` 类依赖一个实现了 `UserFinderInterface` 接口的对象;`UserFinder` 类实现了这个接口,并依赖于一个 `Connection` 对象。所有这些依赖关系都是通过类构造器参数的类型提示定义的。通过属性依赖关系的注册,DI 容器可以自动解决这些依赖关系并能通过一个简单的 `get('userLister')` 调用创建一个新的 `UserLister` 实例。 ```php namespace app\models; @@ -231,9 +219,7 @@ $lister = new UserLister($finder); 实践中的运用 --------------- -当你在应用程序的[入口脚本](structure-entry-scripts.md)中引入 `Yii.php` 文件时,Yii 就创建了一个 DI 容器。这个 DI 容器可以通过 -[[Yii::$container]] 访问。当你调用 [[Yii::createObject()]] 时,此方法实际上会调用这个容器的 [[yii\di\Container::get()|get()]] -方法创建一个新对象。如上所述,DI 容器会自动解决依赖关系(如果有)并将其注入新创建的对象中。因为 Yii 在其多数核心代码中都使用了 +当在应用程序的[入口脚本](structure-entry-scripts.md)中引入 `Yii.php` 文件时,Yii 就创建了一个 DI 容器。这个 DI 容器可以通过 [[Yii::$container]] 访问。当调用 [[Yii::createObject()]] 时,此方法实际上会调用这个容器的 [[yii\di\Container::get()|get()]] 方法创建新对象。如上所述,DI 容器会自动解决依赖关系(如果有)并将其注入新创建的对象中。因为 Yii 在其多数核心代码中都使用了 [[Yii::createObject()]] 创建新对象,所以你可以通过 [[Yii::$container]] 全局性地自定义这些对象。 例如,你可以全局性自定义 [[yii\widgets\LinkPager]] 中分页按钮的默认数量: @@ -254,8 +240,7 @@ echo \yii\widgets\LinkPager::widget(); echo \yii\widgets\LinkPager::widget(['maxButtonCount' => 20]); ``` -另一个例子是借用 DI 容器中自动构造器注入带来的好处。假设你的控制器类依赖一些其他对象,例如一个旅馆预订服务。你可以通过一个构造器参数声明 -依赖关系,然后让 DI 容器帮你自动解决这个依赖关系。 +另一个例子是借用 DI 容器中自动构造方法注入带来的好处。假设你的控制器类依赖一些其他对象,例如一个旅馆预订服务。你可以通过一个构造器参数声明依赖关系,然后让 DI 容器帮你自动解决这个依赖关系。 ```php namespace app\controllers; @@ -275,8 +260,7 @@ class HotelController extends Controller } ``` -如果你从浏览器中访问这个控制器,你将看到一个报错信息,提醒你 `BookingInterface` 无法被实例化。这是因为你需要告诉 DI 容器怎样处理这个 -依赖关系。 +如果你从浏览器中访问这个控制器,你将看到一个报错信息,提醒你 `BookingInterface` 无法被实例化。这是因为你需要告诉 DI 容器怎样处理这个依赖关系。 ```php \Yii::$container->set('app\components\BookingInterface', 'app\components\BookingService'); @@ -297,9 +281,7 @@ class HotelController extends Controller 总结 ------- -依赖注入和[服务定位器](concept-service-locator.md) 都是流行的设计模式,它们使你可以用充分解耦且更利于测试的风格构建软件。我们强烈推荐 -你阅读 [Martin 的文章](http://martinfowler.com/articles/injection.html) ,对依赖注入和服务定位器有个更深入的理解。 +依赖注入和[服务定位器](concept-service-locator.md)都是流行的设计模式,它们使你可以用充分解耦且更利于测试的风格构建软件。强烈推荐你阅读 [Martin 的文章](http://martinfowler.com/articles/injection.html) ,对依赖注入和服务定位器有个更深入的理解。 -Yii 在依赖住入(DI)容器之上实现了它的[服务定位器](concept-service-locator.md)。当一个服务定位器尝试创建一个新的对象实例时,它会把 -调用转发到 DI 容器。后者将会像前文所述那样自动解决依赖关系。 +Yii 在依赖住入(DI)容器之上实现了它的[服务定位器](concept-service-locator.md)。当一个服务定位器尝试创建一个新的对象实例时,它会把调用转发到 DI 容器。后者将会像前文所述那样自动解决依赖关系。 diff --git a/docs/guide-zh-CN/concept-events.md b/docs/guide-zh-CN/concept-events.md new file mode 100644 index 0000000..b5df466 --- /dev/null +++ b/docs/guide-zh-CN/concept-events.md @@ -0,0 +1,241 @@ +事件 +====== + +事件可以将自定义代码“注入”到现有代码中的特定执行点。附加自定义代码到某个事件,当这个事件被触发时,这些代码就会自动执行。例如,邮件程序对象成功发出消息时可触发 `messageSent` 事件。如想追踪成功发送的消息,可以附加相应追踪代码到 `messageSent` 事件。 + +Yii 引入了名为 [[yii\base\Component]] 的基类以支持事件。如果一个类需要触发事件就应该继承 [[yii\base\Component]] 或其子类。 + + +事件处理器(Event Handlers) +-------------- + +事件处理器是一个[PHP 回调函数](http://www.php.net/manual/en/language.types.callable.php),当它所附加到的事件被触发时它就会执行。可以使用以下回调函数之一: + +- 字符串形式指定的 PHP 全局函数,如 `'trim'` ; +- 对象名和方法名数组形式指定的对象方法,如 `[$object, $method]` ; +- 类名和方法名数组形式指定的静态类方法,如 `[$class, $method]` ; +- 匿名函数,如 `function ($event) { ... }` 。 + +事件处理器的格式是: + +```php +function ($event) { + // $event 是 yii\base\Event 或其子类的对象 +} +``` + +通过 `$event` 参数,事件处理器就获得了以下有关事件的信息: + +- [[yii\base\Event::name|event name]]:事件名 +- [[yii\base\Event::sender|event sender]]:调用 `trigger()` 方法的对象 +- [[yii\base\Event::data|custom data]]:附加事件处理器时传入的数据,默认为空,后文详述 + + +附加事件处理器 +---------------- + +调用 [[yii\base\Component::on()]] 方法来附加处理器到事件上。如: + +```php +$foo = new Foo; + +// 处理器是全局函数 +$foo->on(Foo::EVENT_HELLO, 'function_name'); + +// 处理器是对象方法 +$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']); + +// 处理器是静态类方法 +$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']); + +// 处理器是匿名函数 +$foo->on(Foo::EVENT_HELLO, function ($event) { + //事件处理逻辑 +}); +``` + +附加事件处理器时可以提供额外数据作为 [[yii\base\Component::on()]] 方法的第三个参数。数据在事件被触发和处理器被调用时能被处理器使用。如: + +```php +// 当事件被触发时以下代码显示 "abc" +// 因为 $event->data 包括被传递到 "on" 方法的数据 +$foo->on(Foo::EVENT_HELLO, function ($event) { + echo $event->data; +}, 'abc'); +``` + + +时间处理器顺序 +----------------- + +可以附加一个或多个处理器到一个事件。当事件被触发,已附加的处理器将按附加次序依次调用。如果某个处理器需要停止其后的处理器调用,可以设置 `$event` 参数的 [yii\base\Event::handled]] 属性为真,如下: + +```php +$foo->on(Foo::EVENT_HELLO, function ($event) { + $event->handled = true; +}); +``` + +默认新附加的事件处理器排在已存在处理器队列的最后。因此,这个处理器将在事件被触发时最后一个调用。在处理器队列最前面插入新处理器将使该处理器最先调用,可以传递第四个参数 `$append` 为假并调用 [[yii\base\Component::on()]] 方法实现: + +``php +$foo->on(Foo::EVENT_HELLO, function ($event) { + // 这个处理器将被插入到处理器队列的第一位... +}, $data, false); +``` + + +触发事件 +---------- + +事件通过调用 [[yii\base\Component::trigger()]] 方法触发,此方法须传递**事件名**,还可以传递一个事件对象,用来传递参数到事件处理器。如: + +```php +namespace app\components; + +use yii\base\Component; +use yii\base\Event; + +class Foo extends Component +{ + const EVENT_HELLO = 'hello'; + + public function bar() + { + $this->trigger(self::EVENT_HELLO); + } +} +``` + +以上代码当调用 `bar()` ,它将触发名为 `hello` 的事件。 + +> 提示:推荐使用类常量来表示事件名。上例中,常量 `EVENT_HELLO` 用来表示 `hello` 。这有两个好处。第一,它可以防止拼写错误并支持 IDE 的自动完成。第二,只要简单检查常量声明就能了解一个类支持哪些事件。 + +有时想要在触发事件时同时传递一些额外信息到事件处理器。例如,邮件程序要传递消息信息到 `messageSent` 事件的处理器以便处理器了解哪些消息被发送了。为此,可以提供一个事件对象作为 [[yii\base\Component::trigger()]] 方法的第二个参数。这个事件对象必须是 [[yii\base\Event]] 类或其子类的实例。如: + +```php +namespace app\components; + +use yii\base\Component; +use yii\base\Event; + +class MessageEvent extends Event +{ + public $message; +} + +class Mailer extends Component +{ + const EVENT_MESSAGE_SENT = 'messageSent'; + + public function send($message) + { + // ...发送 $message 的逻辑... + + $event = new MessageEvent; + $event->message = $message; + $this->trigger(self::EVENT_MESSAGE_SENT, $event); + } +} +``` + +当 [[yii\base\Component::trigger()]] 方法被调用时,它将调用所有附加到命名事件(trigger 方法第一个参数)的事件处理器。 + + +移除事件处理器 +--------------- + +从事件移除处理器,调用 [[yii\base\Component::off()]] 方法。如: + +```php +// 处理器是全局函数 +$foo->off(Foo::EVENT_HELLO, 'function_name'); + +// 处理器是对象方法 +$foo->off(Foo::EVENT_HELLO, [$object, 'methodName']); + +// 处理器是静态类方法 +$foo->off(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']); + +// 处理器是匿名函数 +$foo->off(Foo::EVENT_HELLO, $anonymousFunction); +``` + +注意当匿名函数附加到事件后一般不要尝试移除匿名函数,除非你在某处存储了它。以上示例中,假设匿名函数存储为变量 `$anonymousFunction` 。 + +移除事件的全部处理器,简单调用 [[yii\base\Component::off()]] 即可,不需要第二个参数: + +```php +$foo->off(Foo::EVENT_HELLO); +``` + +类级别的事件处理器 +------------------- + +以上部分,我们叙述了在**实例级别**如何附加处理器到事件。有时想要一个类的所有实例而不是一个指定的实例都响应一个被触发的事件,并不是一个个附加事件处理器到每个实例,而是通过调用静态方法 [[yii\base\Event::on()]] 在**类级别**附加处理器。 + +例如,[活动记录](db-active-record.md)对象要在每次往数据库新增一条新记录时触发一个 [[yii\base\ActiveRecord::EVENT_AFTER_INSERT]] 事件。要追踪每个[活动记录](db-active-record.md)对象的新增记录完成情况,应如下写代码: + +```php +use Yii; +use yii\base\Event; +use yii\db\ActiveRecord; + +Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) { + Yii::trace(get_class($event->sender) . ' is inserted'); +}); +``` + +每当 [[yii\base\ActiveRecord|ActiveRecord]] 或其子类的实例触发 [[yii\base\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] 事件时,这个事件处理器都会执行。在这个处理器中,可以通过 `$event->sender` 获取触发事件的对象。 + +当对象触发事件时,它首先调用实例级别的处理器,然后才会调用类级别处理器。 + +可调用静态方法[[yii\base\Event::trigger()]]来触发一个**类级别**事件。类级别事件不与特定对象相关联。因此,它只会引起类级别事件处理器的调用。如: + +```php +use yii\base\Event; + +Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) { + echo $event->sender; // 显示 "app\models\Foo" +}); + +Event::trigger(Foo::className(), Foo::EVENT_HELLO); +``` + +注意这种情况下 `$event->sender` 指向触发事件的类名而不是对象实例。 + +> 注意:因为类级别的处理器响应类和其子类的所有实例触发的事件,必须谨慎使用,尤其是底层的基类,如 [[yii\base\Object]]。 + +移除类级别的事件处理器只需调用[[yii\base\Event::off()]],如: + +```php +// 移除 $handler +Event::off(Foo::className(), Foo::EVENT_HELLO, $handler); + +// 移除 Foo::EVENT_HELLO 事件的全部处理器 +Event::off(Foo::className(), Foo::EVENT_HELLO); +``` + + +全局事件 +------------- + +所谓**全局事件**实际上是一个基于以上叙述的事件机制的戏法。它需要一个全局可访问的单例,如[应用](structure-applications.md)实例。 + +事件触发者不调用其自身的 `trigger()` 方法,而是调用单例的 `trigger()` 方法来触发全局事件。类似地,事件处理器被附加到单例的事件。如: + +```php +use Yii; +use yii\base\Event; +use app\components\Foo; + +Yii::$app->on('bar', function ($event) { + echo get_class($event->sender); // 显示 "app\components\Foo" +}); + +Yii::$app->trigger('bar', new Event(['sender' => new Foo])); +``` + +全局事件的一个好处是当附加处理器到一个对象要触发的事件时,不需要产生该对象。相反,处理器附加和事件触发都通过单例(如应用实例)完成。 + +然而,因为全局事件的命名空间由各方共享,应合理命名全局事件,如引入一些命名空间(例:"frontend.mail.sent", "backend.mail.sent")。 \ No newline at end of file diff --git a/docs/guide-zh-CN/concept-service-locator.md b/docs/guide-zh-CN/concept-service-locator.md index 6af646c..c9f278d 100644 --- a/docs/guide-zh-CN/concept-service-locator.md +++ b/docs/guide-zh-CN/concept-service-locator.md @@ -1,18 +1,15 @@ -服务定位器(Service Locator) +服务定位器 =============== -服务定位器是一个了解如何提供各种应用所需的服务(或组件)的对象。在一个服务定位器中,每一个组件都只有一个单独的实例,并通过 -ID 唯一地标识。用这个 ID 就能从服务定位器中得到这个组件。 +服务定位器是一个了解如何提供各种应用所需的服务(或组件)的对象。在服务定位器中,每个组件都只有一个单独的实例,并通过ID 唯一地标识。用这个 ID 就能从服务定位器中得到这个组件。 -在 Yii 中,服务定位器只是 [[yii\di\ServiceLocator]] 或其子类的一个实例。 +在 Yii 中,服务定位器是 [[yii\di\ServiceLocator]] 或其子类的一个实例。 -最最常用的服务定位器一般是 *application(应用)* 对象,可以通过 `\Yii::$app` 访问。它所提供的服务被称为 -*application components(应用组件)*,比如:`request`、`response`、`urlManager`(分别是请求、响应、Url 管理器)组件。你可以通过服务定位器所提供的功能,非常容易地配置这些组件,或甚至是用你自己的实现替换掉他们。 +最常用的服务定位器是**application(应用)**对象,可以通过 `\Yii::$app` 访问。它所提供的服务被称为**application components(应用组件)**,比如:`request`、`response`、`urlManager` 组件。可以通过服务定位器所提供的功能,非常容易地配置这些组件,或甚至是用你自己的实现替换掉他们。 -除了 Application 对象,每一个模块对象本身也是一个服务定位器。 +除了 application 对象,每个模块对象本身也是一个服务定位器。 -要使用一个服务定位器,第一步是要注册相关组件。组件可以通过 [[yii\di\ServiceLocator::set()]] -方法进行注册。以下的方法展示了注册组件的不同方法: +要使用服务定位器,第一步是要注册相关组件。组件可以通过 [[yii\di\ServiceLocator::set()]] 方法进行注册。以下的方法展示了注册组件的不同方法: ```php use yii\di\ServiceLocator; @@ -48,15 +45,11 @@ $cache = $locator->get('cache'); $cache = $locator->cache; ``` -如上文所示, [[yii\di\ServiceLocator]] 允许你通过组件 ID 像访问一个属性值那样访问一个组件。当你第一次访问某组件时, -[[yii\di\ServiceLocator]] 会通过该组件的注册信息创建一个该组件的实例,并返回它。之后,如果再次访问,则服务定位器会返回同一个实例。 +如上所示, [[yii\di\ServiceLocator]] 允许通过组件 ID 像访问一个属性值那样访问一个组件。当你第一次访问某组件时,[[yii\di\ServiceLocator]] 会通过该组件的注册信息创建一个该组件的实例,并返回它。之后,如果再次访问,则服务定位器会返回同一个实例。 -你可以通过 [[yii\di\ServiceLocator::has()]] 检查某组件 ID 是否被注册。 -若你用一个无效的 ID 调用 [[yii\di\ServiceLocator::get()]],则会抛出一个异常。 +你可以通过 [[yii\di\ServiceLocator::has()]] 检查某组件 ID 是否被注册。若你用一个无效的 ID 调用 [[yii\di\ServiceLocator::get()]],则会抛出一个异常。 -因为服务定位器,经常会在创建时附带[配置信息](concept-configurations.md),因此我们提供了一个可写的属性,名为 -[[yii\di\ServiceLocator::setComponents()|components]],这样就可以配置该属性,或一次性注册多个组件。下面的代码展示了如何用一个配置数组,配置一个应用并注册 -"db","cache" 和 "search" 三个组件: +因为服务定位器,经常会在创建时附带[配置信息](concept-configurations.md),因此我们提供了一个可写的属性,名为 [[yii\di\ServiceLocator::setComponents()|components]],这样就可以配置该属性,或一次性注册多个组件。下面的代码展示了如何用一个配置数组,配置一个应用并注册"db","cache" 和 "search" 三个组件: ```php return [ // ... diff --git a/docs/guide-zh-CN/db-dao.md b/docs/guide-zh-CN/db-dao.md index ff6e8be..3d65a06 100644 --- a/docs/guide-zh-CN/db-dao.md +++ b/docs/guide-zh-CN/db-dao.md @@ -11,7 +11,7 @@ Oracle MSSQL: version 2012 或更高版本,如需使用 LIMIT/OFFSET。 配置 -开始使用数据库首先需要配置数据库连接组件,通过添加 db 组件到应用配置实现("基础的" web 应用是 config/web.php),如下所示: +开始使用数据库首先需要配置数据库连接组件,通过添加 db 组件到应用配置实现("基础的" Web 应用是 config/web.php),如下所示: return [ // ... diff --git a/docs/guide-zh-CN/input-validation.md b/docs/guide-zh-CN/input-validation.md new file mode 100644 index 0000000..d6d6b34 --- /dev/null +++ b/docs/guide-zh-CN/input-validation.md @@ -0,0 +1,527 @@ +输入验证 +================ + +一般说来,程序猿永远不应该信任从最终用户直接接收到的数据,并且使用它们之前应始终先验证其可靠性。 + +要给 [model](structure-models.md) 填充其所需的用户输入数据,你可以调用 [[yii\base\Model::validate()]] 方法验证它们。该方法会返回一个布尔值,指明是否通过验证。若没有通过,你能通过 [[yii\base\Model::errors]] 属性获取相应的报错信息。比如, + +```php +$model = new \app\models\ContactForm; + +// 用用户输入来填充模型的特性 +$model->attributes = \Yii::$app->request->post('ContactForm'); + +if ($model->validate()) { + // 若所有输入都是有效的 +} else { + // 有效性验证失败:$errors 属性就是存储错误信息的数组 + $errors = $model->errors; +} +``` + +`validate()` 方法,在幕后为执行验证操作,进行了以下步骤: + +1. 通过从 [[yii\base\Model::scenarios()]] 方法返回基于当前 [[yii\base\Model::scenario|场景(scenario)]] 的特性属性列表,算出哪些特性应该进行有效性验证。这些属性被称作 *active attributes*(激活特性)。 +2. 通过从 [[yii\base\Model::rules()]] 方法返回基于当前 [[yii\base\Model::scenario|场景(scenario)]] 的验证规则列表,这些规则被称作 *active rules*(激活规则)。 +3. 用每个激活规则去验证每个与之关联的激活特性。若失败,则记录下对应模型特性的错误信息。 + + +## 声明规则(Rules) + +要让 `validate()` 方法起作用,你需要声明与需验证模型特性相关的验证规则。为此,需要重写 [[yii\base\Model::rules()]] 方法。下面的例子展示了如何声明用于验证 `ContactForm` 模型的相关验证规则: + +```php +public function rules() +{ + return [ + // name,email,subject 和 body 特性是 `require`(必填)的 + [['name', 'email', 'subject', 'body'], 'required'], + + // email 特性必须是一个有效的 email 地址 + ['email', 'email'], + ]; +} +``` + +[[yii\base\Model::rules()|rules()]] 方法应返回一个由规则所组成的数组,每一个规则都呈现为以下这类格式的小数组: + +```php +[ + // required, specifies which attributes should be validated by this rule. + // For a single attribute, you can use the attribute name directly + // without having it in an array instead of an array + ['attribute1', 'attribute2', ...], + + // required, specifies the type of this rule. + // It can be a class name, validator alias, or a validation method name + 'validator', + + // optional, specifies in which scenario(s) this rule should be applied + // if not given, it means the rule applies to all scenarios + // You may also configure the "except" option if you want to apply the rule + // to all scenarios except the listed ones + 'on' => ['scenario1', 'scenario2', ...], + + // optional, specifies additional configurations for the validator object + 'property1' => 'value1', 'property2' => 'value2', ... +] +``` + +For each rule you must specify at least which attributes the rule applies to and what is the type of the rule. +You can specify the rule type in one of the following forms: + +* the alias of a core validator, such as `required`, `in`, `date`, etc. Please refer to + the [Core Validators](tutorial-core-validators.md) for the complete list of core validators. +* the name of a validation method in the model class, or an anonymous function. Please refer to the + [Inline Validators](#inline-validators) subsection for more details. +* the name of a validator class. Please refer to the [Standalone Validators](#standalone-validators) + subsection for more details. + +A rule can be used to validate one or multiple attributes, and an attribute may be validated by one or multiple rules. +A rule may be applied in certain [scenarios](structure-models.md#scenarios) only by specifying the `on` option. +If you do not specify an `on` option, it means the rule will be applied to all scenarios. + +When the `validate()` method is called, it does the following steps to perform validation: + +1. Determine which attributes should be validated by checking the current [[yii\base\Model::scenario|scenario]] + against the scenarios declared in [[yii\base\Model::scenarios()]]. These attributes are the active attributes. +2. Determine which rules should be applied by checking the current [[yii\base\Model::scenario|scenario]] + against the rules declared in [[yii\base\Model::rules()]]. These rules are the active rules. +3. Use each active rule to validate each active attribute which is associated with the rule. + +According to the above validation steps, an attribute will be validated if and only if it is +an active attribute declared in `scenarios()` and is associated with one or multiple active rules +declared in `rules()`. + + +### 自定义错误信息 + +Most validators have default error messages that will be added to the model being validated when its attributes +fail the validation. For example, the [[yii\validators\RequiredValidator|required]] validator will add +a message "Username cannot be blank." to a model when the `username` attribute fails the rule using this validator. + +You can customize the error message of a rule by specifying the `message` property when declaring the rule, +like the following, + +```php +public function rules() +{ + return [ + ['username', 'required', 'message' => 'Please choose a username.'], + ]; +} +``` + +Some validators may support additional error messages to more precisely describe different causes of +validation failures. For example, the [[yii\validators\NumberValidator|number]] validator supports +[[yii\validators\NumberValidator::tooBig|tooBig]] and [[yii\validators\NumberValidator::tooSmall|tooSmall]] +to describe the validation failure when the value being validated is too big and too small, respectively. +You may configure these error messages like configuring other properties of validators in a validation rule. + + +### 验证事件 + +When [[yii\base\Model::validate()]] is called, it will call two methods that you may override to customize +the validation process: + +* [[yii\base\Model::beforeValidate()]]: the default implementation will trigger a [[yii\base\Model::EVENT_BEFORE_VALIDATE]] + event. You may either override this method or respond to this event to do some preprocessing work + (e.g. normalizing data inputs) before the validation occurs. The method should return a boolean value indicating + whether the validation should proceed or not. +* [[yii\base\Model::afterValidate()]]: the default implementation will trigger a [[yii\base\Model::EVENT_AFTER_VALIDATE]] + event. You may either override this method or respond to this event to do some postprocessing work after + the validation is completed. + + +### 条件式验证 + +To validate attributes only when certain conditions apply, e.g. the validation of one attribute depends +on the value of another attribute you can use the [[yii\validators\Validator::when|when]] property +to define such conditions. For example, + +```php +[ + ['state', 'required', 'when' => function($model) { + return $model->country == 'USA'; + }], +] +``` + +The [[yii\validators\Validator::when|when]] property takes a PHP callable with the following signature: + +```php +/** + * @param Model $model the model being validated + * @param string $attribute the attribute being validated + * @return boolean whether the rule should be applied + */ +function ($model, $attribute) +``` + +If you also need to support client-side conditional validation, you should configure +the [[yii\validators\Validator::whenClient|whenClient]] property which takes a string representing a JavaScript +function whose return value determines whether to apply the rule or not. For example, + +```php +[ + ['state', 'required', 'when' => function ($model) { + return $model->country == 'USA'; + }, 'whenClient' => "function (attribute, value) { + return $('#country').value == 'USA'; + }"], +] +``` + + +### 数据过滤 + +User inputs often need to be filtered or preprocessed. For example, you may want to trim the spaces around the +`username` input. You may use validation rules to achieve this goal. + +The following examples shows how to trim the spaces in the inputs and turn empty inputs into nulls by using +the [trim](tutorial-core-validators.md#trim) and [default](tutorial-core-validators.md#default) core validators: + +```php +[ + [['username', 'email'], 'trim'], + [['username', 'email'], 'default'], +] +``` + +You may also use the more general [filter](tutorial-core-validators.md#filter) validator to perform more complex +data filtering. + +As you can see, these validation rules do not really validate the inputs. Instead, they will process the values +and save them back to the attributes being validated. + + +### 处理空输入 + +When input data are submitted from HTML forms, you often need to assign some default values to the inputs +if they are empty. You can do so by using the [default](tutorial-core-validators.md#default) validator. For example, + +```php +[ + // set "username" and "email" as null if they are empty + [['username', 'email'], 'default'], + + // set "level" to be 1 if it is empty + ['level', 'default', 'value' => 1], +] +``` + +By default, an input is considered empty if its value is an empty string, an empty array or a null. +You may customize the default empty detection logic by configuring the the [[yii\validators\Validator::isEmpty]] property +with a PHP callable. For example, + +```php +[ + ['agree', 'required', 'isEmpty' => function ($value) { + return empty($value); + }], +] +``` + +> Note: Most validators do not handle empty inputs if their [[yii\base\Validator::skipOnEmpty]] property takes + the default value true. They will simply be skipped during validation if their associated attributes receive empty + inputs. Among the [core validators](tutorial-core-validators.md), only the `captcha`, `default`, `filter`, + `required`, and `trim` validators will handle empty inputs. + + +## Ad Hoc Validation + +Sometimes you need to do *ad hoc validation* for values that are not bound to any model. + +If you only need to perform one type of validation (e.g. validating email addresses), you may call +the [[yii\validators\Validator::validate()|validate()]] method of the desired validator, like the following: + +```php +$email = 'test@example.com'; +$validator = new yii\validators\EmailValidator(); + +if ($validator->validate($email, $error)) { + echo 'Email is valid.'; +} else { + echo $error; +} +``` + +> Note: Not all validators support such kind of validation. An example is the [unique](tutorial-core-validators.md#unique) + core validator which is designed to work with a model only. + +If you need to perform multiple validations against several values, you can use [[yii\base\DynamicModel]] +which supports declaring both attributes and rules on the fly. Its usage is like the following: + +```php +public function actionSearch($name, $email) +{ + $model = DynamicModel::validateData(compact('name', 'email'), [ + [['name', 'email'], 'string', 'max' => 128], + ['email', 'email'], + ]); + + if ($model->hasErrors()) { + // validation fails + } else { + // validation succeeds + } +} +``` + +The [[yii\base\DynamicModel::validateData()]] method creates an instance of `DynamicModel`, defines the attributes +using the given data (`name` and `email` in this example), and then calls [[yii\base\Model::validate()]] +with the given rules. + +Alternatively, you may use the following more "classic" syntax to perform ad hoc data validation: + +```php +public function actionSearch($name, $email) +{ + $model = new DynamicModel(compact('name', 'email')); + $model->addRule(['name', 'email'], 'string', ['max' => 128]) + ->addRule('email', 'email') + ->validate(); + + if ($model->hasErrors()) { + // validation fails + } else { + // validation succeeds + } +} +``` + +After validation, you can check if the validation succeeds or not by calling the +[[yii\base\DynamicModel::hasErrors()|hasErrors()]] method, and then get the validation errors from the +[[yii\base\DynamicModel::errors|errors]] property, like you do with a normal model. +You may also access the dynamic attributes defined through the model instance, e.g., +`$model->name` and `$model->email`. + + +## 创建验证器(Validators) + +Besides using the [core validators](tutorial-core-validators.md) included in the Yii releases, you may also +create your own validators. You may create inline validators or standalone validators. + + +### 行内验证器(Inline Validators) + +An inline validator is one defined in terms of a model method or an anonymous function. The signature of +the method/function is: + +```php +/** + * @param string $attribute the attribute currently being validated + * @param array $params the additional name-value pairs given in the rule + */ +function ($attribute, $params) +``` + +If an attribute fails the validation, the method/function should call [[yii\base\Model::addError()]] to save +the error message in the model so that it can be retrieved back later to present to end users. + +Below are some examples: + +```php +use yii\base\Model; + +class MyForm extends Model +{ + public $country; + public $token; + + public function rules() + { + return [ + // an inline validator defined as the model method validateCountry() + ['country', 'validateCountry'], + + // an inline validator defined as an anonymous function + ['token', function ($attribute, $params) { + if (!ctype_alnum($this->$attribute)) { + $this->addError($attribute, 'The token must contain letters or digits.'); + } + }], + ]; + } + + public function validateCountry($attribute, $params) + { + if (!in_array($this->$attribute, ['USA', 'Web'])) { + $this->addError($attribute, 'The country must be either "USA" or "Web".'); + } + } +} +``` + +> Note: By default, inline validators will not be applied if their associated attributes receive empty inputs + or if they have already failed some validation rules. If you want to make sure a rule is always applied, + you may configure the [[yii\validators\Validator::skipOnEmpty|skipOnEmpty]] and/or [[yii\validators\Validator::skipOnError|skipOnError]] + properties to be false in the rule declarations. For example: +> ```php +[ + ['country', 'validateCountry', 'skipOnEmpty' => false, 'skipOnError' => false], +] +``` + + +### 独立验证器(Standalone Validators) + +A standalone validator is a class extending [[yii\validators\Validator]] or its child class. You may implement +its validation logic by overriding the [[yii\validators\Validator::validateAttribute()]] method. If an attribute +fails the validation, call [[yii\base\Model::addError()]] to save the error message in the model, like you do +with [inline validators](#inline-validators). For example, + +```php +namespace app\components; + +use yii\validators\Validator; + +class CountryValidator extends Validator +{ + public function validateAttribute($model, $attribute) + { + if (!in_array($model->$attribute, ['USA', 'Web'])) { + $this->addError($attribute, 'The country must be either "USA" or "Web".'); + } + } +} +``` + +If you want your validator to support validating a value without a model, you should also override +[[yii\validators\Validator::validate()]]. You may also override [[yii\validators\Validator::validateValue()]] +instead of `validateAttribute()` and `validate()` because by default the latter two methods are implemented +by calling `validateValue()`. + + +## 客户端验证器(Client-Side Validation) + +Client-side validation based on JavaScript is desirable when end users provide inputs via HTML forms, because +it allows users to find out input errors faster and thus provides better user experience. You may use or implement +a validator that supports client-side validation *in addition to* server-side validation. + +> Info: While client-side validation is desirable, it is not a must. It main purpose is to provider users better + experience. Like input data coming from end users, you should never trust client-side validation. For this reason, + you should always perform server-side validation by calling [[yii\base\Model::validate()]], like + described in the previous subsections. + + +### 使用客户端验证 + +Many [core validators](tutorial-core-validators.md) support client-side validation out-of-box. All you need to do +is just to use [[yii\widgets\ActiveForm]] to build your HTML forms. For example, `LoginForm` below declares two +rules: one uses the [required](tutorial-core-validators.md#required) core validator which is supported on both +client and server sides; the other uses the `validatePassword` inline validator which is only supported on the server +side. + +```php +namespace app\models; + +use yii\base\Model; +use app\models\User; + +class LoginForm extends Model +{ + public $username; + public $password; + + public function rules() + { + return [ + // username and password are both required + [['username', 'password'], 'required'], + + // password is validated by validatePassword() + ['password', 'validatePassword'], + ]; + } + + public function validatePassword() + { + $user = User::findByUsername($this->username); + + if (!$user || !$user->validatePassword($this->password)) { + $this->addError('password', 'Incorrect username or password.'); + } + } +} +``` + +The HTML form built by the following code contains two input fields `username` and `password`. +If you submit the form without entering anything, you will find the error messages requiring you +to enter something appear right away without any communication with the server. + +```php + + field($model, 'username') ?> + field($model, 'password')->passwordInput() ?> + + +``` + +Behind the scene, [[yii\widgets\ActiveForm]] will read the validation rules declared in the model +and generate appropriate JavaScript code for validators that support client-side validation. When a user +changes the value of an input field or submit the form, the client-side validation JavaScript will be triggered. + +If you want to turn off client-side validation completely, you may configure the +[[yii\widgets\ActiveForm::enableClientValidation]] property to be false. You may also turn off client-side +validation of individual input fields by configuring their [[yii\widgets\ActiveField::enableClientValidation]] +property to be false. + + +### 实现客户端验证 + +To create a validator that supports client-side validation, you should implement the +[[yii\validators\Validator::clientValidateAttribute()]] method which returns a piece of JavaScript code +that performs the validation on the client side. Within the JavaScript code, you may use the following +predefined variables: + +- `attribute`: the name of the attribute being validated. +- `value`: the value being validated. +- `messages`: an array used to hold the validation error messages for the attribute. + +In the following example, we create a `StatusValidator` which validates if an input is a valid status input +against the existing status data. The validator supports both server side and client side validation. + +```php +namespace app\components; + +use yii\validators\Validator; +use app\models\Status; + +class StatusValidator extends Validator +{ + public function init() + { + parent::init(); + $this->message = 'Invalid status input.'; + } + + public function validateAttribute($model, $attribute) + { + $value = $model->$attribute; + if (!Status::find()->where(['id' => $value])->exists()) { + $model->addError($attribute, $this->message); + } + } + + public function clientValidateAttribute($model, $attribute, $view) + { + $statuses = json_encode(Status::find()->select('id')->asArray()->column()); + $message = json_encode($this->message); + return << Tip: The above code is given mainly to demonstrate how to support client-side validation. In practice, + you may use the [in](tutorial-core-validators.md#in) core validator to achieve the same goal. You may + write the validation rule like the following: +> ```php +[ + ['status', 'in', 'range' => Status::find()->select('id')->asArray()->column()], +] +``` diff --git a/docs/guide-zh-CN/intro-yii.md b/docs/guide-zh-CN/intro-yii.md index 10ec2e0..0843e2a 100644 --- a/docs/guide-zh-CN/intro-yii.md +++ b/docs/guide-zh-CN/intro-yii.md @@ -1,7 +1,7 @@ Yii 是什么 =========== -Yii 是一个高性能,基于组件的 PHP 框架,用于快速开发现代 Web 应用程序。名字 Yii (读作 `易`)在中文里有“极致简单与不断变化”两重含义,也可看作 **Yes It Is**! 的缩写。 +Yii 是一个高性能,基于组件的 PHP 框架,用于快速开发现代 Web 应用程序。名字 Yii (读作 `易`)在中文里有 “极致简单与不断演变” 两重含义,也可看作 **Yes It Is**! 的缩写。 Yii 最适合做什么? diff --git a/docs/guide-zh-CN/start-installation.md b/docs/guide-zh-CN/start-installation.md index 43f1487..8be865d 100644 --- a/docs/guide-zh-CN/start-installation.md +++ b/docs/guide-zh-CN/start-installation.md @@ -3,7 +3,7 @@ 你可以通过两种方式安装 Yii:使用 [Composer](http://getcomposer.org/) 或下载一个归档文件。推荐使用前者,这样只需执行一条简单的命令就可以安装新的[扩展](extend-creating-extensions.md)或更新 Yii 了。 -> 注意:和 Yii 1 不同,以标准方式安装 Yii 2 时会同时下载并安装框架本身和一个应用程序骨架。 +> 注意:和 Yii 1 不同,以标准方式安装 Yii 2 时会同时下载并安装框架本身和一个应用程序的基本骨架。 通过 Composer 安装 @@ -22,13 +22,13 @@ Composer 安装后,切换到一个可通过 Web 访问的目录,执行如下 composer create-project --prefer-dist yiisoft/yii2-app-basic basic -如上命令会将 Yii 安装在一个名为 `basic` 的目录中。 +如上命令会将 Yii 安装在名为 `basic` 的目录中。 -> 技巧:如果你想安装 Yii 的最新开发版本,可以使用如下命令,它添加了一个 [stability 选项](https://getcomposer.org/doc/04-schema.md#minimum-stability): +> 技巧:如果你想安装 Yii 的最新开发版本,可以使用如下命令,它添加了一个 [stability 选项](https://getcomposer.org/doc/04-schema.md#minimum-stability)([中文版](https://github.com/5-say/composer-doc-cn/blob/master/cn-introduction/04-schema.md#minimum-stability)): > > composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic > -> 注意,Yii 的开发版不应该用于生产环境中,它可能会破坏运行中的代码。 +> 注意,Yii 的开发版(dev 版)不应该用于生产环境中,它可能会破坏运行中的代码。 通过归档文件安装 @@ -38,20 +38,26 @@ Composer 安装后,切换到一个可通过 Web 访问的目录,执行如下 1. 从 [yiiframework.com](http://www.yiiframework.com/download/yii2-basic) 下载归档文件。 2. 将下载的文件解压缩到 Web 目录中。 +3. 修改 `config/web.php` 文件,给 `cookieValidationKey` 配置项添加一个密钥(若你通过 Composer 安装,则此步骤会自动完成): + + ```php + // !!! 在下面插入一段密钥(若为空) - 以供 cookie validation 的需要 + 'cookieValidationKey' => '在此处输入你的密钥', + ``` 其他安装方式 -------------------------- -上文介绍了两种安装 Yii 的方法,安装的同时也会创建一个立即可用的 Web 应用程序。对于小的项目或学习,这是一个很好的起点。 +上文介绍了两种安装 Yii 的方法,安装的同时也会创建一个立即可用的 Web 应用程序。对于小的项目或用于学习上手,这都是一个不错的起点。 -但是还可以有其他的安装方式: +但是其他的安装方式也存在: -* 如果你只想安装核心框架,然后从头开始创建一个应用程序,可以参考[从头构建自定义模版](tutorial-start-from-scratch.md)一节的介绍。 -* 如果你要开发一个更复杂的应用,更好的地适用于团队开发环境的,可以考虑安装[高级应用模版](tutorial-advanced-app.md)。 +* 如果你只想安装核心框架,然后从零开始构建整个属于你自己的应用程序模版,可以参考[从头构建自定义模版](tutorial-start-from-scratch.md)一节的介绍。 +* 如果你要开发一个更复杂的应用,可以更好地适用于团队开发环境的,可以考虑安装[高级应用模版](tutorial-advanced-app.md)。 -检查安装 +验证安装的结果 -------------------------- 安装完成后,就可以使用浏览器通过如下 URL 访问刚安装完的 Yii 应用了: @@ -60,11 +66,11 @@ Composer 安装后,切换到一个可通过 Web 访问的目录,执行如下 http://localhost/basic/web/index.php ``` -这个 URL 假设你将 Yii 安装到了一个位于 Web 文档根目录下的 `basic` 目录中,且该 Web 服务器正运行在你自己的电脑上 (`localhost`)。你可能需要将其调整为适应自己的安装环境。 +这个 URL 假设你将 Yii 安装到了一个位于 Web 文档根目录下的 `basic` 目录中,且该 Web 服务器正运行在你自己的电脑上(`localhost`)。你可能需要将其调整为适应自己的安装环境。 ![Yii 安装成功](images/start-app-installed.png) -你应该可以在浏览器中看到如上所示的 “Congratulations!” 页面。如果没有,请检查你安装的 PHP 环境是否符合 Yii 的需求,可以通过如下任意一种方式检查是否满足最小需求: +你应该可以在浏览器中看到如上所示的 “Congratulations!” 页面。如果没有,请通过以下任意一种方式,检查当前 PHP 环境是否满足 Yii 最基本需求: * 通过浏览器访问 URL `http://localhost/basic/requirements.php` * 执行如下命令: @@ -74,21 +80,21 @@ http://localhost/basic/web/index.php php requirements.php ``` -你需要配置好 PHP 安装环境,使其符合 Yii 的最小需求。最重要的是需要有 PHP 5.4 以上版本。如果应用需要用到数据库,那还要安装 [PDO PHP 扩展](http://www.php.net/manual/en/pdo.installation.php) 和相应的数据库驱动(例如访问 MySQL 数据库所需的 `pdo_mysql`)。 +你需要配置好 PHP 安装环境,使其符合 Yii 的最小需求。主要是需要 PHP 5.4 以上版本。如果应用需要用到数据库,那还要安装 [PDO PHP 扩展](http://www.php.net/manual/zh/pdo.installation.php) 和相应的数据库驱动(例如访问 MySQL 数据库所需的 `pdo_mysql`)。 配置 Web 服务器 ----------------------- ->补充:如果你现在只是要试用 Yii 而不是要将其部署到生产环境中的服务器上,本小节可以跳过。 +>补充:如果你现在只是要试用 Yii 而不是将其部署到生产环境中,本小节可以跳过。 -通过上述方法安装的应用程序在 Windows,Max OS X, Linux 中的 [Apache HTTP 服务器](http://httpd.apache.org/)或 [Nginx HTTP 服务器](http://nginx.org/) 上都可以直接运行。 +通过上述方法安装的应用程序在 Windows,Max OS X,Linux 中的 [Apache HTTP 服务器](http://httpd.apache.org/)或 [Nginx HTTP 服务器](http://nginx.org/) 上都可以直接运行。 在生产环境的服务器上,你可能会想配置服务器让应用程序可以通过 URL `http://www.example.com/index.php` 访问而不是 `http://www.example.com/basic/web/index.php`。这种配置需要将 Web 服务器的文档根目录指向 `basic/web` 目录。可能你还会想隐藏掉 URL 中的 `index.php`,具体细节在 [URL 解析和生成](runtime-url-handling.md) 一章中有介绍,你将学到如何配置 Apache 或 Nginx 服务器实现这些目标。 ->补充:将 `basic/web` 设置为文档根目录,可以防止终端用户访问 `basic/web` 相邻目录中的私有应用程序代码和敏感数据文件。禁止对其他目录的访问是一个切实可行的安全改进。 +>补充:将 `basic/web` 设置为文档根目录,可以防止终端用户访问 `basic/web` 相邻目录中的私有应用代码和敏感数据文件。禁止对其他目录的访问是一个不错的安全改进。 ->补充:如果你的应用程序将来要运行在共享的主机环境中,没有权限修改它的 Web 服务器配置,你依然可以调整应用程序的结构提升安全性。详情请参考[共享主机环境](tutorial-shared-hosting.md) 一章。 +>补充:如果你的应用程序将来要运行在共享虚拟主机环境中,没有修改其 Web 服务器配置的权限,你依然可以通过调整应用的结构来提升安全性。详情请参考[共享主机环境](tutorial-shared-hosting.md) 一章。 ### 推荐使用的 Apache 配置 @@ -137,7 +143,7 @@ server { try_files $uri $uri/ /index.php?$args; } - # 给这段解除注释,能够避免 Yii 接管不存在文件的处理过程(404) + # 若取消下面这段的注释,可避免 Yii 接管不存在文件的处理过程(404) #location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { # try_files $uri =404; #} @@ -155,6 +161,6 @@ server { } ``` -使用该配置时,你还应该在 `php.ini` 文件中设置 `cgi.fix_pathinfo=0` 以避免很多不必要的 `stat()` 系统调用。 +使用该配置时,你还应该在 `php.ini` 文件中设置 `cgi.fix_pathinfo=0` ,能避免掉很多不必要的 `stat()` 系统调用。 还要注意当运行一个 HTTPS 服务器时,需要添加 `fastcgi_param HTTPS on;` 一行,这样 Yii 才能正确地判断连接是否安全。 diff --git a/docs/guide-zh-CN/tutorial-core-validators.md b/docs/guide-zh-CN/tutorial-core-validators.md new file mode 100644 index 0000000..4551017 --- /dev/null +++ b/docs/guide-zh-CN/tutorial-core-validators.md @@ -0,0 +1,445 @@ +核心验证器(Core Validators) +=============== + +Yii 提供一系列常用的核心验证器,主要存在于 `yii\validators` 命名空间之下。为了避免使用冗长的类名,你可以直接用**昵称**来指定相应的核心验证器。比如你可以用 `required` 昵称代指 [[yii\validators\RequiredValidator]] 类: + +```php +public function rules() +{ + return [ + [['email', 'password'], 'required'], + ]; +} +``` + +[[yii\validators\Validator::builtInValidators]] 属性声明了所有被支持的验证器昵称。 + +下面,我们将详细介绍每一款验证器的主要用法和属性。 + + +## [[yii\validators\BooleanValidator|boolean(布尔型)]] + +```php +[ + // 检查 "selected" 是否为 0 或 1,无视数据类型 + ['selected', 'boolean'], + + // 检查 "deleted" 是否为布尔类型,即 true 或 false + ['deleted', 'boolean', 'trueValue' => true, 'falseValue' => false, 'strict' => true], +] +``` + +该验证器检查输入值是否为一个布尔值。 + +- `trueValue`: 代表**真**的值。默认为 `'1'`。 +- `falseValue`:代表**假**的值。默认为 `'0'`。 +- `strict`:是否要求待测输入必须严格匹配 `trueValue` 或 `falseValue`。默认为 `false`。 + + +> 注意:因为通过 HTML 表单传递的输入数据都是字符串类型,所以一般情况下你都需要保持 + [[yii\validators\BooleanValidator::strict|strict]] 属性为假。 + + +## [[yii\captcha\CaptchaValidator|captcha(验证码)]] + +```php +[ + ['verificationCode', 'captcha'], +] +``` + +该验证器通常配合 [[yii\captcha\CaptchaAction]] 以及 [[yii\captcha\Captcha]] +使用,以确保某一输入与 [[yii\captcha\Captcha|CAPTCHA]] 小部件所显示的验证代码(verification code)相同。 + +- `caseSensitive`:对验证代码的比对是否要求大小写敏感。默认为 false。 +- `captchaAction`:指向用于渲染 CAPTCHA 图片的 [[yii\captcha\CaptchaAction|CAPTCHA action]] 的 [路由](structure-controllers.md#routes)。默认为 `'site/captcha'`。 +- `skipOnEmpty`:当输入为空时,是否跳过验证。默认为 false,也就是输入值为必需项。 + + +## [[yii\validators\CompareValidator|compare(比较)]] + +```php +[ + // 检查 "password" 特性的值是否与 "password_repeat" 的值相同 + ['password', 'compare'], + + // 检查年龄是否大于等于 30 + ['age', 'compare', 'compareValue' => 30, 'operator' => '>='], +] +``` + +该验证器比较两个特定输入值之间的关系是否与 `operator` 属性所指定的相同。 + +- `compareAttribute`:用于与原特性相比较的特性名称。当该验证器被用于验证某目标特性时,该属性会默认为目标属性加后缀 `_repeat`。举例来说,若目标特性为 `password`,则该属性默认为 `password_repeat`。 +- `compareValue`:用于与输入值相比较的常量值。当该属性与 `compareAttribute` 属性同时被指定时,该属性优先被使用。 +- `operator`:比较操作符。默认为 `==`,意味着检查输入值是否与 `compareAttribute` 或 `compareValue` 的值相等。该属性支持如下操作符: + * `==`:检查两值是否相等。比对为非严格模式。 + * `===`:检查两值是否全等。比对为严格模式。 + * `!=`:检查两值是否不等。比对为非严格模式。 + * `!==`:检查两值是否不全等。比对为严格模式。 + * `>`:检查待测目标值是否大于给定被测值。 + * `>=`:检查待测目标值是否大于等于给定被测值。 + * `<`:检查待测目标值是否小于给定被测值。 + * `<=`:检查待测目标值是否小于等于给定被测值。 + + +## [[yii\validators\DateValidator|date(日期)]] + +```php +[ + [['from', 'to'], 'date'], +] +``` + +该验证器检查输入值是否为适当格式的 date,time,或者 datetime。另外,它还可以帮你把输入值转换为一个 UNIX 时间戳并保存到 [[yii\validators\DateValidator::timestampAttribute|timestampAttribute]] 属性所指定的特性里。 + +- `format`:待测的 日期/时间 格式。请参考 + [date_create_from_format() 相关的 PHP 手册](http://www.php.net/manual/zh/datetime.createfromformat.php)了解设定格式字符串的更多细节。默认值为 `'Y-m-d'`。 +- `timestampAttribute`:用于保存用输入时间/日期转换出来的 UNIX 时间戳的特性。 + + +## [[yii\validators\DefaultValueValidator|default(默认值)]] + +```php +[ + // 若 "age" 为空,则将其设为 null + ['age', 'default', 'value' => null], + + // 若 "country" 为空,则将其设为 "USA" + ['country', 'default', 'value' => 'USA'], + + // 若 "from" 和 "to" 为空,则分别给他们分配自今天起,3 天后和 6 天后的日期。 + [['from', 'to'], 'default', 'value' => function ($model, $attribute) { + return date('Y-m-d', strtotime($attribute === 'to' ? '+3 days' :'+6 days')); + }], +] +``` + +该验证器并不进行数据验证。而是,给为空的待测特性分配默认值。 + +- `value`:默认值,或一个返回默认值的 PHP Callable 对象(即回调函数)。它们会分配给检测为空的待测特性。PHP 回调方法的样式如下: + +```php +function foo($model, $attribute) { + // ... compute $value ... + return $value; +} +``` + +> 补充:如何判断待测值是否为空,被写在另外一个话题的[处理空输入](input-validation.md#handling-empty-inputs)章节。 + + +## [[yii\validators\NumberValidator|double(双精度浮点型)]] + +```php +[ + // 检查 "salary" 是否为浮点数 + ['salary', 'double'], +] +``` + +该验证器检查输入值是否为双精度浮点数。他等效于 [number](#number) 验证器。 + +- `max`:上限值(含界点)。若不设置,则验证器不检查上限。 +- `min`:下限值(含界点)。若不设置,则验证器不检查下限。 + + +## [[yii\validators\EmailValidator|email(电子邮件)]] + +```php +[ + // 检查 "email" 是否为有效的邮箱地址 + ['email', 'email'], +] +``` + +该验证器检查输入值是否为有效的邮箱地址。 + +- `allowName`:检查是否允许带名称的电子邮件地址 (e.g. `张三 `)。 默认为 false。 +- `checkDNS`:检查邮箱域名是否存在,且有没有对应的 A 或 MX 记录。不过要知道,有的时候该项检查可能会因为临时性 DNS 故障而失败,哪怕它其实是有效的。默认为 false。 +- `enableIDN`:验证过程是否应该考虑 IDN(internationalized domain names,国际化域名,也称多语种域名,比如中文域名)。默认为 false。要注意但是为使用 IDN 验证功能,请先确保安装并开启 `intl` PHP 扩展,不然会导致抛出异常。 + + +## [[yii\validators\ExistValidator|exist(存在性)]] + +```php +[ + // a1 需要在 "a1" 特性所代表的字段内存在 + ['a1', 'exist'], + + // a1 必需存在,但检验的是 a1 的值在字段 a2 中的存在性 + ['a1', 'exist', 'targetAttribute' => 'a2'], + + // a1 和 a2 的值都需要存在,且它们都能收到错误提示 + [['a1', 'a2'], 'exist', 'targetAttribute' => ['a1', 'a2']], + + // a1 和 a2 的值都需要存在,只有 a1 能接收到错误信息 + ['a1', 'exist', 'targetAttribute' => ['a1', 'a2']], + + // 通过同时在 a2 和 a3 字段中检查 a2 和 a1 的值来确定 a1 的存在性 + ['a1', 'exist', 'targetAttribute' => ['a2', 'a1' => 'a3']], + + // a1 必需存在,若 a1 为数组,则其每个子元素都必须存在。 + ['a1', 'exist', 'allowArray' => true], +] +``` + +该验证器检查输入值是否在某表字段中存在。它只对[活动记录](db-active-record.md)类型的模型类特性起作用,能支持对一个或多过字段的验证。 + +- `targetClass`:用于查找输入值的目标 [AR](db-active-record.md) 类。若不设置,则会使用正在进行验证的当前模型类。 +- `targetAttribute`:用于检查输入值存在性的 `targetClass` 的模型特性。 + - 若不设置,它会直接使用待测特性名(整个参数数组的首元素)。 + - 除了指定为字符串以外,你也可以用数组的形式,同时指定多个用于验证的表字段,数组的键和值都是代表字段的特性名,值表示 `targetClass` 的待测数据源字段,而键表示当前模型的待测特性名。 + - 若键和值相同,你可以只指定值。(如:`['a2']` 就代表 `['a2'=>'a2']`) +- `filter`:用于检查输入值存在性必然会进行数据库查询,而该属性为用于进一步筛选该查询的过滤条件。可以为代表额外查询条件的字符串或数组(关于查询条件的格式,请参考 [[yii\db\Query::where()]]);或者样式为 `function ($query)` 的匿名函数,`$query` 参数为你希望在该函数内进行修改的 [[yii\db\Query|Query]] 对象。 +- `allowArray`:是否允许输入值为数组。默认为 false。若该属性为 true 且输入值为数组,则数组的每个元素都必须在目标字段中存在。值得注意的是,若用吧 `targetAttribute` 设为多元素数组来验证被测值在多字段中的存在性时,该属性不能设置为 true。 + +> 译者注:[exist](#exist) 和 [unique](#unique) 验证器的机理和参数都相似,有点像一体两面的阴和阳。 +- 他们的区别是 exist 要求 `targetAttribute` 键所代表的的属性在其值所代表字段中找得到;而 unique 正相反,要求键所代表的的属性不能在其值所代表字段中被找到。 +- 从另一个角度来理解:他们都会在验证的过程中执行数据库查询,查询的条件即为where $v=$k (假设 `targetAttribute` 的其中一对键值对为 `$k => $v`)。unique 要求查询的结果数 `$count==0`,而 exist 则要求查询的结果数 `$count>0` +- 最后别忘了,unique 验证器不存在 `allowArray` 属性哦。 + + +## [[yii\validators\FileValidator|file(文件)]] + +```php +[ + // 检查 "primaryImage" 是否为 PNG, JPG 或 GIF 格式的上传图片。 + // 文件大小必须小于 1MB + ['primaryImage', 'file', 'extensions' => ['png', 'jpg', 'gif'], 'maxSize' => 1024*1024*1024], +] +``` + +该验证器检查输入值是否为一个有效的上传文件。 + +- `extensions`:可接受上传的文件扩展名列表。它可以是数组,也可以是用空格或逗号分隔各个扩展名的字符串 (e.g. "gif, jpg")。 + 扩展名大小写不敏感。默认为 null,意味着所有扩展名都被接受。 +- `mimeTypes`:可接受上传的 MIME 类型列表。它可以是数组,也可以是用空格或逗号分隔各个 MIME 的字符串 (e.g. "image/jpeg, image/png")。 + Mime 类型名是大小写不敏感的。默认为 null,意味着所有 MIME 类型都被接受。 +- `minSize`:上传文件所需最少多少 Byte 的大小。默认为 null,代表没有下限。 +- `maxSize`:上传文件所需最多多少 Byte 的大小。默认为 null,代表没有上限。 +- `maxFiles`:给定特性最多能承载多少个文件。默认为 1,代表只允许单文件上传。若值大于一,那么输入值必须为包含最多 `maxFiles` 个上传文件元素的数组。 +- `checkExtensionByMimeType`:是否通过文件的 MIME 类型来判断其文件扩展。若由 MIME 判定的文件扩展与给定文件的扩展不一样,则文件会被认为无效。默认为 true,代表执行上述检测。 + +`FileValidator` 通常与 [[yii\web\UploadedFile]] 共同使用。请参考 [文件上传](input-file-upload.md)章节来了解有关文件上传与上传文件的检验的全部内容。 + + +## [[yii\validators\FilterValidator|filter(滤镜)]] + +```php +[ + // trim 掉 "username" 和 "email" 输入 + [['username', 'email'], 'filter', 'filter' => 'trim', 'skipOnArray' => true], + + // 标准化 "phone" 输入 + ['phone', 'filter', 'filter' => function ($value) { + // 在此处标准化输入的电话号码 + return $value; + }], +] +``` + +该验证器并不进行数据验证。而是,给输入值应用一个滤镜,并在检验过程之后把它赋值回特性变量。 + +- `filter`:用于定义滤镜的 PHP 回调函数。可以为全局函数名,匿名函数,或其他。该函数的样式必须是 `function ($value) { return $newValue; }`。该属性不能省略,必须设置。 +- `skipOnArray`:是否在输入值为数组时跳过滤镜。默认为 false。请注意如果滤镜不能处理数组输入,你就应该把该属性设为 true。否则可能会导致 PHP Error 的发生。 + +> 技巧:如果你只是想要用 trim 处理下输入值,你可以直接用 [trim](#trim) 验证器的。 + + +## [[yii\validators\ImageValidator|image(图片)]] + +```php +[ + // 检查 "primaryImage" 是否为适当尺寸的有效图片 + ['primaryImage', 'image', 'extensions' => 'png, jpg', + 'minWidth' => 100, 'maxWidth' => 1000, + 'minHeight' => 100, 'maxHeight' => 1000, + ], +] +``` + +该验证器检查输入值是否为代表有效的图片文件。它继承自 [file](#file) 验证器,并因此继承有其全部属性。除此之外,它还支持以下为图片检验而设的额外属性: + +- `minWidth`:图片的最小宽度。默认为 null,代表无下限。 +- `maxWidth`:图片的最大宽度。默认为 null,代表无上限。 +- `minHeight`:图片的最小高度。 默认为 null,代表无下限。 +- `maxHeight`:图片的最大高度。默认为 null,代表无上限。 + + +## [[yii\validators\RangeValidator|in(范围)]] + +```php +[ + // 检查 "level" 是否为 1、2 或 3 中的一个 + ['level', 'in', 'range' => [1, 2, 3]], +] +``` + +该验证器检查输入值是否存在于给定列表的范围之中。 + +- `range`:用于检查输入值的给定值列表。 +- `strict`:输入值与给定值直接的比较是否为严格模式(也就是类型与值都要相同,即全等)。默认为 false。 +- `not`:是否对验证的结果取反。默认为 false。当该属性被设置为 true,验证器检查输入值是否**不在**给定列表内。 +- `allowArray`:是否接受输入值为数组。当该值为 true 且输入值为数组时,数组内的每一个元素都必须在给定列表内存在,否则返回验证失败。 + + +## [[yii\validators\NumberValidator|integer(整数)]] + +```php +[ + // 检查 "age" 是否为整数 + ['age', 'integer'], +] +``` + +该验证器检查输入值是否为整形。 + +- `max`:上限值(含界点)。若不设置,则验证器不检查上限。 +- `min`:下限值(含界点)。若不设置,则验证器不检查下限。 + + +## [[yii\validators\RegularExpressionValidator|match(正则表达式)]] + +```php +[ + // 检查 "username" 是否由字母开头,且只包含单词字符 + ['username', 'match', 'pattern' => '/^[a-z]\w*$/i'] +] +``` + +该验证器检查输入值是否匹配指定正则表达式。 + +- `pattern`:用于检测输入值的正则表达式。该属性是必须的,若不设置则会抛出异常。 +- `not`:是否对验证的结果取反。默认为 false,代表输入值匹配正则表达式时验证成功。如果设为 true,则输入值不匹配正则时返回匹配成功。 + + +## [[yii\validators\NumberValidator|number(数字)]] + +```php +[ + // 检查 "salary" 是否为数字 + ['salary', 'number'], +] +``` + +该验证器检查输入值是否为数字。他等效于 [double](#double) 验证器。 + +- `max`:上限值(含界点)。若不设置,则验证器不检查上限。 +- `min`:下限值(含界点)。若不设置,则验证器不检查下限。 + + +## [[yii\validators\RequiredValidator|required(必填)]] + +```php +[ + // 检查 "username" 与 "password" 是否为空 + [['username', 'password'], 'required'], +] +``` + +该验证器检查输入值是否为空,还是已经提供了。 + +- `requiredValue`:所期望的输入值。若没设置,意味着输入不能为空。 +- `strict`:检查输入值时是否检查类型。默认为 false。当没有设置 `requiredValue` 属性时,若该属性为 true,验证器会检查输入值是否严格为 null;若该属性设为 false,该验证器会用一个更加宽松的规则检验输入值是否为空。 + +当设置了 `requiredValue` 属性时,若该属性为 true,输入值与 `requiredValue` 的比对会同时检查数据类型。 + +> 补充:如何判断待测值是否为空,被写在另外一个话题的[处理空输入](input-validation.md#handling-empty-inputs)章节。 + + +## [[yii\validators\SafeValidator|safe(安全)]] + +```php +[ + // 标记 "description" 为安全特性 + ['description', 'safe'], +] +``` + +该验证器并不进行数据验证。而是把一个特性标记为[安全特性](structure-models.md#safe-attributes)。 + + +## [[yii\validators\StringValidator|string(字符串)]] + +```php +[ + // 检查 "username" 是否为长度 4 到 24 之间的字符串 + ['username', 'string', 'length' => [4, 24]], +] +``` + +该验证器检查输入值是否为特定长度的字符串。并检查特性的值是否为某个特定长度。 + +- `length`:指定待测输入字符串的长度限制。该属性可以被指定为以下格式之一: + * 证书:the exact length that the string should be of; + * 单元素数组:代表输入字符串的最小长度 (e.g. `[8]`)。这会重写 `min` 属性。 + * 包含两个元素的数组:代表输入字符串的最小和最大长度(e.g. `[8, 128]`)。 + 这会同时重写 `min` 和 `max` 属性。 +- `min`:输入字符串的最小长度。若不设置,则代表不设下限。 +- `max`:输入字符串的最大长度。若不设置,则代表不设上限。 +- `encoding`:待测字符串的编码方式。若不设置,则使用应用自身的 [[yii\base\Application::charset|charset]] 属性值,该值默认为 `UTF-8`。 + + +## [[yii\validators\FilterValidator|trim(译为修剪/裁边)]] + +```php +[ + // trim 掉 "username" 和 "email" 两侧的多余空格 + [['username', 'email'], 'trim'], +] +``` + +该验证器并不进行数据验证。而是,trim 掉输入值两侧的多余空格。注意若该输入值为数组,那它会忽略掉该验证器。 + + +## [[yii\validators\UniqueValidator|unique(唯一性)]] + +```php +[ + // a1 需要在 "a1" 特性所代表的字段内唯一 + ['a1', 'unique'], + + // a1 需要唯一,但检验的是 a1 的值在字段 a2 中的唯一性 + ['a1', 'unique', 'targetAttribute' => 'a2'], + + // a1 和 a2 的组合需要唯一,且它们都能收到错误提示 + [['a1', 'a2'], 'unique', 'targetAttribute' => ['a1', 'a2']], + + // a1 和 a2 的组合需要唯一,只有 a1 能接收错误提示 + ['a1', 'unique', 'targetAttribute' => ['a1', 'a2']], + + // 通过同时在 a2 和 a3 字段中检查 a2 和 a3 的值来确定 a1 的唯一性 + ['a1', 'unique', 'targetAttribute' => ['a2', 'a1' => 'a3']], +] +``` + +该验证器检查输入值是否在某表字段中唯一。它只对[活动记录](db-active-record.md)类型的模型类特性起作用,能支持对一个或多过字段的验证。 + +- `targetClass`:用于查找输入值的目标 [AR](db-active-record.md) 类。若不设置,则会使用正在进行验证的当前模型类。 +- `targetAttribute`:用于检查输入值唯一性的 `targetClass` 的模型特性。 + - 若不设置,它会直接使用待测特性名(整个参数数组的首元素)。 + - 除了指定为字符串以外,你也可以用数组的形式,同时指定多个用于验证的表字段,数组的键和值都是代表字段的特性名,值表示 `targetClass` 的待测数据源字段,而键表示当前模型的待测特性名。 + - 若键和值相同,你可以只指定值。(如:`['a2']` 就代表 `['a2'=>'a2']`) +- `filter`:用于检查输入值唯一性必然会进行数据库查询,而该属性为用于进一步筛选该查询的过滤条件。可以为代表额外查询条件的字符串或数组(关于查询条件的格式,请参考 [[yii\db\Query::where()]]);或者样式为 `function ($query)` 的匿名函数,`$query` 参数为你希望在该函数内进行修改的 [[yii\db\Query|Query]] 对象。 + +> 译者注:[exist](#exist) 和 [unique](#unique) 验证器的机理和参数都相似,有点像一体两面的阴和阳。 +- 他们的区别是 exist 要求 `targetAttribute` 键所代表的的属性在其值所代表字段中找得到;而 unique 正相反,要求键所代表的的属性不能在其值所代表字段中被找到。 +- 从另一个角度来理解:他们都会在验证的过程中执行数据库查询,查询的条件即为where $v=$k (假设 `targetAttribute` 的其中一对键值对为 `$k => $v`)。unique 要求查询的结果数 `$count==0`,而 exist 则要求查询的结果数 `$count>0` +- 最后别忘了,unique 验证器不存在 `allowArray` 属性哦。 + + +## [[yii\validators\UrlValidator|url(网址)]] + +```php +[ + // 检查 "website" 是否为有效的 URL。若没有 URI 方案,则给 "website" 特性加 "http://" 前缀 + ['website', 'url', 'defaultScheme' => 'http'], +] +``` + +该验证器检查输入值是否为有效 URL。 + +- `validSchemes`:用于指定那些 URI 方案会被视为有效的数组。默认为 `['http', 'https']`,代表 `http` 和 `https` URLs 会被认为有效。 +- `defaultScheme`:若输入值没有对应的方案前缀,会使用的默认 URI 方案前缀。默认为 null,代表不修改输入值本身。 +- `enableIDN`:验证过程是否应该考虑 IDN(internationalized domain names,国际化域名,也称多语种域名,比如中文域名)。默认为 false。要注意但是为使用 IDN 验证功能,请先确保安装并开启 `intl` PHP 扩展,不然会导致抛出异常。 diff --git a/docs/guide/README.md b/docs/guide/README.md index f5ce8b9..39d79e5 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -151,9 +151,9 @@ Testing ------- * [Overview](test-overview.md) -* **TBD** [Unit Tests](test-unit.md) -* **TBD** [Functional Tests](test-functional.md) -* **TBD** [Acceptance Tests](test-acceptance.md) +* [Unit Tests](test-unit.md) +* [Functional Tests](test-functional.md) +* [Acceptance Tests](test-acceptance.md) * [Fixtures](test-fixtures.md) diff --git a/docs/guide/caching-http.md b/docs/guide/caching-http.md index 912eee5..a6b3495 100644 --- a/docs/guide/caching-http.md +++ b/docs/guide/caching-http.md @@ -96,7 +96,7 @@ public function behaviors() The above code states that HTTP caching should be enabled for the `view` action only. It should generate an `ETag` HTTP header based on the title and content of the requested post. When a browser visits the `view` page for the first time, the page will be generated on the server and sent to the browser; -If the browser visits the same page again and there is change to the title and content of the post, +If the browser visits the same page again and there is no change to the title and content of the post, the server will not re-generate the page, and the browser will use the cached version on the client side. As a result, server-side rendering and page content transmission are both skipped. diff --git a/docs/guide/input-validation.md b/docs/guide/input-validation.md index bfa6b7c..33f58f5 100644 --- a/docs/guide/input-validation.md +++ b/docs/guide/input-validation.md @@ -408,7 +408,7 @@ Client-side validation based on JavaScript is desirable when end users provide i it allows users to find out input errors faster and thus provides better user experience. You may use or implement a validator that supports client-side validation *in addition to* server-side validation. -> Info: While client-side validation is desirable, it is not a must. It main purpose is to provider users better +> Info: While client-side validation is desirable, it is not a must. Its main purpose is to provide users better experience. Like input data coming from end users, you should never trust client-side validation. For this reason, you should always perform server-side validation by calling [[yii\base\Model::validate()]], like described in the previous subsections. @@ -487,6 +487,7 @@ predefined variables: - `attribute`: the name of the attribute being validated. - `value`: the value being validated. - `messages`: an array used to hold the validation error messages for the attribute. +- `deferred`: an array which deferred objects can be pushed into (explained in the next subsection). In the following example, we create a `StatusValidator` which validates if an input is a valid status input against the existing status data. The validator supports both server side and client side validation. @@ -534,3 +535,106 @@ JS; ['status', 'in', 'range' => Status::find()->select('id')->asArray()->column()], ] ``` + +### Deferred Validation + +If you need to perform asynchronous client-side validation, you can create [Deferred objects](http://api.jquery.com/category/deferred-object/). +For example, to perform a custom AJAX validation, you can use the following code: + +```php +public function clientValidateAttribute($model, $attribute, $view) +{ + return << 150) { + messages.push('Image too wide!!'); + } + def.resolve(); + } + var reader = new FileReader(); + reader.onloadend = function() { + img.src = reader.result; + } + reader.readAsDataURL(file); + + deferred.push(def); +JS; +} +``` + +> Note: The `resolve()` method must be called after the attribute has been validated. Otherwise the main form + validation will not complete. + +For simplicity, the `deferred` array is equipped with a shortcut method `add()` which automatically creates a Deferred +object and add it to the `deferred` array. Using this method, you can simplify the above example as follows, + +```php +public function clientValidateAttribute($model, $attribute, $view) +{ + return << 150) { + messages.push('Image too wide!!'); + } + def.resolve(); + } + var reader = new FileReader(); + reader.onloadend = function() { + img.src = reader.result; + } + reader.readAsDataURL(file); + }); +JS; +} +``` + + +### AJAX Validation + +Some validations can only be done on the server side, because only the server has the necessary information. +For example, to validate if a username is unique or not, it is necessary to check the user table on the server side. +You can use AJAX-based validation in this case. It will trigger an AJAX request in the background to validate the +input while keeping the same user experience as the regular client-side validation. + +To enable AJAX validation for the whole form, you have to set the +[[yii\widgets\ActiveForm::enableAjaxValidation]] property to be `true`. You may also turn it on or off +for individual input fields by configuring their [[yii\widgets\ActiveField::enableAjaxValidation]] property. + +You also need to prepare the server so that it can handle the AJAX validation requests. +This can be achieved by a code snippet like the following in controller actions: + +```php +if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) { + Yii::$app->response->format = Response::FORMAT_JSON; + return ActiveForm::validate($model); +} +``` + +The above code will check whether the current request is an AJAX. If yes, it will respond to +this request by running the validation and returning the errors in JSON format. + +> Info: You can also use [Deferred Validation](#deferred-validation) to perform AJAX validation. + However, the AJAX validation feature described here is more systematic and requires less coding effort. diff --git a/docs/guide/intro-upgrade-from-v1.md b/docs/guide/intro-upgrade-from-v1.md index be13896..46f90df 100644 --- a/docs/guide/intro-upgrade-from-v1.md +++ b/docs/guide/intro-upgrade-from-v1.md @@ -498,5 +498,5 @@ Using Yii 1.1 and 2.x together ------------------------------ If you have legacy Yii 1.1 code that you want to use together with Yii 2.0, please refer to -the [Using Yii 1.1 and 2.0 Together](extend-using-v1-v2.md) section. +the [Using Yii 1.1 and 2.0 Together](tutorial-yii-integration.md) section. diff --git a/docs/guide/security-authentication.md b/docs/guide/security-authentication.md index 2c5d3f8..27f0f8f 100644 --- a/docs/guide/security-authentication.md +++ b/docs/guide/security-authentication.md @@ -64,15 +64,18 @@ class User extends ActiveRecord implements IdentityInterface } ``` -Two of the outlined methods are simple: `findIdentity` is provided with an ID value and returns a model instance associated with that ID. The `getId` method returns the ID itself. -Two of the other methods--`getAuthKey` and `validateAuthKey`--are used to provide extra security to the "remember me" cookie. The `getAuthKey` method should return a string that is unique for each user. You can create reliably create a unique string using `Yii::$app->getSecurity()->generateRandomKey()`. It's a good idea to also save this as part of the user's record: +Two of the outlined methods are simple: `findIdentity` is provided with an ID value and returns a model instance +associated with that ID. The `getId` method returns the ID itself. Two of the other methods – `getAuthKey` and +`validateAuthKey` – are used to provide extra security to the "remember me" cookie. The `getAuthKey` method should +return a string that is unique for each user. You can reliably create a unique string using +`Yii::$app->getSecurity()->generateRandomString()`. It's a good idea to also save this as part of the user's record: ```php public function beforeSave($insert) { if (parent::beforeSave($insert)) { if ($this->isNewRecord) { - $this->auth_key = Yii::$app->getSecurity()->generateRandomKey(); + $this->auth_key = Yii::$app->getSecurity()->generateRandomString(); } return true; } diff --git a/docs/guide/security-authorization.md b/docs/guide/security-authorization.md index 87b304d..4fca0d9 100644 --- a/docs/guide/security-authorization.md +++ b/docs/guide/security-authorization.md @@ -422,7 +422,7 @@ use Yii; use yii\rbac\Rule; /** - * Checks if authorID matches user passed via params + * Checks if user group matches */ class UserGroupRule extends Rule { diff --git a/docs/guide/security-passwords.md b/docs/guide/security-passwords.md index b2fbb38..19796b6 100644 --- a/docs/guide/security-passwords.md +++ b/docs/guide/security-passwords.md @@ -42,7 +42,7 @@ Yii security helper makes generating pseudorandom data simple: ```php -$key = Yii::$app->getSecurity()->generateRandomKey(); +$key = Yii::$app->getSecurity()->generateRandomString(); ``` Note that you need to have the `openssl` extension installed in order to generate cryptographically secure random data. diff --git a/docs/guide/start-databases.md b/docs/guide/start-databases.md index 4906df2..4de60c3 100644 --- a/docs/guide/start-databases.md +++ b/docs/guide/start-databases.md @@ -2,7 +2,7 @@ Working with Databases ====================== This section will describe how to create a new page that displays country data fetched from -from a database table named `country`. To achieve this goal, you will configure a database connection, +a database table named `country`. To achieve this goal, you will configure a database connection, create an [Active Record](db-active-record.md) class, and define an [action](structure-controllers.md), and create a [view](structure-views.md). @@ -32,16 +32,16 @@ CREATE TABLE `country` ( `population` INT(11) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -INSERT INTO `Country` VALUES ('AU','Australia',18886000); -INSERT INTO `Country` VALUES ('BR','Brazil',170115000); -INSERT INTO `Country` VALUES ('CA','Canada',1147000); -INSERT INTO `Country` VALUES ('CN','China',1277558000); -INSERT INTO `Country` VALUES ('DE','Germany',82164700); -INSERT INTO `Country` VALUES ('FR','France',59225700); -INSERT INTO `Country` VALUES ('GB','United Kingdom',59623400); -INSERT INTO `Country` VALUES ('IN','India',1013662000); -INSERT INTO `Country` VALUES ('RU','Russia',146934000); -INSERT INTO `Country` VALUES ('US','United States',278357000); +INSERT INTO `country` VALUES ('AU','Australia',18886000); +INSERT INTO `country` VALUES ('BR','Brazil',170115000); +INSERT INTO `country` VALUES ('CA','Canada',1147000); +INSERT INTO `country` VALUES ('CN','China',1277558000); +INSERT INTO `country` VALUES ('DE','Germany',82164700); +INSERT INTO `country` VALUES ('FR','France',59225700); +INSERT INTO `country` VALUES ('GB','United Kingdom',59623400); +INSERT INTO `country` VALUES ('IN','India',1013662000); +INSERT INTO `country` VALUES ('RU','Russia',146934000); +INSERT INTO `country` VALUES ('US','United States',278357000); ``` At this point, you have a database named `yii2basic`, and within it a `country` table with three columns, containing ten rows of data. diff --git a/docs/guide/structure-applications.md b/docs/guide/structure-applications.md index 405b162..2a1c137 100644 --- a/docs/guide/structure-applications.md +++ b/docs/guide/structure-applications.md @@ -151,7 +151,7 @@ For example, During the bootstrapping process, each component will be instantiated. If the component class implements [[yii\base\BootstrapInterface]], its [[yii\base\BootstrapInterface::bootstrap()|bootstrap()]] method -will be also be called. +will also be called. Another practical example is in the application configuration for the [Basic Application Template](start-installation.md), where the `debug` and `gii` modules are configured as bootstrapping components when the application is running @@ -391,11 +391,11 @@ does not specify one. The route may consist of child module ID, controller ID, a For example, `help`, `post/create`, `admin/post/create`. If action ID is not given, it will take the default value as specified in [[yii\base\Controller::defaultAction]]. -For [yii\web\Application|Web applications], the default value of this property is `'site'`, which means +For [[yii\web\Application|Web applications]], the default value of this property is `'site'`, which means the `SiteController` controller and its default action should be used. As a result, if you access the application without specifying a route, it will show the result of `app\controllers\SiteController::actionIndex()`. -For [yii\console\Application|console applications], the default value is `'help'`, which means the core command +For [[yii\console\Application|console applications]], the default value is `'help'`, which means the core command [[yii\console\controllers\HelpController::actionIndex()]] should be used. As a result, if you run the command `yii` without providing any arguments, it will display the help information. @@ -556,7 +556,7 @@ For example, ``` Note that the same `beforeAction` event is also triggered by [modules](structure-modules.md) -and [controllers)(structure-controllers.md). Application objects are the first ones +and [controllers](structure-controllers.md). Application objects are the first ones triggering this event, followed by modules (if any), and finally controllers. If an event handler sets [[yii\base\ActionEvent::isValid]] to be `false`, all the following events will NOT be triggered. @@ -582,7 +582,7 @@ For example, ``` Note that the same `afterAction` event is also triggered by [modules](structure-modules.md) -and [controllers)(structure-controllers.md). These objects trigger this event in the reverse order +and [controllers](structure-controllers.md). These objects trigger this event in the reverse order as for that of `beforeAction`. That is, controllers are the first objects triggering this event, followed by modules (if any), and finally applications. diff --git a/docs/guide/structure-controllers.md b/docs/guide/structure-controllers.md index 07c5b09..c7bf27e 100644 --- a/docs/guide/structure-controllers.md +++ b/docs/guide/structure-controllers.md @@ -112,11 +112,13 @@ For this reason, controller IDs are often nouns referring to the types of the re For example, you may use `article` as the ID of a controller that handles article data. By default, controller IDs should contain these characters only: English letters in lower case, digits, -underscores, dashes and forward slashes. For example, `article`, `post-comment`, `admin/post2-comment` are -all valid controller IDs, while `article?`, `PostComment`, `admin\post` are not. +underscores, dashes and forward slashes. For example, `article` and `post-comment` are both valid controller IDs, +while `article?`, `PostComment`, `admin\post` are not. -The dashes in a controller ID are used to separate words, while the forward slashes to organize controllers in -sub-directories. +A controller ID may also contain a subdirectory prefix. For example, `admin/article` stands for an `article` controller +in the `admin` subdirectory under the [[yii\base\Application::controllerNamespace|controller namespace]]. +Valid characters for subdirectory prefixes include: English letters in lower and upper cases, digits, underscores and +forward slashes, where forward slashes are used as separators for multi-level subdirectories (e.g. `panels/admin`). ### Controller Class Naming @@ -134,7 +136,8 @@ takes the default value `app\controllers`: * `article` derives `app\controllers\ArticleController`; * `post-comment` derives `app\controllers\PostCommentController`; -* `admin/post2-comment` derives `app\controllers\admin\Post2CommentController`. +* `admin/post-comment` derives `app\controllers\admin\PostCommentController`; +* `adminPanels/post-comment` derives `app\controllers\adminPanels\PostCommentController`. Controller classes must be [autoloadable](concept-autoloading.md). For this reason, in the above examples, the `article` controller class should be saved in the file whose [alias](concept-aliases.md) diff --git a/docs/guide/test-acceptance.md b/docs/guide/test-acceptance.md new file mode 100644 index 0000000..45ca7b1 --- /dev/null +++ b/docs/guide/test-acceptance.md @@ -0,0 +1,11 @@ +Acceptance Tests +================ + +> Note: This section is under development. + +- http://codeception.com/docs/04-AcceptanceTests +- https://github.com/yiisoft/yii2/blob/master/apps/advanced/README.md#testing +- https://github.com/yiisoft/yii2/blob/master/apps/basic/tests/README.md + +How to run php-server +--------------------- diff --git a/docs/guide/test-functional.md b/docs/guide/test-functional.md new file mode 100644 index 0000000..f5eac8a --- /dev/null +++ b/docs/guide/test-functional.md @@ -0,0 +1,8 @@ +Functional Tests +---------------- + +> Note: This section is under development. + +- http://codeception.com/docs/05-FunctionalTests +- https://github.com/yiisoft/yii2/blob/master/apps/advanced/README.md#testing +- https://github.com/yiisoft/yii2/blob/master/apps/basic/tests/README.md diff --git a/docs/guide/test-overview.md b/docs/guide/test-overview.md index 1395d17..089e3cb 100644 --- a/docs/guide/test-overview.md +++ b/docs/guide/test-overview.md @@ -1,4 +1,37 @@ Testing ======= -TBD \ No newline at end of file +> Note: This section is under development. + +TODO: + +- https://github.com/yiisoft/yii2/blob/master/extensions/codeception/README.md + +Testing is an important part of software development. Whether we are aware of it or not, we conduct testing continuously. +For example, when we write a class in PHP, we may debug it step by step or simply use echo or die statements to verify +that implementation is correct. In case of web application we're entering some test data in forms to ensure the page +interacts with us as expected. The testing process could be automated so that each time when we need to test something, +we just need to call up the code that perform testing for us. This is known as automated testing, which is the main topic +of testing chapters. + +The testing support provided by Yii includes: + +- [Unit testing](test-unit.md) - verifies that a single unit of code is working as expected. +- [Functional testing](test-functional.md) - verifies scenarios from a user's perspective via browser emulation. +- [Acceptance testing](test-acceptance.md) - verifies scenarios from a user's perspective in a browser. + +Yii provides ready to use test sets for all three testing types in both basic and advanced application templates. + +Test environment setup +---------------------- + +In order to run tests with Yii you need to install [Codeception](http://codeception.com/). A good way to install it is +the following: + +``` +composer global require "codeception/codeception=2.0.*" +composer global require "codeception/specify=*" +composer global require "codeception/verify=*" +``` + +That's it. Now we're able to use `codecept` from command line. diff --git a/docs/guide/test-unit.md b/docs/guide/test-unit.md new file mode 100644 index 0000000..9a2c41f --- /dev/null +++ b/docs/guide/test-unit.md @@ -0,0 +1,19 @@ +Unit Tests +========== + +> Note: This section is under development. + +TODO: + +- https://github.com/yiisoft/yii2/blob/master/apps/advanced/README.md#testing +- https://github.com/yiisoft/yii2/blob/master/apps/basic/tests/README.md + +A unit test verifies that a single unit of code is working as expected. In object-oriented programming, the most basic +code unit is a class. A unit test thus mainly needs to verify that each of the class interface methods works properly. +That is, given different input parameters, the test verifies the method returns expected results. +Unit tests are usually developed by people who write the classes being tested. + +Unit testing in Yii is built on top of PHPUnit and, optionally, Codeception so it's recommended to go through their docs: + +- [PHPUnit docs starting from chapter 2](http://phpunit.de/manual/current/en/writing-tests-for-phpunit.html). +- [Codeception Unit Tests](http://codeception.com/docs/06-UnitTests). \ No newline at end of file diff --git a/docs/guide/tutorial-core-validators.md b/docs/guide/tutorial-core-validators.md index b064f81..236136b 100644 --- a/docs/guide/tutorial-core-validators.md +++ b/docs/guide/tutorial-core-validators.md @@ -143,7 +143,7 @@ function foo($model, $attribute) { ``` > Info: How to determine if a value is empty or not is a separate topic covered - in the [Empty Values](input-validation.md#empty-values) section. + in the [Empty Values](input-validation.md#handling-empty-inputs) section. ## [[yii\validators\NumberValidator|double]] @@ -368,7 +368,7 @@ This validator checks if the input value matches the specified regular expressio ] ``` -This validator checks if the input value is a number. It is equivalent to the [double](#double] validator. +This validator checks if the input value is a number. It is equivalent to the [double](#double) validator. - `max`: the upper limit (inclusive) of the value. If not set, it means the validator does not check the upper limit. - `min`: the lower limit (inclusive) of the value. If not set, it means the validator does not check the lower limit. @@ -393,7 +393,7 @@ This validator checks if the input value is provided and not empty. if this property is true. > Info: How to determine if a value is empty or not is a separate topic covered - in the [Empty Values](input-validation.md#empty-values) section. + in the [Empty Values](input-validation.md#handling-empty-inputs) section. ## [[yii\validators\SafeValidator|safe]] @@ -418,15 +418,13 @@ a [safe attribute](structure-models.md#safe-attributes). ] ``` -This validator checks if the input value - -Validates that the attribute value is of certain length. +This validator checks if the input value is a valid string with certain length. - `length`: specifies the length limit of the input string being validated. This can be specified in one of the following forms: * an integer: the exact length that the string should be of; * an array of one element: the minimum length of the input string (e.g. `[8]`). This will overwrite `min`. - * an array of two elements: the minimum and maximum lengths of the input string (e.g. `[8, 128]`)`. + * an array of two elements: the minimum and maximum lengths of the input string (e.g. `[8, 128]`). This will overwrite both `min` and `max`. - `min`: the minimum length of the input string. If not set, it means no minimum length limit. - `max`: the maximum length of the input string. If not set, it means no maximum length limit. diff --git a/docs/guide/tutorial-template-engines.md b/docs/guide/tutorial-template-engines.md index 316d428..4fc92db 100644 --- a/docs/guide/tutorial-template-engines.md +++ b/docs/guide/tutorial-template-engines.md @@ -82,6 +82,15 @@ In case you don't need result you shoud use `void` wrapper: {{ void(myObject.my_function({'a' : 'b'})) }} ``` +#### Setting object properties + +There's a special function called `set` that allows you to set property of an object. For example, the following +in the template will change page title: + +``` +{{ set(this, 'title', 'New title') }} +``` + #### Importing namespaces and classes You can import additional classes and namespaces right in the template: @@ -97,6 +106,23 @@ Aliased class import: {{ use({'alias' => '/app/widgets/MyWidget'}) }} ``` +#### Referencing other views + +There are two ways of referencing views in `include` and `extends` statements: + +``` +{% include "comment.twig" %} +{% extends "post.twig" % + +{% include "@app/views/snippets/avatar.twig" %} +{% extends "@app/views/layouts/2columns.twig" %} +``` + +In the first case the view will be searched relatively to the path current view is in. For `comment.twig` and `post.twig` +that means these will be searched in the same directory as the view that's rendered currently. + +In the second case we're using path aliases. All the Yii aliases such as `@app` are available by default. + #### Widgets Extension helps using widgets in convenient way converting their syntax to function calls: diff --git a/docs/internals/getting-started.md b/docs/internals/getting-started.md index 9d4b8a6..078e18b 100644 --- a/docs/internals/getting-started.md +++ b/docs/internals/getting-started.md @@ -18,17 +18,70 @@ git remote add upstream https://github.com/yiisoft/yii2.git Please refer to [Git workflow for Yii 2 contributors](git-workflow.md) for details about creating pull requests. -### Unit tests +Unit tests +---------- To run the unit tests you have to install composer packages for the dev-repo. Run `composer update` in the repo root directory to get the latest packages. -You can now execute unit tests by running `./vendor/bin/phpunit`. +You can now execute unit tests by running `phpunit`. You may limit the tests to a group of tests you are working on e.g. to run only tests for the validators and redis -`./vendor/bin/phpunit --group=validators,redis`. +`phpunit --group=validators,redis`. -### Extensions +Functional and acceptance tests +------------------------------- + +In order to run functional and acceptance tests you need to install additional composer packages for the application you're going +to test. Add the following four packages to your `composer.json` `require-dev` section: + +``` +"yiisoft/yii2-codeception": "*", +``` + +For advanced application you may need `yiisoft/yii2-faker: *` as well. + +Then for the basic application template run `./build/build app/link basic`. For advanced template command is +`./build/build app/link advanced`. + +After package installation is complete you can run the following for basic app: + +``` +cd apps/basic +codecept build +codecept run +``` + +For advanced application frontend it will be: + +``` +cd apps/advanced/frontend +codecept build +codecept run +``` + +Note that you need a running webserver in order to pass acceptance tests. That can be easily achieved with PHP's built-in +webserver: + +``` +cd apps/advanced/frontend/www +php -S 127.0.0.1:8080 index-test.php +``` + +Note that you should have Codeception and PHPUnit installed globally: + +``` +composer global require "phpunit/phpunit=4.1.*" +composer global require "codeception/codeception=2.0.*" +composer global require "codeception/specify=*" +composer global require "codeception/verify=*" +``` + +After running commands you'll see "Changed current directory to /your/global/composer/dir" message. If it's the +first time you're installing a package globally you need to add `/your/global/composer/dir/vendor/bin/` to your `PATH`. + +Extensions +---------- To work on extensions you have to install them in the application you want to try them in. Just add them to the `composer.json` as you would normally do e.g. add `"yiisoft/yii2-redis": "*"` to the diff --git a/docs/internals/translation-teams.md b/docs/internals/translation-teams.md index d19abf2..78199b2 100644 --- a/docs/internals/translation-teams.md +++ b/docs/internals/translation-teams.md @@ -34,5 +34,5 @@ Spanish Ukrainian --------- -- Alexandr Bordun [@borales](https://github.com/Borales), admin@yiiframework.com.ua -- Roman Bahatyi [@RichWeber](https://github.com/RichWeber), rbagatyi@gmail.com \ No newline at end of file +- **Alexandr Bordun [@borales](https://github.com/Borales), admin@yiiframework.com.ua** +- Roman Bahatyi [@RichWeber](https://github.com/RichWeber), rbagatyi@gmail.com diff --git a/extensions/apidoc/commands/ApiController.php b/extensions/apidoc/commands/ApiController.php index 927fbf2..17ce3fb 100644 --- a/extensions/apidoc/commands/ApiController.php +++ b/extensions/apidoc/commands/ApiController.php @@ -28,8 +28,8 @@ class ApiController extends BaseController */ public $guide; - // TODO add force update option + // TODO add force update option /** * Renders API documentation files * @param array $sourceDirs diff --git a/extensions/apidoc/commands/GuideController.php b/extensions/apidoc/commands/GuideController.php index e11ef60..c4f7495 100644 --- a/extensions/apidoc/commands/GuideController.php +++ b/extensions/apidoc/commands/GuideController.php @@ -28,6 +28,7 @@ class GuideController extends BaseController */ public $apiDocs; + /** * Renders API documentation files * @param array $sourceDirs diff --git a/extensions/apidoc/components/BaseController.php b/extensions/apidoc/components/BaseController.php index 23b9797..83521a1 100644 --- a/extensions/apidoc/components/BaseController.php +++ b/extensions/apidoc/components/BaseController.php @@ -30,6 +30,7 @@ abstract class BaseController extends Controller */ public $exclude; + protected function normalizeTargetDir($target) { $target = rtrim(Yii::getAlias($target), '\\/'); diff --git a/extensions/apidoc/helpers/ApiIndexer.php b/extensions/apidoc/helpers/ApiIndexer.php index cab957b..75e27c1 100644 --- a/extensions/apidoc/helpers/ApiIndexer.php +++ b/extensions/apidoc/helpers/ApiIndexer.php @@ -1,13 +1,12 @@ + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ */ namespace yii\apidoc\helpers; - use cebe\jssearch\Indexer; use cebe\jssearch\tokenizer\StandardTokenizer; use cebe\jssearch\TokenizerInterface; diff --git a/extensions/apidoc/helpers/ApiMarkdown.php b/extensions/apidoc/helpers/ApiMarkdown.php index 44c8ebe..6bf319d 100644 --- a/extensions/apidoc/helpers/ApiMarkdown.php +++ b/extensions/apidoc/helpers/ApiMarkdown.php @@ -31,6 +31,7 @@ class ApiMarkdown extends GithubMarkdown protected $renderingContext; + /** * Renders a code block */ diff --git a/extensions/apidoc/helpers/ApiMarkdownLaTeX.php b/extensions/apidoc/helpers/ApiMarkdownLaTeX.php index e2358b9..d3001f8 100644 --- a/extensions/apidoc/helpers/ApiMarkdownLaTeX.php +++ b/extensions/apidoc/helpers/ApiMarkdownLaTeX.php @@ -29,6 +29,7 @@ class ApiMarkdownLaTeX extends GithubMarkdown protected $renderingContext; + protected function inlineMarkers() { return array_merge(parent::inlineMarkers(), [ diff --git a/extensions/apidoc/helpers/IndexFileAnalyzer.php b/extensions/apidoc/helpers/IndexFileAnalyzer.php index 1093813..bfa6e61 100644 --- a/extensions/apidoc/helpers/IndexFileAnalyzer.php +++ b/extensions/apidoc/helpers/IndexFileAnalyzer.php @@ -17,6 +17,7 @@ class IndexFileAnalyzer extends Markdown private $_chapter = 0; private $_chapters = []; + public function analyze($text) { $this->parse($text); diff --git a/extensions/apidoc/models/BaseDoc.php b/extensions/apidoc/models/BaseDoc.php index f64f475..bff39e5 100644 --- a/extensions/apidoc/models/BaseDoc.php +++ b/extensions/apidoc/models/BaseDoc.php @@ -24,24 +24,21 @@ class BaseDoc extends Object * @var \phpDocumentor\Reflection\DocBlock\Context */ public $phpDocContext; - public $name; - public $sourceFile; public $startLine; public $endLine; - public $shortDescription; public $description; public $since; public $deprecatedSince; public $deprecatedReason; - /** * @var \phpDocumentor\Reflection\DocBlock\Tag[] */ public $tags = []; + public function hasTag($name) { foreach ($this->tags as $tag) { diff --git a/extensions/apidoc/models/ClassDoc.php b/extensions/apidoc/models/ClassDoc.php index 60dabf5..338e128 100644 --- a/extensions/apidoc/models/ClassDoc.php +++ b/extensions/apidoc/models/ClassDoc.php @@ -18,10 +18,8 @@ namespace yii\apidoc\models; class ClassDoc extends TypeDoc { public $parentClass; - public $isAbstract; public $isFinal; - /** * @var string[] */ @@ -29,7 +27,6 @@ class ClassDoc extends TypeDoc public $traits = []; // will be set by Context::updateReferences() public $subclasses = []; - /** * @var EventDoc[] */ @@ -39,6 +36,7 @@ class ClassDoc extends TypeDoc */ public $constants = []; + public function findSubject($subjectName) { if (($subject = parent::findSubject($subjectName)) !== null) { diff --git a/extensions/apidoc/models/ConstDoc.php b/extensions/apidoc/models/ConstDoc.php index 9ed9a03..2d83555 100644 --- a/extensions/apidoc/models/ConstDoc.php +++ b/extensions/apidoc/models/ConstDoc.php @@ -18,6 +18,7 @@ class ConstDoc extends BaseDoc public $definedBy; public $value; + /** * @param \phpDocumentor\Reflection\ClassReflector\ConstantReflector $reflector * @param Context $context diff --git a/extensions/apidoc/models/Context.php b/extensions/apidoc/models/Context.php index 7416996..326b24e 100644 --- a/extensions/apidoc/models/Context.php +++ b/extensions/apidoc/models/Context.php @@ -33,9 +33,9 @@ class Context extends Component * @var TraitDoc[] */ public $traits = []; - public $errors = []; + public function getType($type) { $type = ltrim($type, '\\'); diff --git a/extensions/apidoc/models/EventDoc.php b/extensions/apidoc/models/EventDoc.php index d699d67..7bb9991 100644 --- a/extensions/apidoc/models/EventDoc.php +++ b/extensions/apidoc/models/EventDoc.php @@ -20,6 +20,7 @@ class EventDoc extends ConstDoc public $type; public $types; + /** * @param \phpDocumentor\Reflection\ClassReflector\ConstantReflector $reflector * @param Context $context diff --git a/extensions/apidoc/models/FunctionDoc.php b/extensions/apidoc/models/FunctionDoc.php index c844e31..3a022d8 100644 --- a/extensions/apidoc/models/FunctionDoc.php +++ b/extensions/apidoc/models/FunctionDoc.php @@ -30,6 +30,7 @@ class FunctionDoc extends BaseDoc public $returnTypes; public $isReturnByReference; + /** * @param \phpDocumentor\Reflection\FunctionReflector $reflector * @param Context $context diff --git a/extensions/apidoc/models/InterfaceDoc.php b/extensions/apidoc/models/InterfaceDoc.php index 461302d..4426104 100644 --- a/extensions/apidoc/models/InterfaceDoc.php +++ b/extensions/apidoc/models/InterfaceDoc.php @@ -16,10 +16,10 @@ namespace yii\apidoc\models; class InterfaceDoc extends TypeDoc { public $parentInterfaces = []; - // will be set by Context::updateReferences() public $implementedBy = []; + /** * @param \phpDocumentor\Reflection\InterfaceReflector $reflector * @param Context $context diff --git a/extensions/apidoc/models/MethodDoc.php b/extensions/apidoc/models/MethodDoc.php index c420a54..6b0d329 100644 --- a/extensions/apidoc/models/MethodDoc.php +++ b/extensions/apidoc/models/MethodDoc.php @@ -17,14 +17,12 @@ class MethodDoc extends FunctionDoc { public $isAbstract; public $isFinal; - public $isStatic; - public $visibility; - // will be set by creating class public $definedBy; + /** * @param \phpDocumentor\Reflection\ClassReflector\MethodReflector $reflector * @param Context $context diff --git a/extensions/apidoc/models/ParamDoc.php b/extensions/apidoc/models/ParamDoc.php index 87b7f5f..d9f6552 100644 --- a/extensions/apidoc/models/ParamDoc.php +++ b/extensions/apidoc/models/ParamDoc.php @@ -23,13 +23,13 @@ class ParamDoc extends Object public $isOptional; public $defaultValue; public $isPassedByReference; - // will be set by creating class public $description; public $type; public $types; public $sourceFile; + /** * @param \phpDocumentor\Reflection\FunctionReflector\ArgumentReflector $reflector * @param Context $context diff --git a/extensions/apidoc/models/PropertyDoc.php b/extensions/apidoc/models/PropertyDoc.php index 8a7c883..75f8015 100644 --- a/extensions/apidoc/models/PropertyDoc.php +++ b/extensions/apidoc/models/PropertyDoc.php @@ -20,18 +20,16 @@ class PropertyDoc extends BaseDoc { public $visibility; public $isStatic; - public $type; public $types; public $defaultValue; - // will be set by creating class public $getter; public $setter; - // will be set by creating class public $definedBy; + public function getIsReadOnly() { return $this->getter !== null && $this->setter === null; diff --git a/extensions/apidoc/models/TraitDoc.php b/extensions/apidoc/models/TraitDoc.php index 6dd6eb5..a49bc95 100644 --- a/extensions/apidoc/models/TraitDoc.php +++ b/extensions/apidoc/models/TraitDoc.php @@ -18,9 +18,9 @@ class TraitDoc extends TypeDoc // classes using the trait // will be set by Context::updateReferences() public $usedBy = []; - public $traits = []; + /** * @param \phpDocumentor\Reflection\TraitReflector $reflector * @param Context $context diff --git a/extensions/apidoc/models/TypeDoc.php b/extensions/apidoc/models/TypeDoc.php index 12bbc57..be24cef 100644 --- a/extensions/apidoc/models/TypeDoc.php +++ b/extensions/apidoc/models/TypeDoc.php @@ -34,9 +34,9 @@ class TypeDoc extends BaseDoc * @var PropertyDoc[] */ public $properties = []; - public $namespace; + public function findSubject($subjectName) { if ($subjectName[0] != '$') { @@ -46,7 +46,7 @@ class TypeDoc extends BaseDoc } } } - if (substr($subjectName, -2, 2) == '()') { + if (!empty($subjectName) && substr_compare($subjectName, '()', -2, 2) === 0) { return null; } if ($this->properties === null) { diff --git a/extensions/apidoc/renderers/BaseRenderer.php b/extensions/apidoc/renderers/BaseRenderer.php index 86ab186..c7c1372 100644 --- a/extensions/apidoc/renderers/BaseRenderer.php +++ b/extensions/apidoc/renderers/BaseRenderer.php @@ -42,10 +42,10 @@ abstract class BaseRenderer extends Component * @var Controller the apidoc controller instance. Can be used to control output. */ public $controller; - public $guideUrl; public $guideReferences = []; + public function init() { ApiMarkdown::$renderer = $this; @@ -71,8 +71,8 @@ abstract class BaseRenderer extends Component $links = []; foreach ($types as $type) { $postfix = ''; - if (!is_object($type)) { - if (substr($type, -2, 2) == '[]') { + if (is_string($type)) { + if (!empty($type) && substr_compare($type, '[]', -2, 2) === 0) { $postfix = '[]'; $type = substr($type, 0, -2); } @@ -85,7 +85,7 @@ abstract class BaseRenderer extends Component ltrim($type, '\\'); } } - if (!is_object($type)) { + if (is_string($type)) { $linkText = ltrim($type, '\\'); if ($title !== null) { $linkText = $title; @@ -110,7 +110,7 @@ abstract class BaseRenderer extends Component } else { $links[] = $type; } - } else { + } elseif ($type instanceof BaseDoc) { $linkText = $type->name; if ($title !== null) { $linkText = $title; diff --git a/extensions/apidoc/templates/bootstrap/ApiRenderer.php b/extensions/apidoc/templates/bootstrap/ApiRenderer.php index 0caf2ca..a3852d7 100644 --- a/extensions/apidoc/templates/bootstrap/ApiRenderer.php +++ b/extensions/apidoc/templates/bootstrap/ApiRenderer.php @@ -24,6 +24,7 @@ class ApiRenderer extends \yii\apidoc\templates\html\ApiRenderer public $layout = '@yii/apidoc/templates/bootstrap/layouts/api.php'; public $indexView = '@yii/apidoc/templates/bootstrap/views/index.php'; + /** * @inheritdoc */ diff --git a/extensions/apidoc/templates/bootstrap/GuideRenderer.php b/extensions/apidoc/templates/bootstrap/GuideRenderer.php index f5fad98..21a2bb6 100644 --- a/extensions/apidoc/templates/bootstrap/GuideRenderer.php +++ b/extensions/apidoc/templates/bootstrap/GuideRenderer.php @@ -23,6 +23,7 @@ class GuideRenderer extends \yii\apidoc\templates\html\GuideRenderer public $layout = '@yii/apidoc/templates/bootstrap/layouts/guide.php'; + /** * @inheritDoc */ diff --git a/extensions/apidoc/templates/bootstrap/SideNavWidget.php b/extensions/apidoc/templates/bootstrap/SideNavWidget.php index 9baeb4c..dc89279 100644 --- a/extensions/apidoc/templates/bootstrap/SideNavWidget.php +++ b/extensions/apidoc/templates/bootstrap/SideNavWidget.php @@ -78,6 +78,7 @@ class SideNavWidget extends \yii\bootstrap\Widget */ public $activeUrl; + /** * Initializes the widget. */ diff --git a/extensions/apidoc/templates/html/ApiRenderer.php b/extensions/apidoc/templates/html/ApiRenderer.php index bc58866..474fa38 100644 --- a/extensions/apidoc/templates/html/ApiRenderer.php +++ b/extensions/apidoc/templates/html/ApiRenderer.php @@ -46,12 +46,14 @@ class ApiRenderer extends BaseApiRenderer implements ViewContextInterface * @var string path or alias of the view file to use for rendering the index page. */ public $indexView = '@yii/apidoc/templates/html/views/index.php'; + /** * @var View */ private $_view; private $_targetDir; + public function init() { parent::init(); diff --git a/extensions/apidoc/templates/html/GuideRenderer.php b/extensions/apidoc/templates/html/GuideRenderer.php index fbab1b2..52e0d2e 100644 --- a/extensions/apidoc/templates/html/GuideRenderer.php +++ b/extensions/apidoc/templates/html/GuideRenderer.php @@ -34,6 +34,7 @@ abstract class GuideRenderer extends BaseGuideRenderer private $_view; private $_targetDir; + public function init() { parent::init(); diff --git a/extensions/apidoc/templates/html/views/seeAlso.php b/extensions/apidoc/templates/html/views/seeAlso.php index 5d6e377..8abbf31 100644 --- a/extensions/apidoc/templates/html/views/seeAlso.php +++ b/extensions/apidoc/templates/html/views/seeAlso.php @@ -23,10 +23,12 @@ if (empty($see)) { } else { echo '

See also:

    '; foreach ($see as $ref) { - if (substr($ref, -1, 1) != '>') { - $ref .= '.'; + if (!empty($ref)) { + if (substr_compare($ref, '>', -1, 1)) { + $ref .= '.'; + } + echo "
  • $ref
  • "; } - echo "
  • $ref
  • "; } echo '
'; } diff --git a/extensions/apidoc/templates/online/ApiRenderer.php b/extensions/apidoc/templates/online/ApiRenderer.php index 017f587..8d2394d 100644 --- a/extensions/apidoc/templates/online/ApiRenderer.php +++ b/extensions/apidoc/templates/online/ApiRenderer.php @@ -20,9 +20,9 @@ class ApiRenderer extends \yii\apidoc\templates\html\ApiRenderer { public $layout = false; public $indexView = '@yii/apidoc/templates/online/views/index.php'; - public $pageTitle = 'Yii Framework 2.0 API Documentation'; + /** * @inheritdoc */ diff --git a/extensions/authclient/AuthAction.php b/extensions/authclient/AuthAction.php index 68c03fe..0d7245e 100644 --- a/extensions/authclient/AuthAction.php +++ b/extensions/authclient/AuthAction.php @@ -93,6 +93,8 @@ class AuthAction extends Action * @var string the redirect url after unsuccessful authorization (e.g. user canceled). */ private $_cancelUrl = ''; + + /** * @var string name or alias of the view file, which should be rendered in order to perform redirection. * If not set default one will be used. diff --git a/extensions/authclient/BaseClient.php b/extensions/authclient/BaseClient.php index e11c10d..95b0d30 100644 --- a/extensions/authclient/BaseClient.php +++ b/extensions/authclient/BaseClient.php @@ -58,6 +58,7 @@ abstract class BaseClient extends Component implements ClientInterface */ private $_viewOptions; + /** * @param string $id service id. */ diff --git a/extensions/authclient/BaseOAuth.php b/extensions/authclient/BaseOAuth.php index add9158..b798c8b 100644 --- a/extensions/authclient/BaseOAuth.php +++ b/extensions/authclient/BaseOAuth.php @@ -39,12 +39,6 @@ abstract class BaseOAuth extends BaseClient implements ClientInterface */ public $version = '1.0'; /** - * @var string URL, which user will be redirected after authentication at the OAuth provider web site. - * Note: this should be absolute URL (with http:// or https:// leading). - * By default current URL will be used. - */ - private $_returnUrl; - /** * @var string API base URL. */ public $apiBaseUrl; @@ -56,6 +50,13 @@ abstract class BaseOAuth extends BaseClient implements ClientInterface * @var string auth request scope. */ public $scope; + + /** + * @var string URL, which user will be redirected after authentication at the OAuth provider web site. + * Note: this should be absolute URL (with http:// or https:// leading). + * By default current URL will be used. + */ + private $_returnUrl; /** * @var array cURL request options. Option values from this field will overwrite corresponding * values from [[defaultCurlOptions()]]. @@ -70,6 +71,7 @@ abstract class BaseOAuth extends BaseClient implements ClientInterface */ private $_signatureMethod = []; + /** * @param string $returnUrl return URL */ diff --git a/extensions/authclient/CHANGELOG.md b/extensions/authclient/CHANGELOG.md index 0cac761..fd2d9f6 100644 --- a/extensions/authclient/CHANGELOG.md +++ b/extensions/authclient/CHANGELOG.md @@ -5,6 +5,7 @@ Yii Framework 2 authclient extension Change Log -------------------------- - Bug #3633: OpenId return URL comparison advanced to prevent url encode problem (klimov-paul) +- Bug #4490: `yii\authclient\widgets\AuthChoice` does not preserve initial settings while opening popup (klimov-paul) - Enh #3416: VKontakte OAuth support added (klimov-paul) - Enh #4076: Request HTTP headers argument added to `yii\authclient\BaseOAuth::api()` method (klimov-paul) - Enh #4134: `yii\authclient\InvalidResponseException` added for tracking invalid remote server response (klimov-paul) diff --git a/extensions/authclient/Collection.php b/extensions/authclient/Collection.php index d777dca..532264f 100644 --- a/extensions/authclient/Collection.php +++ b/extensions/authclient/Collection.php @@ -47,6 +47,7 @@ class Collection extends Component */ private $_clients = []; + /** * @param array $clients list of auth clients */ diff --git a/extensions/authclient/InvalidResponseException.php b/extensions/authclient/InvalidResponseException.php index 3baf08e..ef5871a 100644 --- a/extensions/authclient/InvalidResponseException.php +++ b/extensions/authclient/InvalidResponseException.php @@ -26,6 +26,7 @@ class InvalidResponseException extends Exception */ public $responseBody = ''; + /** * Constructor. * @param array $responseHeaders response headers diff --git a/extensions/authclient/OAuth1.php b/extensions/authclient/OAuth1.php index 2d78410..5fdb286 100644 --- a/extensions/authclient/OAuth1.php +++ b/extensions/authclient/OAuth1.php @@ -62,6 +62,7 @@ class OAuth1 extends BaseOAuth */ public $accessTokenMethod = 'GET'; + /** * Fetches the OAuth request token. * @param array $params additional request params. @@ -349,7 +350,7 @@ class OAuth1 extends BaseOAuth $headerParams[] = 'realm="' . rawurlencode($realm) . '"'; } foreach ($params as $key => $value) { - if (substr($key, 0, 5) != 'oauth') { + if (substr_compare($key, 'oauth', 0, 5)) { continue; } $headerParams[] = rawurlencode($key) . '="' . rawurlencode($value) . '"'; diff --git a/extensions/authclient/OAuth2.php b/extensions/authclient/OAuth2.php index 40b5192..cb3f1b3 100644 --- a/extensions/authclient/OAuth2.php +++ b/extensions/authclient/OAuth2.php @@ -50,6 +50,7 @@ class OAuth2 extends BaseOAuth */ public $tokenUrl; + /** * Composes user authorization URL. * @param array $params additional auth GET params. diff --git a/extensions/authclient/OAuthToken.php b/extensions/authclient/OAuthToken.php index 2c35225..f40ad27 100644 --- a/extensions/authclient/OAuthToken.php +++ b/extensions/authclient/OAuthToken.php @@ -43,6 +43,8 @@ class OAuthToken extends Object * @var array token parameters. */ private $_params = []; + + /** * @var integer object creation timestamp. */ diff --git a/extensions/authclient/OpenId.php b/extensions/authclient/OpenId.php index b26e5fe..3c06564 100644 --- a/extensions/authclient/OpenId.php +++ b/extensions/authclient/OpenId.php @@ -68,7 +68,6 @@ class OpenId extends BaseClient implements ClientInterface * ~~~ */ public $optionalAttributes = []; - /** * @var boolean whether to verify the peer's certificate. */ @@ -83,7 +82,6 @@ class OpenId extends BaseClient implements ClientInterface * This value will take effect only if [[verifyPeer]] is set. */ public $cainfo; - /** * @var string authentication return URL. */ @@ -96,6 +94,8 @@ class OpenId extends BaseClient implements ClientInterface * @var string client trust root (realm), by default [[\yii\web\Request::hostInfo]] value will be used. */ private $_trustRoot; + + /** * @var array data, which should be used to retrieve the OpenID response. * If not set combination of GET and POST will be used. @@ -761,7 +761,7 @@ class OpenId extends BaseClient implements ClientInterface } else { // 'ax' prefix is either undefined, or points to another extension, so we search for another prefix foreach ($this->data as $key => $value) { - if (substr($key, 0, strlen('openid_ns_')) == 'openid_ns_' && $value == 'http://openid.net/srv/ax/1.0') { + if (strncmp($key, 'openid_ns_', 10) === 0 && $value == 'http://openid.net/srv/ax/1.0') { $alias = substr($key, strlen('openid_ns_')); break; } @@ -775,7 +775,7 @@ class OpenId extends BaseClient implements ClientInterface $attributes = []; foreach ($this->data as $key => $value) { $keyMatch = 'openid_' . $alias . '_value_'; - if (substr($key, 0, strlen($keyMatch)) != $keyMatch) { + if (strncmp($key, $keyMatch, strlen($keyMatch))) { continue; } $key = substr($key, strlen($keyMatch)); @@ -802,7 +802,7 @@ class OpenId extends BaseClient implements ClientInterface $sregToAx = array_flip($this->axToSregMap); foreach ($this->data as $key => $value) { $keyMatch = 'openid_sreg_'; - if (substr($key, 0, strlen($keyMatch)) != $keyMatch) { + if (strncmp($key, $keyMatch, strlen($keyMatch))) { continue; } $key = substr($key, strlen($keyMatch)); diff --git a/extensions/authclient/clients/Facebook.php b/extensions/authclient/clients/Facebook.php index 649af8b..e257092 100644 --- a/extensions/authclient/clients/Facebook.php +++ b/extensions/authclient/clients/Facebook.php @@ -57,6 +57,7 @@ class Facebook extends OAuth2 */ public $scope = 'email'; + /** * @inheritdoc */ diff --git a/extensions/authclient/clients/GitHub.php b/extensions/authclient/clients/GitHub.php index 0350738..2fbc123 100644 --- a/extensions/authclient/clients/GitHub.php +++ b/extensions/authclient/clients/GitHub.php @@ -53,6 +53,7 @@ class GitHub extends OAuth2 */ public $apiBaseUrl = 'https://api.github.com'; + /** * @inheritdoc */ diff --git a/extensions/authclient/clients/GoogleOAuth.php b/extensions/authclient/clients/GoogleOAuth.php index 2504cb7..0a30e63 100644 --- a/extensions/authclient/clients/GoogleOAuth.php +++ b/extensions/authclient/clients/GoogleOAuth.php @@ -55,6 +55,7 @@ class GoogleOAuth extends OAuth2 */ public $apiBaseUrl = 'https://www.googleapis.com/plus/v1'; + /** * @inheritdoc */ diff --git a/extensions/authclient/clients/GoogleOpenId.php b/extensions/authclient/clients/GoogleOpenId.php index 9bcc61c..3b7d113 100644 --- a/extensions/authclient/clients/GoogleOpenId.php +++ b/extensions/authclient/clients/GoogleOpenId.php @@ -48,6 +48,7 @@ class GoogleOpenId extends OpenId 'pref/language', ]; + /** * @inheritdoc */ diff --git a/extensions/authclient/clients/LinkedIn.php b/extensions/authclient/clients/LinkedIn.php index 9b3b7e2..cbbdc18 100644 --- a/extensions/authclient/clients/LinkedIn.php +++ b/extensions/authclient/clients/LinkedIn.php @@ -56,6 +56,7 @@ class LinkedIn extends OAuth2 */ public $apiBaseUrl = 'https://api.linkedin.com/v1'; + /** * @inheritdoc */ diff --git a/extensions/authclient/clients/Live.php b/extensions/authclient/clients/Live.php index f622430..472f47c 100644 --- a/extensions/authclient/clients/Live.php +++ b/extensions/authclient/clients/Live.php @@ -53,6 +53,7 @@ class Live extends OAuth2 */ public $apiBaseUrl = 'https://apis.live.net/v5.0'; + /** * @inheritdoc */ diff --git a/extensions/authclient/clients/Twitter.php b/extensions/authclient/clients/Twitter.php index c5e4d34..3cee9b4 100644 --- a/extensions/authclient/clients/Twitter.php +++ b/extensions/authclient/clients/Twitter.php @@ -65,6 +65,7 @@ class Twitter extends OAuth1 */ public $apiBaseUrl = 'https://api.twitter.com/1.1'; + /** * @inheritdoc */ diff --git a/extensions/authclient/clients/VKontakte.php b/extensions/authclient/clients/VKontakte.php index 652f48b..23edfb6 100644 --- a/extensions/authclient/clients/VKontakte.php +++ b/extensions/authclient/clients/VKontakte.php @@ -14,7 +14,6 @@ use yii\authclient\OAuth2; * * In order to use VKontakte OAuth you must register your application at . * - * * Example application configuration: * * ~~~ @@ -54,6 +53,7 @@ class VKontakte extends OAuth2 */ public $apiBaseUrl = 'https://api.vk.com/method'; + /** * @inheritdoc */ diff --git a/extensions/authclient/clients/YandexOAuth.php b/extensions/authclient/clients/YandexOAuth.php index 7a2197c..f449f74 100644 --- a/extensions/authclient/clients/YandexOAuth.php +++ b/extensions/authclient/clients/YandexOAuth.php @@ -53,6 +53,7 @@ class YandexOAuth extends OAuth2 */ public $apiBaseUrl = 'https://login.yandex.ru'; + /** * @inheritdoc */ diff --git a/extensions/authclient/clients/YandexOpenId.php b/extensions/authclient/clients/YandexOpenId.php index 0d1c62d..58ddcfa 100644 --- a/extensions/authclient/clients/YandexOpenId.php +++ b/extensions/authclient/clients/YandexOpenId.php @@ -46,6 +46,7 @@ class YandexOpenId extends OpenId 'contact/email', ]; + /** * @inheritdoc */ diff --git a/extensions/authclient/signature/RsaSha1.php b/extensions/authclient/signature/RsaSha1.php index b17af0f..ae75af9 100644 --- a/extensions/authclient/signature/RsaSha1.php +++ b/extensions/authclient/signature/RsaSha1.php @@ -33,6 +33,8 @@ class RsaSha1 extends BaseMethod * This value can be fetched from file specified by [[publicCertificateFile]]. */ protected $_publicCertificate; + + /** * @var string path to the file, which holds private key certificate. */ diff --git a/extensions/authclient/widgets/AuthChoice.php b/extensions/authclient/widgets/AuthChoice.php index 0f26f88..163a071 100644 --- a/extensions/authclient/widgets/AuthChoice.php +++ b/extensions/authclient/widgets/AuthChoice.php @@ -96,6 +96,7 @@ class AuthChoice extends Widget */ private $_clients; + /** * @param ClientInterface[] $clients auth providers */ diff --git a/extensions/authclient/widgets/assets/authchoice.js b/extensions/authclient/widgets/assets/authchoice.js index 11ae105..e97bdf2 100644 --- a/extensions/authclient/widgets/assets/authchoice.js +++ b/extensions/authclient/widgets/assets/authchoice.js @@ -38,7 +38,7 @@ jQuery(function($) { } var url = this.href; - var popupOptions = options.popup; + var popupOptions = $.extend({}, options.popup); // clone var localPopupWidth = this.getAttribute('data-popup-width'); if (localPopupWidth) { diff --git a/extensions/bootstrap/ActiveField.php b/extensions/bootstrap/ActiveField.php index b892fdb..75890b2 100644 --- a/extensions/bootstrap/ActiveField.php +++ b/extensions/bootstrap/ActiveField.php @@ -95,17 +95,14 @@ class ActiveField extends \yii\widgets\ActiveField * @var bool whether to render [[checkboxList()]] and [[radioList()]] inline. */ public $inline = false; - /** * @var string|null optional template to render the `{input}` placeholder content */ public $inputTemplate; - /** * @var array options for the wrapper tag, used in the `{beginWrapper}` placeholder */ public $wrapperOptions = []; - /** * @var null|array CSS grid classes for horizontal layout. This must be an array with these keys: * - 'offset' the offset grid class to append to the wrapper if no label is rendered @@ -115,47 +112,40 @@ class ActiveField extends \yii\widgets\ActiveField * - 'hint' the hint grid class */ public $horizontalCssClasses; - /** * @var string the template for checkboxes in default layout */ public $checkboxTemplate = "
\n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n{error}\n{hint}\n
"; - /** * @var string the template for radios in default layout */ public $radioTemplate = "
\n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n{error}\n{hint}\n
"; - /** * @var string the template for checkboxes in horizontal layout */ public $horizontalCheckboxTemplate = "{beginWrapper}\n
\n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n
\n{error}\n{endWrapper}\n{hint}"; - /** * @var string the template for radio buttons in horizontal layout */ public $horizontalRadioTemplate = "{beginWrapper}\n
\n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n
\n{error}\n{endWrapper}\n{hint}"; - /** * @var string the template for inline checkboxLists */ public $inlineCheckboxListTemplate = "{label}\n{beginWrapper}\n{input}\n{error}\n{endWrapper}\n{hint}"; - /** * @var string the template for inline radioLists */ public $inlineRadioListTemplate = "{label}\n{beginWrapper}\n{input}\n{error}\n{endWrapper}\n{hint}"; - /** * @var bool whether to render the error. Default is `true` except for layout `inline`. */ public $enableError = true; - /** * @var bool whether to render the label. Default is `true`. */ public $enableLabel = true; + /** * @inheritdoc */ diff --git a/extensions/bootstrap/ActiveForm.php b/extensions/bootstrap/ActiveForm.php index 0a3b821..b034a00 100644 --- a/extensions/bootstrap/ActiveForm.php +++ b/extensions/bootstrap/ActiveForm.php @@ -69,7 +69,6 @@ class ActiveForm extends \yii\widgets\ActiveForm * @var array HTML attributes for the form tag. Default is `['role' => 'form']`. */ public $options = ['role' => 'form']; - /** * @var string the form layout. Either 'default', 'horizontal' or 'inline'. * By choosing a layout, an appropriate default field configuration is applied. This will @@ -79,6 +78,7 @@ class ActiveForm extends \yii\widgets\ActiveForm */ public $layout = 'default'; + /** * @inheritdoc */ diff --git a/extensions/bootstrap/Alert.php b/extensions/bootstrap/Alert.php index 60c34c8..91e8d9b 100644 --- a/extensions/bootstrap/Alert.php +++ b/extensions/bootstrap/Alert.php @@ -68,6 +68,7 @@ class Alert extends Widget */ public $closeButton = []; + /** * Initializes the widget. */ diff --git a/extensions/bootstrap/Button.php b/extensions/bootstrap/Button.php index d13c9e9..0c21a3e 100644 --- a/extensions/bootstrap/Button.php +++ b/extensions/bootstrap/Button.php @@ -39,6 +39,7 @@ class Button extends Widget */ public $encodeLabel = true; + /** * Initializes the widget. * If you override this method, make sure you call the parent implementation first. diff --git a/extensions/bootstrap/ButtonDropdown.php b/extensions/bootstrap/ButtonDropdown.php index 2324312..5fc6f62 100644 --- a/extensions/bootstrap/ButtonDropdown.php +++ b/extensions/bootstrap/ButtonDropdown.php @@ -59,6 +59,7 @@ class ButtonDropdown extends Widget */ public $encodeLabel = true; + /** * Renders the widget. */ diff --git a/extensions/bootstrap/ButtonGroup.php b/extensions/bootstrap/ButtonGroup.php index bbf54b4..7066b96 100644 --- a/extensions/bootstrap/ButtonGroup.php +++ b/extensions/bootstrap/ButtonGroup.php @@ -52,6 +52,7 @@ class ButtonGroup extends Widget */ public $encodeLabels = true; + /** * Initializes the widget. * If you override this method, make sure you call the parent implementation first. diff --git a/extensions/bootstrap/Carousel.php b/extensions/bootstrap/Carousel.php index 25027f2..68c3def 100644 --- a/extensions/bootstrap/Carousel.php +++ b/extensions/bootstrap/Carousel.php @@ -66,6 +66,7 @@ class Carousel extends Widget */ public $items = []; + /** * Initializes the widget. */ diff --git a/extensions/bootstrap/Collapse.php b/extensions/bootstrap/Collapse.php index c4f42ca..0a48448 100644 --- a/extensions/bootstrap/Collapse.php +++ b/extensions/bootstrap/Collapse.php @@ -59,6 +59,7 @@ class Collapse extends Widget */ public $items = []; + /** * Initializes the widget. */ diff --git a/extensions/bootstrap/Dropdown.php b/extensions/bootstrap/Dropdown.php index fab353b..5fe406c 100644 --- a/extensions/bootstrap/Dropdown.php +++ b/extensions/bootstrap/Dropdown.php @@ -45,7 +45,8 @@ class Dropdown extends Widget * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. */ protected $_containerOptions = []; - + + /** * Initializes the widget. * If you override this method, make sure you call the parent implementation first. diff --git a/extensions/bootstrap/Modal.php b/extensions/bootstrap/Modal.php index 6235278..62e8221 100644 --- a/extensions/bootstrap/Modal.php +++ b/extensions/bootstrap/Modal.php @@ -82,6 +82,7 @@ class Modal extends Widget */ public $toggleButton; + /** * Initializes the widget. */ diff --git a/extensions/bootstrap/Nav.php b/extensions/bootstrap/Nav.php index 3a5791f..29641a9 100644 --- a/extensions/bootstrap/Nav.php +++ b/extensions/bootstrap/Nav.php @@ -93,6 +93,7 @@ class Nav extends Widget */ public $params; + /** * Initializes the widget. */ diff --git a/extensions/bootstrap/NavBar.php b/extensions/bootstrap/NavBar.php index af41725..4a9f587 100644 --- a/extensions/bootstrap/NavBar.php +++ b/extensions/bootstrap/NavBar.php @@ -85,6 +85,7 @@ class NavBar extends Widget */ public $innerContainerOptions = []; + /** * Initializes the widget. */ diff --git a/extensions/bootstrap/Progress.php b/extensions/bootstrap/Progress.php index 1c466f6..dbeed97 100644 --- a/extensions/bootstrap/Progress.php +++ b/extensions/bootstrap/Progress.php @@ -89,6 +89,7 @@ class Progress extends Widget */ public $bars; + /** * Initializes the widget. * If you override this method, make sure you call the parent implementation first. diff --git a/extensions/bootstrap/Tabs.php b/extensions/bootstrap/Tabs.php index a618df5..87407b7 100644 --- a/extensions/bootstrap/Tabs.php +++ b/extensions/bootstrap/Tabs.php @@ -102,6 +102,7 @@ class Tabs extends Widget */ public $navType = 'nav-tabs'; + /** * Initializes the widget. */ diff --git a/extensions/bootstrap/Widget.php b/extensions/bootstrap/Widget.php index 2984e7a..6e6bc85 100644 --- a/extensions/bootstrap/Widget.php +++ b/extensions/bootstrap/Widget.php @@ -39,6 +39,7 @@ class Widget extends \yii\base\Widget */ public $clientEvents = []; + /** * Initializes the widget. * This method will register the bootstrap asset bundle. If you override this method, diff --git a/extensions/codeception/BasePage.php b/extensions/codeception/BasePage.php index 8f4d129..f3a8ae1 100644 --- a/extensions/codeception/BasePage.php +++ b/extensions/codeception/BasePage.php @@ -27,11 +27,13 @@ abstract class BasePage extends Component * the route and the rest of the name-value pairs are treated as GET parameters, e.g. `array('site/page', 'name' => 'about')`. */ public $route; + /** * @var \Codeception\AbstractGuy the testing guy object */ protected $guy; + /** * Constructor. * diff --git a/extensions/codeception/TestCase.php b/extensions/codeception/TestCase.php index c1b6952..e889249 100644 --- a/extensions/codeception/TestCase.php +++ b/extensions/codeception/TestCase.php @@ -33,6 +33,7 @@ class TestCase extends Test */ public $appConfig = '@tests/unit/_config.php'; + /** * Returns the value of an object property. * diff --git a/extensions/composer/Installer.php b/extensions/composer/Installer.php index 44c91ff..b4b6ad8 100644 --- a/extensions/composer/Installer.php +++ b/extensions/composer/Installer.php @@ -23,9 +23,9 @@ class Installer extends LibraryInstaller const EXTRA_WRITABLE = 'writable'; const EXTRA_EXECUTABLE = 'executable'; const EXTRA_CONFIG = 'config'; - const EXTENSION_FILE = 'yiisoft/extensions.php'; + /** * @inheritdoc */ diff --git a/extensions/debug/LogTarget.php b/extensions/debug/LogTarget.php index 3a402f5..043df3a 100644 --- a/extensions/debug/LogTarget.php +++ b/extensions/debug/LogTarget.php @@ -25,6 +25,7 @@ class LogTarget extends Target public $module; public $tag; + /** * @param \yii\debug\Module $module * @param array $config diff --git a/extensions/debug/Module.php b/extensions/debug/Module.php index d5c5308..a4f7908 100644 --- a/extensions/debug/Module.php +++ b/extensions/debug/Module.php @@ -194,8 +194,7 @@ class Module extends \yii\base\Module implements BootstrapInterface return true; } } - Yii::warning('Access to debugger is denied due to IP address restriction. The requested IP is ' . $ip, __METHOD__); - + Yii::warning('Access to debugger is denied due to IP address restriction. The requesting IP address is ' . $ip, __METHOD__); return false; } diff --git a/extensions/debug/Panel.php b/extensions/debug/Panel.php index 3e1bbe3..80cf845 100644 --- a/extensions/debug/Panel.php +++ b/extensions/debug/Panel.php @@ -49,6 +49,7 @@ class Panel extends Component */ public $actions = []; + /** * @return string name of the panel */ diff --git a/extensions/debug/components/search/Filter.php b/extensions/debug/components/search/Filter.php index 0048ed5..c30779e 100644 --- a/extensions/debug/components/search/Filter.php +++ b/extensions/debug/components/search/Filter.php @@ -23,6 +23,7 @@ class Filter extends Component */ protected $rules = []; + /** * Adds data filtering rule. * diff --git a/extensions/debug/components/search/matchers/Base.php b/extensions/debug/components/search/matchers/Base.php index e98a724..4fa4b9b 100644 --- a/extensions/debug/components/search/matchers/Base.php +++ b/extensions/debug/components/search/matchers/Base.php @@ -22,6 +22,7 @@ abstract class Base extends Component implements MatcherInterface */ protected $baseValue; + /** * @inheritdoc */ diff --git a/extensions/debug/components/search/matchers/SameAs.php b/extensions/debug/components/search/matchers/SameAs.php index bb3088d..65bdadc 100644 --- a/extensions/debug/components/search/matchers/SameAs.php +++ b/extensions/debug/components/search/matchers/SameAs.php @@ -20,6 +20,7 @@ class SameAs extends Base */ public $partial = false; + /** * @inheritdoc */ diff --git a/extensions/debug/controllers/DefaultController.php b/extensions/debug/controllers/DefaultController.php index 0c36aa4..dbb42d3 100644 --- a/extensions/debug/controllers/DefaultController.php +++ b/extensions/debug/controllers/DefaultController.php @@ -33,6 +33,7 @@ class DefaultController extends Controller */ public $summary; + /** * @inheritdoc */ diff --git a/extensions/debug/models/search/Db.php b/extensions/debug/models/search/Db.php index da1d6fb..771fb9f 100644 --- a/extensions/debug/models/search/Db.php +++ b/extensions/debug/models/search/Db.php @@ -23,12 +23,12 @@ class Db extends Base * @var string type of the input search value */ public $type; - /** * @var integer query attribute input search value */ public $query; + /** * @inheritdoc */ diff --git a/extensions/debug/models/search/Debug.php b/extensions/debug/models/search/Debug.php index 48863ea..1f496c8 100644 --- a/extensions/debug/models/search/Debug.php +++ b/extensions/debug/models/search/Debug.php @@ -23,47 +23,40 @@ class Debug extends Base * @var string tag attribute input search value */ public $tag; - /** * @var string ip attribute input search value */ public $ip; - /** * @var string method attribute input search value */ public $method; - /** * @var integer ajax attribute input search value */ public $ajax; - /** * @var string url attribute input search value */ public $url; - /** * @var string status code attribute input search value */ public $statusCode; - /** * @var integer sql count attribute input search value */ public $sqlCount; - /** * @var integer total mail count attribute input search value */ public $mailCount; - /** * @var array critical codes, used to determine grid row options. */ public $criticalCodes = [400, 404, 500]; + /** * @inheritdoc */ diff --git a/extensions/debug/models/search/Log.php b/extensions/debug/models/search/Log.php index 9d8814d..fe9dcf4 100644 --- a/extensions/debug/models/search/Log.php +++ b/extensions/debug/models/search/Log.php @@ -23,17 +23,16 @@ class Log extends Base * @var string ip attribute input search value */ public $level; - /** * @var string method attribute input search value */ public $category; - /** * @var integer message attribute input search value */ public $message; + /** * @inheritdoc */ diff --git a/extensions/debug/models/search/Mail.php b/extensions/debug/models/search/Mail.php index c1fac4c..50d2c2c 100644 --- a/extensions/debug/models/search/Mail.php +++ b/extensions/debug/models/search/Mail.php @@ -22,52 +22,44 @@ class Mail extends Base * @var string from attribute input search value */ public $from; - /** * @var string to attribute input search value */ public $to; - /** * @var string reply attribute input search value */ public $reply; - /** * @var string cc attribute input search value */ public $cc; - /** * @var string bcc attribute input search value */ public $bcc; - /** * @var string subject attribute input search value */ public $subject; - /** * @var string body attribute input search value */ public $body; - /** * @var string charset attribute input search value */ public $charset; - /** * @var string headers attribute input search value */ public $headers; - /** * @var string file attribute input search value */ public $file; + public function rules() { return [ diff --git a/extensions/debug/models/search/Profile.php b/extensions/debug/models/search/Profile.php index d62df7b..85f713c 100644 --- a/extensions/debug/models/search/Profile.php +++ b/extensions/debug/models/search/Profile.php @@ -23,12 +23,12 @@ class Profile extends Base * @var string method attribute input search value */ public $category; - /** * @var integer info attribute input search value */ public $info; + /** * @inheritdoc */ diff --git a/extensions/debug/panels/DbPanel.php b/extensions/debug/panels/DbPanel.php index 763e3f9..37bde4e 100644 --- a/extensions/debug/panels/DbPanel.php +++ b/extensions/debug/panels/DbPanel.php @@ -28,16 +28,17 @@ class DbPanel extends Panel * the execution is considered taking critical number of DB queries. */ public $criticalQueryThreshold; + /** * @var array db queries info extracted to array as models, to use with data provider. */ private $_models; - /** * @var array current database request timings */ private $_timings; + /** * @inheritdoc */ diff --git a/extensions/debug/panels/LogPanel.php b/extensions/debug/panels/LogPanel.php index 35b07ce..f7e5bb1 100644 --- a/extensions/debug/panels/LogPanel.php +++ b/extensions/debug/panels/LogPanel.php @@ -25,6 +25,7 @@ class LogPanel extends Panel */ private $_models; + /** * @inheritdoc */ diff --git a/extensions/debug/panels/MailPanel.php b/extensions/debug/panels/MailPanel.php index e16612d..1b6090b 100644 --- a/extensions/debug/panels/MailPanel.php +++ b/extensions/debug/panels/MailPanel.php @@ -29,11 +29,13 @@ class MailPanel extends Panel * @var string path where all emails will be saved. should be an alias. */ public $mailPath = '@runtime/debug/mail'; + /** * @var array current request sent messages */ private $_messages = []; + /** * @inheritdoc */ diff --git a/extensions/debug/panels/ProfilingPanel.php b/extensions/debug/panels/ProfilingPanel.php index 9f08fdc..16f1c9a 100644 --- a/extensions/debug/panels/ProfilingPanel.php +++ b/extensions/debug/panels/ProfilingPanel.php @@ -25,6 +25,7 @@ class ProfilingPanel extends Panel */ private $_models; + /** * @inheritdoc */ diff --git a/extensions/elasticsearch/ActiveRecord.php b/extensions/elasticsearch/ActiveRecord.php index f11d986..4f2497f 100644 --- a/extensions/elasticsearch/ActiveRecord.php +++ b/extensions/elasticsearch/ActiveRecord.php @@ -56,6 +56,7 @@ class ActiveRecord extends BaseActiveRecord private $_version; private $_highlight; + /** * Returns the database connection used by this AR class. * By default, the "elasticsearch" application component is used as the database connection. diff --git a/extensions/elasticsearch/Command.php b/extensions/elasticsearch/Command.php index 6654efd..413c31c 100644 --- a/extensions/elasticsearch/Command.php +++ b/extensions/elasticsearch/Command.php @@ -43,9 +43,9 @@ class Command extends Component * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-highlighting.html */ public $highlight; - public $options = []; + /** * @param array $options * @return mixed diff --git a/extensions/elasticsearch/Connection.php b/extensions/elasticsearch/Connection.php index 4801ed6..2e96486 100644 --- a/extensions/elasticsearch/Connection.php +++ b/extensions/elasticsearch/Connection.php @@ -46,7 +46,6 @@ class Connection extends Component * @var array the active node. key of [[nodes]]. Will be randomly selected on [[open()]]. */ public $activeNode; - // TODO http://www.elasticsearch.org/guide/en/elasticsearch/client/php-api/current/_configuration.html#_example_configuring_http_basic_auth public $auth = []; /** @@ -62,6 +61,7 @@ class Connection extends Component */ public $dataTimeout = null; + public function init() { foreach ($this->nodes as $node) { diff --git a/extensions/elasticsearch/DebugAction.php b/extensions/elasticsearch/DebugAction.php index 233f73f..57af795 100644 --- a/extensions/elasticsearch/DebugAction.php +++ b/extensions/elasticsearch/DebugAction.php @@ -36,6 +36,7 @@ class DebugAction extends Action */ public $controller; + public function run($logId, $tag) { $this->controller->loadData($tag); diff --git a/extensions/elasticsearch/DebugPanel.php b/extensions/elasticsearch/DebugPanel.php index 2813753..039a049 100644 --- a/extensions/elasticsearch/DebugPanel.php +++ b/extensions/elasticsearch/DebugPanel.php @@ -24,6 +24,7 @@ class DebugPanel extends Panel { public $db = 'elasticsearch'; + public function init() { $this->actions['elasticsearch-query'] = [ diff --git a/extensions/elasticsearch/Query.php b/extensions/elasticsearch/Query.php index de95ba3..ab0eeb1 100644 --- a/extensions/elasticsearch/Query.php +++ b/extensions/elasticsearch/Query.php @@ -137,7 +137,6 @@ class Query extends Component implements QueryInterface * on one or more fields. */ public $highlight; - public $facets = []; diff --git a/extensions/elasticsearch/QueryBuilder.php b/extensions/elasticsearch/QueryBuilder.php index e992dde..ed5bf3c 100644 --- a/extensions/elasticsearch/QueryBuilder.php +++ b/extensions/elasticsearch/QueryBuilder.php @@ -24,6 +24,7 @@ class QueryBuilder extends \yii\base\Object */ public $db; + /** * Constructor. * @param Connection $connection the database connection. diff --git a/extensions/faker/FixtureController.php b/extensions/faker/FixtureController.php index 7cc38b7..a0bc164 100644 --- a/extensions/faker/FixtureController.php +++ b/extensions/faker/FixtureController.php @@ -164,6 +164,7 @@ class FixtureController extends \yii\console\controllers\FixtureController * More info in [Faker](https://github.com/fzaninotto/Faker.) library docs. */ public $providers = []; + /** * @var \Faker\Generator Faker generator instance */ diff --git a/extensions/faker/README.md b/extensions/faker/README.md index 6a7f330..fc14819 100644 --- a/extensions/faker/README.md +++ b/extensions/faker/README.md @@ -78,7 +78,7 @@ return [ return $fixture; }, 'auth_key' => function ($fixture, $faker, $index) { - $fixture['auth_key'] = Yii::$app->getSecurity()->generateRandomKey(); + $fixture['auth_key'] = Yii::$app->getSecurity()->generateRandomString(); return $fixture; }, ]; diff --git a/extensions/gii/CHANGELOG.md b/extensions/gii/CHANGELOG.md index 71bb4e2..e70314b 100644 --- a/extensions/gii/CHANGELOG.md +++ b/extensions/gii/CHANGELOG.md @@ -8,6 +8,7 @@ Yii Framework 2 gii extension Change Log - Bug #2314: Gii model generator does not generate correct relation type in some special case (qiangxue) - Bug #3265: Fixed incorrect controller class name validation (suralc) - Bug #3693: Fixed broken Gii preview when a file is unchanged (cebe) +- Bug #4410: Fixed Gii to preserve database column order in generated _form.php (kmindi) - Enh #2018: Search model is not required anymore in CRUD generator (johonunu) - Enh #3088: The gii module will manage their own URL rules now (qiangxue) - Enh #3222: Added `useTablePrefix` option to the model generator for Gii (horizons2) diff --git a/extensions/gii/CodeFile.php b/extensions/gii/CodeFile.php index 7fa57cc..aa7ebb7 100644 --- a/extensions/gii/CodeFile.php +++ b/extensions/gii/CodeFile.php @@ -54,6 +54,7 @@ class CodeFile extends Object */ public $operation; + /** * Constructor. * @param string $path the file path that the new code should be saved to. @@ -61,7 +62,7 @@ class CodeFile extends Object */ public function __construct($path, $content) { - $this->path = strtr($path, ['/' => DIRECTORY_SEPARATOR, '\\' => DIRECTORY_SEPARATOR]); + $this->path = strtr($path, '/\\', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR); $this->content = $content; $this->id = md5($this->path); if (is_file($path)) { diff --git a/extensions/gii/Generator.php b/extensions/gii/Generator.php index 8f2d972..4620524 100644 --- a/extensions/gii/Generator.php +++ b/extensions/gii/Generator.php @@ -58,6 +58,7 @@ abstract class Generator extends Model */ public $messageCategory = 'app'; + /** * @return string name of the code generator */ diff --git a/extensions/gii/components/ActiveField.php b/extensions/gii/components/ActiveField.php index c444b7b..d8d899d 100644 --- a/extensions/gii/components/ActiveField.php +++ b/extensions/gii/components/ActiveField.php @@ -21,6 +21,7 @@ class ActiveField extends \yii\widgets\ActiveField */ public $model; + /** * @inheritdoc */ diff --git a/extensions/gii/controllers/DefaultController.php b/extensions/gii/controllers/DefaultController.php index 438fc3b..752bfab 100644 --- a/extensions/gii/controllers/DefaultController.php +++ b/extensions/gii/controllers/DefaultController.php @@ -27,6 +27,7 @@ class DefaultController extends Controller */ public $generator; + public function actionIndex() { $this->layout = 'main'; diff --git a/extensions/gii/generators/controller/Generator.php b/extensions/gii/generators/controller/Generator.php index 40cbaa4..ffd5914 100644 --- a/extensions/gii/generators/controller/Generator.php +++ b/extensions/gii/generators/controller/Generator.php @@ -46,6 +46,7 @@ class Generator extends \yii\gii\Generator */ public $actions = 'index'; + /** * @inheritdoc */ diff --git a/extensions/gii/generators/crud/Generator.php b/extensions/gii/generators/crud/Generator.php index 8a68386..e9c46a9 100644 --- a/extensions/gii/generators/crud/Generator.php +++ b/extensions/gii/generators/crud/Generator.php @@ -38,6 +38,7 @@ class Generator extends \yii\gii\Generator public $indexWidgetType = 'grid'; public $searchModelClass = ''; + /** * @inheritdoc */ @@ -373,7 +374,7 @@ class Generator extends \yii\gii\Generator $labels[$name] = 'ID'; } else { $label = Inflector::camel2words($name); - if (strcasecmp(substr($label, -3), ' id') === 0) { + if (!empty($label) && substr_compare($label, ' id', -3, 3, true) === 0) { $label = substr($label, 0, -3) . ' ID'; } $labels[$name] = $label; diff --git a/extensions/gii/generators/crud/default/views/_form.php b/extensions/gii/generators/crud/default/views/_form.php index c9dcc4f..b600266 100644 --- a/extensions/gii/generators/crud/default/views/_form.php +++ b/extensions/gii/generators/crud/default/views/_form.php @@ -28,8 +28,10 @@ use yii\widgets\ActiveForm; $form = ActiveForm::begin(); ?> -generateActiveField($attribute) . " ?>\n\n"; +getColumnNames() as $attribute) { + if (in_array($attribute, $safeAttributes)) { + echo " generateActiveField($attribute) . " ?>\n\n"; + } } ?>
Html::submitButton($model->isNewRecord ? generateString('Create') ?> : generateString('Update') ?>, ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> diff --git a/extensions/gii/generators/extension/Generator.php b/extensions/gii/generators/extension/Generator.php index 33ce047..7c719a6 100644 --- a/extensions/gii/generators/extension/Generator.php +++ b/extensions/gii/generators/extension/Generator.php @@ -34,6 +34,7 @@ class Generator extends \yii\gii\Generator public $authorName; public $authorEmail; + /** * @inheritdoc */ diff --git a/extensions/gii/generators/extension/default/AutoloadExample.php b/extensions/gii/generators/extension/default/AutoloadExample.php index 24c789b..694f0ab 100644 --- a/extensions/gii/generators/extension/default/AutoloadExample.php +++ b/extensions/gii/generators/extension/default/AutoloadExample.php @@ -1,12 +1,10 @@ - + namespace namespace, 0, -1) ?>; +/** + * This is just an example. + */ class AutoloadExample extends \yii\base\Widget { public function run() diff --git a/extensions/gii/generators/form/Generator.php b/extensions/gii/generators/form/Generator.php index 7dc36c0..f1304f9 100644 --- a/extensions/gii/generators/form/Generator.php +++ b/extensions/gii/generators/form/Generator.php @@ -26,6 +26,7 @@ class Generator extends \yii\gii\Generator public $viewName; public $scenarioName; + /** * @inheritdoc */ diff --git a/extensions/gii/generators/model/Generator.php b/extensions/gii/generators/model/Generator.php index 534dfe6..f48c18f 100644 --- a/extensions/gii/generators/model/Generator.php +++ b/extensions/gii/generators/model/Generator.php @@ -32,6 +32,7 @@ class Generator extends \yii\gii\Generator public $generateLabelsFromComments = false; public $useTablePrefix = false; + /** * @inheritdoc */ @@ -196,7 +197,7 @@ class Generator extends \yii\gii\Generator $labels[$column->name] = 'ID'; } else { $label = Inflector::camel2words($column->name); - if (strcasecmp(substr($label, -3), ' id') === 0) { + if (!empty($label) && substr_compare($label, ' id', -3, 3, true)) { $label = substr($label, 0, -3) . ' ID'; } $labels[$column->name] = $label; @@ -428,7 +429,7 @@ class Generator extends \yii\gii\Generator */ protected function generateRelationName($relations, $className, $table, $key, $multiple) { - if (strcasecmp(substr($key, -2), 'id') === 0 && strcasecmp($key, 'id')) { + if (!empty($key) && substr_compare($key, 'id', -2, 2, true) === 0 && strcasecmp($key, 'id')) { $key = rtrim(substr($key, 0, -2), '_'); } if ($multiple) { @@ -478,7 +479,7 @@ class Generator extends \yii\gii\Generator if ($this->isReservedKeyword($this->modelClass)) { $this->addError('modelClass', 'Class name cannot be a reserved PHP keyword.'); } - if (substr($this->tableName, -1) !== '*' && $this->modelClass == '') { + if ((empty($this->tableName) || substr_compare($this->tableName, '*', -1)) && $this->modelClass == '') { $this->addError('modelClass', 'Model Class cannot be blank if table name does not end with asterisk.'); } } @@ -488,7 +489,7 @@ class Generator extends \yii\gii\Generator */ public function validateTableName() { - if (strpos($this->tableName, '*') !== false && substr($this->tableName, -1) !== '*') { + if (strpos($this->tableName, '*') !== false && substr_compare($this->tableName, '*', -1)) { $this->addError('tableName', 'Asterisk is only allowed as the last character.'); return; diff --git a/extensions/gii/generators/module/Generator.php b/extensions/gii/generators/module/Generator.php index 353afe6..6b139ff 100644 --- a/extensions/gii/generators/module/Generator.php +++ b/extensions/gii/generators/module/Generator.php @@ -26,6 +26,7 @@ class Generator extends \yii\gii\Generator public $moduleClass; public $moduleID; + /** * @inheritdoc */ @@ -146,7 +147,7 @@ EOD; if (strpos($this->moduleClass, '\\') === false || Yii::getAlias('@' . str_replace('\\', '/', $this->moduleClass), false) === false) { $this->addError('moduleClass', 'Module class must be properly namespaced.'); } - if (substr($this->moduleClass, -1, 1) == '\\') { + if (empty($this->moduleClass) || substr_compare($this->moduleClass, '\\', -1, 1) === 0) { $this->addError('moduleClass', 'Module class name must not be empty. Please enter a fully qualified class name. e.g. "app\\modules\\admin\\Module".'); } } diff --git a/extensions/imagine/BaseImage.php b/extensions/imagine/BaseImage.php index 1cd51c0..ac166e6 100644 --- a/extensions/imagine/BaseImage.php +++ b/extensions/imagine/BaseImage.php @@ -41,6 +41,7 @@ class BaseImage * gmagick driver definition. */ const DRIVER_GMAGICK = 'gmagick'; + /** * @var array|string the driver to use. This can be either a single driver name or an array of driver names. * If the latter, the first available driver will be used. @@ -52,6 +53,7 @@ class BaseImage */ private static $_imagine; + /** * Returns the `Imagine` object that supports various image manipulations. * @return ImagineInterface the `Imagine` object diff --git a/extensions/jui/Accordion.php b/extensions/jui/Accordion.php index 7847e78..b7c7c1e 100644 --- a/extensions/jui/Accordion.php +++ b/extensions/jui/Accordion.php @@ -85,6 +85,7 @@ class Accordion extends Widget */ public $headerOptions = []; + /** * Renders the widget. */ diff --git a/extensions/jui/DatePicker.php b/extensions/jui/DatePicker.php index 6eb865f..dafd3fa 100644 --- a/extensions/jui/DatePicker.php +++ b/extensions/jui/DatePicker.php @@ -60,6 +60,7 @@ class DatePicker extends InputWidget */ public $containerOptions = []; + /** * @inheritdoc */ diff --git a/extensions/jui/InputWidget.php b/extensions/jui/InputWidget.php index b668ca4..9631518 100644 --- a/extensions/jui/InputWidget.php +++ b/extensions/jui/InputWidget.php @@ -36,6 +36,7 @@ class InputWidget extends Widget */ public $value; + /** * Initializes the widget. * If you override this method, make sure you call the parent implementation first. diff --git a/extensions/jui/Menu.php b/extensions/jui/Menu.php index 5a6c829..140906f 100644 --- a/extensions/jui/Menu.php +++ b/extensions/jui/Menu.php @@ -33,6 +33,7 @@ class Menu extends \yii\widgets\Menu */ public $clientEvents = []; + /** * Initializes the widget. * If you override this method, make sure you call the parent implementation first. diff --git a/extensions/jui/Selectable.php b/extensions/jui/Selectable.php index a8302b5..1cf9aec 100644 --- a/extensions/jui/Selectable.php +++ b/extensions/jui/Selectable.php @@ -79,6 +79,7 @@ class Selectable extends Widget */ public $itemOptions = []; + /** * Renders the widget. */ diff --git a/extensions/jui/Slider.php b/extensions/jui/Slider.php index 64c79e5..740a85f 100644 --- a/extensions/jui/Slider.php +++ b/extensions/jui/Slider.php @@ -37,6 +37,7 @@ class Slider extends Widget 'stop' => 'slidestop', ]; + /** * Executes the widget. */ diff --git a/extensions/jui/SliderInput.php b/extensions/jui/SliderInput.php index 967a91e..760dfb3 100644 --- a/extensions/jui/SliderInput.php +++ b/extensions/jui/SliderInput.php @@ -44,6 +44,12 @@ use yii\helpers\Html; class SliderInput extends InputWidget { /** + * @var array the HTML attributes for the container tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $containerOptions = []; + + /** * @inheritDoc */ protected $clientEventMap = [ @@ -53,11 +59,7 @@ class SliderInput extends InputWidget 'start' => 'slidestart', 'stop' => 'slidestop', ]; - /** - * @var array the HTML attributes for the container tag. - * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. - */ - public $containerOptions = []; + /** * @inheritdoc diff --git a/extensions/jui/Sortable.php b/extensions/jui/Sortable.php index b974d5a..97a7691 100644 --- a/extensions/jui/Sortable.php +++ b/extensions/jui/Sortable.php @@ -88,6 +88,7 @@ class Sortable extends Widget 'update' => 'sortupdate', ]; + /** * Renders the widget. */ diff --git a/extensions/jui/Spinner.php b/extensions/jui/Spinner.php index 2daa866..eb6ad04 100644 --- a/extensions/jui/Spinner.php +++ b/extensions/jui/Spinner.php @@ -44,6 +44,7 @@ class Spinner extends InputWidget 'spin' => 'spin', ]; + /** * Renders the widget. */ diff --git a/extensions/jui/Tabs.php b/extensions/jui/Tabs.php index 52d70dc..017d028 100644 --- a/extensions/jui/Tabs.php +++ b/extensions/jui/Tabs.php @@ -98,6 +98,7 @@ class Tabs extends Widget */ public $encodeLabels = true; + /** * Renders the widget. */ diff --git a/extensions/jui/Widget.php b/extensions/jui/Widget.php index 975aab4..a99bd8c 100644 --- a/extensions/jui/Widget.php +++ b/extensions/jui/Widget.php @@ -58,6 +58,7 @@ class Widget extends \yii\base\Widget */ protected $clientEventMap = []; + /** * Initializes the widget. * If you override this method, make sure you call the parent implementation first. diff --git a/extensions/mongodb/ActiveFixture.php b/extensions/mongodb/ActiveFixture.php index 7934023..7c83096 100644 --- a/extensions/mongodb/ActiveFixture.php +++ b/extensions/mongodb/ActiveFixture.php @@ -39,6 +39,7 @@ class ActiveFixture extends BaseActiveFixture */ public $collectionName; + /** * @inheritdoc */ diff --git a/extensions/mongodb/Cache.php b/extensions/mongodb/Cache.php index a1e3c4c..62593d2 100644 --- a/extensions/mongodb/Cache.php +++ b/extensions/mongodb/Cache.php @@ -54,6 +54,7 @@ class Cache extends \yii\caching\Cache */ public $gcProbability = 100; + /** * Initializes the Cache component. * This method will initialize the [[db]] property to make sure it refers to a valid MongoDB connection. diff --git a/extensions/mongodb/Collection.php b/extensions/mongodb/Collection.php index d79311c..e32aa04 100644 --- a/extensions/mongodb/Collection.php +++ b/extensions/mongodb/Collection.php @@ -72,6 +72,7 @@ class Collection extends Object */ public $mongoCollection; + /** * @return string name of this collection. */ @@ -221,7 +222,11 @@ class Collection extends Object Yii::info($token, __METHOD__); try { Yii::beginProfile($token, __METHOD__); - $result = $this->mongoCollection->ensureIndex($keys, $options); + if (method_exists($this->mongoCollection, 'createIndex')) { + $result = $this->mongoCollection->createIndex($keys, $options); + } else { + $result = $this->mongoCollection->ensureIndex($keys, $options); + } $this->tryResultError($result); Yii::endProfile($token, __METHOD__); diff --git a/extensions/mongodb/Connection.php b/extensions/mongodb/Connection.php index 50ff815..d97ce67 100644 --- a/extensions/mongodb/Connection.php +++ b/extensions/mongodb/Connection.php @@ -112,11 +112,13 @@ class Connection extends Component * @var \MongoClient Mongo client instance. */ public $mongoClient; + /** * @var Database[] list of Mongo databases */ private $_databases = []; + /** * Returns the Mongo collection with the given name. * @param string|null $name collection name, if null default one will be used. diff --git a/extensions/mongodb/Database.php b/extensions/mongodb/Database.php index 3027349..33ed6f7 100644 --- a/extensions/mongodb/Database.php +++ b/extensions/mongodb/Database.php @@ -26,6 +26,7 @@ class Database extends Object * @var \MongoDB Mongo database instance. */ public $mongoDb; + /** * @var Collection[] list of collections. */ @@ -35,6 +36,7 @@ class Database extends Object */ private $_fileCollections = []; + /** * @return string name of this database. */ diff --git a/extensions/mongodb/Migration.php b/extensions/mongodb/Migration.php index e92e2be..265f2fa 100644 --- a/extensions/mongodb/Migration.php +++ b/extensions/mongodb/Migration.php @@ -39,6 +39,7 @@ abstract class Migration extends Component implements MigrationInterface */ public $db = 'mongodb'; + /** * Initializes the migration. * This method will set [[db]] to be the 'db' application component, if it is null. diff --git a/extensions/mongodb/Query.php b/extensions/mongodb/Query.php index 8a5681b..32cb2f9 100644 --- a/extensions/mongodb/Query.php +++ b/extensions/mongodb/Query.php @@ -54,6 +54,7 @@ class Query extends Component implements QueryInterface */ public $from; + /** * Returns the Mongo collection for this query. * @param Connection $db Mongo connection. diff --git a/extensions/mongodb/console/controllers/MigrateController.php b/extensions/mongodb/console/controllers/MigrateController.php index b6b5b9a..e6b05a1 100644 --- a/extensions/mongodb/console/controllers/MigrateController.php +++ b/extensions/mongodb/console/controllers/MigrateController.php @@ -69,6 +69,7 @@ class MigrateController extends BaseMigrateController */ public $db = 'mongodb'; + /** * @inheritdoc */ diff --git a/extensions/mongodb/file/Collection.php b/extensions/mongodb/file/Collection.php index d0d609f..bb37a02 100644 --- a/extensions/mongodb/file/Collection.php +++ b/extensions/mongodb/file/Collection.php @@ -28,11 +28,13 @@ class Collection extends \yii\mongodb\Collection * @var \MongoGridFS Mongo GridFS collection instance. */ public $mongoCollection; + /** * @var \yii\mongodb\Collection file chunks Mongo collection. */ private $_chunkCollection; + /** * Returns the Mongo collection for the file chunks. * @param boolean $refresh whether to reload the collection instance even if it is found in the cache. diff --git a/extensions/mongodb/gii/model/Generator.php b/extensions/mongodb/gii/model/Generator.php index cdfde9f..3b007f3 100644 --- a/extensions/mongodb/gii/model/Generator.php +++ b/extensions/mongodb/gii/model/Generator.php @@ -29,6 +29,7 @@ class Generator extends \yii\gii\Generator public $modelClass; public $baseClass = 'yii\mongodb\ActiveRecord'; + /** * @inheritdoc */ @@ -182,7 +183,7 @@ class Generator extends \yii\gii\Generator $label = 'ID'; } else { $label = Inflector::camel2words($attribute); - if (strcasecmp(substr($label, -3), ' id') === 0) { + if (substr_compare($label, ' id', -3, 3, true) === 0) { $label = substr($label, 0, -3) . ' ID'; } } diff --git a/extensions/mongodb/log/MongoDbTarget.php b/extensions/mongodb/log/MongoDbTarget.php index 2998e43..e227820 100644 --- a/extensions/mongodb/log/MongoDbTarget.php +++ b/extensions/mongodb/log/MongoDbTarget.php @@ -37,6 +37,7 @@ class MongoDbTarget extends Target */ public $logCollection = 'log'; + /** * Initializes the MongoDbTarget component. * This method will initialize the [[db]] property to make sure it refers to a valid MongoDB connection. diff --git a/extensions/redis/ActiveQuery.php b/extensions/redis/ActiveQuery.php index 5c0b921..65da8b3 100644 --- a/extensions/redis/ActiveQuery.php +++ b/extensions/redis/ActiveQuery.php @@ -82,6 +82,7 @@ class ActiveQuery extends Component implements ActiveQueryInterface */ const EVENT_INIT = 'init'; + /** * Constructor. * @param array $modelClass the model class associated with this query diff --git a/extensions/redis/Cache.php b/extensions/redis/Cache.php index 5b880b3..995a0d7 100644 --- a/extensions/redis/Cache.php +++ b/extensions/redis/Cache.php @@ -67,6 +67,7 @@ class Cache extends \yii\caching\Cache */ public $redis = 'redis'; + /** * Initializes the redis Cache component. * This method will initialize the [[redis]] property to make sure it refers to a valid redis connection. diff --git a/extensions/redis/Connection.php b/extensions/redis/Connection.php index 0d00b7b..6b5b700 100644 --- a/extensions/redis/Connection.php +++ b/extensions/redis/Connection.php @@ -209,6 +209,7 @@ class Connection extends Component 'ZSCORE', // key member Get the score associated with the given member in a sorted set 'ZUNIONSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Add multiple sorted sets and store the resulting sorted set in a new key ]; + /** * @var resource redis socket connection */ diff --git a/extensions/smarty/ViewRenderer.php b/extensions/smarty/ViewRenderer.php index eb9a7f0..7b70963 100644 --- a/extensions/smarty/ViewRenderer.php +++ b/extensions/smarty/ViewRenderer.php @@ -25,17 +25,16 @@ class ViewRenderer extends BaseViewRenderer * @var string the directory or path alias pointing to where Smarty cache will be stored. */ public $cachePath = '@runtime/Smarty/cache'; - /** * @var string the directory or path alias pointing to where Smarty compiled templates will be stored. */ public $compilePath = '@runtime/Smarty/compile'; - /** * @var Smarty */ public $smarty; + public function init() { $this->smarty = new Smarty(); diff --git a/extensions/sphinx/ActiveRecord.php b/extensions/sphinx/ActiveRecord.php index 7020f99..ce8a9de 100644 --- a/extensions/sphinx/ActiveRecord.php +++ b/extensions/sphinx/ActiveRecord.php @@ -54,6 +54,7 @@ abstract class ActiveRecord extends BaseActiveRecord */ private $_snippet; + /** * Returns the Sphinx connection used by this AR class. * By default, the "sphinx" application component is used as the Sphinx connection. diff --git a/extensions/sphinx/ColumnSchema.php b/extensions/sphinx/ColumnSchema.php index 240d41f..3c7632c 100644 --- a/extensions/sphinx/ColumnSchema.php +++ b/extensions/sphinx/ColumnSchema.php @@ -54,6 +54,7 @@ class ColumnSchema extends Object */ public $isMva; + /** * Converts the input value according to [[phpType]] after retrieval from the database. * If the value is null or an [[Expression]], it will not be converted. diff --git a/extensions/sphinx/Command.php b/extensions/sphinx/Command.php index 7318bba..44fa322 100644 --- a/extensions/sphinx/Command.php +++ b/extensions/sphinx/Command.php @@ -49,6 +49,7 @@ class Command extends \yii\db\Command */ public $db; + /** * Creates a batch INSERT command. * For example, diff --git a/extensions/sphinx/Connection.php b/extensions/sphinx/Connection.php index 9ba141b..32ba492 100644 --- a/extensions/sphinx/Connection.php +++ b/extensions/sphinx/Connection.php @@ -67,6 +67,7 @@ class Connection extends \yii\db\Connection 'mysql' => 'yii\sphinx\Schema', // MySQL ]; + /** * Obtains the schema information for the named index. * @param string $name index name. diff --git a/extensions/sphinx/IndexSchema.php b/extensions/sphinx/IndexSchema.php index 3ce18a6..21ede67 100644 --- a/extensions/sphinx/IndexSchema.php +++ b/extensions/sphinx/IndexSchema.php @@ -40,6 +40,7 @@ class IndexSchema extends Object */ public $columns = []; + /** * Gets the named column metadata. * This is a convenient method for retrieving a named column even if it does not exist. diff --git a/extensions/sphinx/Query.php b/extensions/sphinx/Query.php index 8a63fa3..397bb02 100644 --- a/extensions/sphinx/Query.php +++ b/extensions/sphinx/Query.php @@ -125,11 +125,13 @@ class Query extends Component implements QueryInterface * @var array query options for the call snippet. */ public $snippetOptions; + /** * @var Connection the Sphinx connection used to generate the SQL statements. */ private $_connection; + /** * @param Connection $connection Sphinx connection instance * @return static the query object itself diff --git a/extensions/sphinx/QueryBuilder.php b/extensions/sphinx/QueryBuilder.php index 48c7d55..f1b434a 100644 --- a/extensions/sphinx/QueryBuilder.php +++ b/extensions/sphinx/QueryBuilder.php @@ -38,6 +38,7 @@ class QueryBuilder extends Object */ public $separator = " "; + /** * Constructor. * @param Connection $connection the Sphinx connection. diff --git a/extensions/sphinx/Schema.php b/extensions/sphinx/Schema.php index d0cf965..83fbb96 100644 --- a/extensions/sphinx/Schema.php +++ b/extensions/sphinx/Schema.php @@ -60,6 +60,7 @@ class Schema extends Object */ private $_builder; + /** * @var array mapping from physical column types (keys) to abstract column types (values) */ diff --git a/extensions/sphinx/gii/model/Generator.php b/extensions/sphinx/gii/model/Generator.php index 07664db..8994ccf 100644 --- a/extensions/sphinx/gii/model/Generator.php +++ b/extensions/sphinx/gii/model/Generator.php @@ -29,6 +29,7 @@ class Generator extends \yii\gii\Generator public $baseClass = 'yii\sphinx\ActiveRecord'; public $useIndexPrefix = false; + /** * @inheritdoc */ @@ -180,7 +181,7 @@ class Generator extends \yii\gii\Generator $labels[$column->name] = 'ID'; } else { $label = Inflector::camel2words($column->name); - if (strcasecmp(substr($label, -3), ' id') === 0) { + if (substr_compare($label, ' id', -3, 3, true) === 0) { $label = substr($label, 0, -3) . ' ID'; } $labels[$column->name] = $label; @@ -266,7 +267,7 @@ class Generator extends \yii\gii\Generator if ($this->isReservedKeyword($this->modelClass)) { $this->addError('modelClass', 'Class name cannot be a reserved PHP keyword.'); } - if (substr($this->indexName, -1) !== '*' && $this->modelClass == '') { + if ((empty($this->indexName) || substr_compare($this->indexName, '*', -1)) && $this->modelClass == '') { $this->addError('modelClass', 'Model Class cannot be blank if table name does not end with asterisk.'); } } @@ -276,7 +277,7 @@ class Generator extends \yii\gii\Generator */ public function validateIndexName() { - if (strpos($this->indexName, '*') !== false && substr($this->indexName, -1) !== '*') { + if (strpos($this->indexName, '*') !== false && substr_compare($this->indexName, '*', -1)) { $this->addError('indexName', 'Asterisk is only allowed as the last character.'); return; diff --git a/extensions/swiftmailer/Mailer.php b/extensions/swiftmailer/Mailer.php index 3368971..179365c 100644 --- a/extensions/swiftmailer/Mailer.php +++ b/extensions/swiftmailer/Mailer.php @@ -79,6 +79,7 @@ class Mailer extends BaseMailer * @var string message default class name. */ public $messageClass = 'yii\swiftmailer\Message'; + /** * @var \Swift_Mailer Swift mailer instance. */ @@ -88,6 +89,7 @@ class Mailer extends BaseMailer */ private $_transport = []; + /** * @return array|\Swift_Mailer Swift mailer instance or array configuration. */ diff --git a/extensions/swiftmailer/Message.php b/extensions/swiftmailer/Message.php index 0c2a208..5ca0cca 100644 --- a/extensions/swiftmailer/Message.php +++ b/extensions/swiftmailer/Message.php @@ -29,6 +29,7 @@ class Message extends BaseMessage */ private $_swiftMessage; + /** * @return \Swift_Message Swift message instance. */ diff --git a/extensions/twig/CHANGELOG.md b/extensions/twig/CHANGELOG.md index c2246aa..7e498c7 100644 --- a/extensions/twig/CHANGELOG.md +++ b/extensions/twig/CHANGELOG.md @@ -8,6 +8,10 @@ Yii Framework 2 twig extension Change Log - Bug #3767: Fixed repeated adding of extensions when using config. One may now pass extension instances as well (grachov) - Bug #3877: Fixed `lexerOptions` throwing exception (dapatrese) - Enh #1799: Added `form_begin`, `form_end` to twig extension (samdark) +- Enh #3674: Various enhancements (samdark) + - Removed `FileLoader` and used `\Twig_Loader_Filesystem` instead. + - Added support of Yii's aliases. + - Added `set()` that allows setting object properties. - Chg #3535: Syntax changes: - Removed `form_begin`, `form_end` (samdark) - Added `use()` and `ViewRenderer::uses` that are importing classes and namespaces (grachov, samdark) diff --git a/extensions/twig/Extension.php b/extensions/twig/Extension.php index dbeb425..0680782 100644 --- a/extensions/twig/Extension.php +++ b/extensions/twig/Extension.php @@ -24,17 +24,16 @@ class Extension extends \Twig_Extension * @var array used namespaces */ protected $namespaces = []; - /** * @var array used class aliases */ protected $aliases = []; - /** * @var array used widgets */ protected $widgets = []; + /** * Creates new instance * @@ -72,6 +71,7 @@ class Extension extends \Twig_Extension new \Twig_SimpleFunction('path', [$this, 'path']), new \Twig_SimpleFunction('url', [$this, 'url']), new \Twig_SimpleFunction('void', function(){}), + new \Twig_SimpleFunction('set', [$this, 'setProperty']), ]; $options = array_merge($options, [ @@ -188,6 +188,11 @@ class Extension extends \Twig_Extension return Url::to(array_merge([$path], $args), true); } + public function setProperty($object, $property, $value) + { + $object->$property = $value; + } + /** * @inheritdoc */ diff --git a/extensions/twig/FileLoader.php b/extensions/twig/FileLoader.php deleted file mode 100644 index ed6ac82..0000000 --- a/extensions/twig/FileLoader.php +++ /dev/null @@ -1,72 +0,0 @@ - - */ -class FileLoader implements \Twig_LoaderInterface -{ - /** - * @var string Path to directory - */ - private $_dir; - - /** - * @param string $dir path to directory - */ - public function __construct($dir) - { - $this->_dir = $dir; - } - - /** - * Compare a file's freshness with previously stored timestamp - * - * @param string $name file name to check - * @param integer $time timestamp to compare with - * @return boolean true if file is still fresh and not changes, false otherwise - */ - public function isFresh($name, $time) - { - return filemtime($this->getFilePath($name)) <= $time; - } - - /** - * Get the source of given file name - * - * @param string $name file name - * @return string contents of given file name - */ - public function getSource($name) - { - return file_get_contents($this->getFilePath($name)); - } - - /** - * Get unique key that can represent this file uniquely among other files. - * @param string $name - * @return string - */ - public function getCacheKey($name) - { - return $this->getFilePath($name); - } - - /** - * internally used to get absolute path of given file name - * @param string $name file name - * @return string absolute path of file - */ - protected function getFilePath($name) - { - return $this->_dir . '/' . $name; - } -} diff --git a/extensions/twig/ViewRenderer.php b/extensions/twig/ViewRenderer.php index c897292..8f634a6 100644 --- a/extensions/twig/ViewRenderer.php +++ b/extensions/twig/ViewRenderer.php @@ -26,13 +26,11 @@ class ViewRenderer extends BaseViewRenderer * templates cache. */ public $cachePath = '@runtime/Twig/cache'; - /** * @var array Twig options. * @see http://twig.sensiolabs.org/doc/api.html#environment-options */ public $options = []; - /** * @var array Objects or static classes. * Keys of the array are names to call in template, values are objects or names of static classes. @@ -40,7 +38,6 @@ class ViewRenderer extends BaseViewRenderer * In the template you can use it like this: `{{ html.a('Login', 'site/login') | raw }}`. */ public $globals = []; - /** * @var array Custom functions. * Keys of the array are names to call in template, values are names of functions or static methods of some class. @@ -48,7 +45,6 @@ class ViewRenderer extends BaseViewRenderer * In the template you can use it like this: `{{ rot13('test') }}` or `{{ a('Login', 'site/login') | raw }}`. */ public $functions = []; - /** * @var array Custom filters. * Keys of the array are names to call in template, values are names of functions or static methods of some class. @@ -56,13 +52,11 @@ class ViewRenderer extends BaseViewRenderer * In the template you can use it like this: `{{ 'test'|rot13 }}` or `{{ model|jsonEncode }}`. */ public $filters = []; - /** * @var array Custom extensions. * Example: `['Twig_Extension_Sandbox', new \Twig_Extension_Text()]` */ public $extensions = []; - /** * @var array Twig lexer options. * @@ -77,7 +71,6 @@ class ViewRenderer extends BaseViewRenderer * @see http://twig.sensiolabs.org/doc/recipes.html#customizing-the-syntax */ public $lexerOptions = []; - /** * @var array namespaces and classes to import. * @@ -92,12 +85,12 @@ class ViewRenderer extends BaseViewRenderer * ``` */ public $uses = []; - /** * @var \Twig_Environment twig environment object that renders twig templates */ public $twig; + public function init() { $this->twig = new \Twig_Environment(null, array_merge([ @@ -150,7 +143,12 @@ class ViewRenderer extends BaseViewRenderer public function render($view, $file, $params) { $this->twig->addGlobal('this', $view); - $this->twig->setLoader(new FileLoader(dirname($file))); + $loader = new \Twig_Loader_Filesystem(dirname($file)); + + foreach (Yii::$aliases as $alias => $path) { + $loader->addPath($path, substr($alias, 1)); + } + $this->twig->setLoader($loader); return $this->twig->render(pathinfo($file, PATHINFO_BASENAME), $params); } diff --git a/extensions/twig/ViewRendererStaticClassProxy.php b/extensions/twig/ViewRendererStaticClassProxy.php index 343417d..6ec612e 100644 --- a/extensions/twig/ViewRendererStaticClassProxy.php +++ b/extensions/twig/ViewRendererStaticClassProxy.php @@ -17,6 +17,7 @@ class ViewRendererStaticClassProxy { private $_staticClassName; + public function __construct($staticClassName) { $this->_staticClassName = $staticClassName; diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index ad0fdd7..e0e8a35 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -69,14 +69,25 @@ Yii Framework 2 Change Log - Bug #4241: `yii\widgets\Pjax` was incorrectly setting container id (mitalcoi) - Bug #4276: Added check for UPLOAD_ERR_NO_FILE in `yii\web\UploadedFile` and return null if no file was uploaded (OmgDef) - Bug #4342: mssql (dblib) driver does not support getting attributes (tof06) +- Bug #4409: Upper case letters in subdirectory prefixes of controller IDs were not properly handled (qiangxue) +- Bug #4412: Formatter used SI Prefixes for base 1024, now uses binary prefixes (kmindi) +- Bug #4427: Formatter could do one division too much (kmindi) +- Bug #4453: `yii message/extract` wasn't properly writing to po files in case of multiple categories (samdark) +- Bug #4469: Make `Security::compareString()` timing depend only on length of `$actual` input and add unit test. (tom--) +- Bug #4470: Avoid endless loop when exporting logs with low values of flushInterval and eportInterval (cebe) +- Bug #4497: Fixed StringHelper::byteSubstr() returning empty string on null $length param (mbman) +- Bug #4514: Fixed Request class crashing when empty CSRF token value is sent in cookie (cebe) +- Bug #4519: `yii\base\Model::isAttributeRequired()` should check if the `when` option of the validator is set (thiagotalma) - Bug: Fixed inconsistent return of `\yii\console\Application::runAction()` (samdark) - Bug: URL encoding for the route parameter added to `\yii\web\UrlManager` (klimov-paul) - Bug: Fixed the bug that requesting protected or private action methods would cause 500 error instead of 404 (qiangxue) - Bug: Fixed Object of class Imagick could not be converted to string in CaptchaAction (eXprojects, cebe) +- Bug: Fixed wrong behavior of `StringHelper::byteSubstr()` in some edge cases (cebe) - Enh #87: Helper `yii\helpers\Security` converted into application component, cryptographic strength improved (klimov-paul) - Enh #422: Added Support for BIT(M) data type default values in Schema (cebe) - Enh #1160: Added $strict parameter to Inflector::camel2id() to handle consecutive uppercase chars (schmunk) - Enh #1249: Added support for Active Record relation via array attributes (klimov-paul, cebe) +- Enh #1388: Added mapping from physical types to abstract types for OCI DB driver (qiangxue) - Enh #1452: Added `Module::getInstance()` to allow accessing the module instance from anywhere within the module (qiangxue) - Enh #2264: `CookieCollection::has()` will return false for expired or removed cookies (qiangxue) - Enh #2435: `yii\db\IntegrityException` is now thrown on database integrity errors instead of general `yii\db\Exception` (samdark) @@ -131,6 +142,7 @@ Yii Framework 2 Change Log - Removed character maps for non-latin languages. - Improved overall slug results. - Added note about the fact that intl is required for non-latin languages to requirements checker. +- Enh #3957: Added more straightforward configurable properties to `BlameableBehavior`, `SluggableBehavior` and `TimestampBehavior` (creocoder) - Enh #3992: In mail layouts you can now access the message object via `$message` variable (qiangxue) - Enh #4028: Added ability to `yii\widgets\Menu` to encode each item's label separately (creocoder, umneeq) - Enh #4048: Added `init` event to `ActiveQuery` classes (qiangxue) @@ -143,9 +155,18 @@ Yii Framework 2 Change Log - Enh #4080: Added proper handling and support of the symlinked directories in `FileHelper`, added $options parameter in `FileHelper::removeDirectory()` (resurtm) - Enh #4086: changedAttributes of afterSave Event now contain old values (dizews) - Enh #4114: Added `Security::generateRandomBytes()`, improved tests (samdark) +- Enh #4131: Security adjustments (tom--) + - Added HKDF to `yii\base\Security`. + - Reverted auto fallback to PHP PBKDF2. + - Fixed PBKDF2 key truncation. + - Adjusted API. - Enh #4209: Added `beforeCopy`, `afterCopy`, `forceCopy` properties to AssetManager (cebe) - Enh #4297: Added check for DOM extension to requirements (samdark) - Enh #4317: Added `absoluteAuthTimeout` to yii\web\User (ivokund, nkovacs) +- Enh #4360: Added client validation support for file validator (Skysplit) +- Enh #4436: Added callback functions to AJAX-based form validation (thiagotalma) +- Enh #4485: Added support for deferred validation in `ActiveForm` (Alex-Code) +- Enh #4520: Added sasl support to `yii\caching\MemCache` (xjflyttp) - Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue) - Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue) - Enh: Added `yii\web\UrlManager::addRules()` to simplify adding new URL rules (qiangxue) @@ -186,8 +207,9 @@ Yii Framework 2 Change Log - Chg #4071: `mail` component renamed to `mailer`, `yii\log\EmailTarget::$mail` renamed to `yii\log\EmailTarget::$mailer` (samdark) - Chg #4147: `BaseMailer::compose()` will not overwrite the `message` parameter if it is explicitly provided (qiangxue) - Chg #4201: change default value of `SyslogTarget::facility` from LOG_SYSLOG to LOG_USER (dizews) +- Chg #4211: BaseActiveRecord::populateRecord now silently hide selected columns that are not defined in AR instead of failing with an error (miramir) - Chg #4227: `\yii\widgets\LinkPager::$hideOnSinglePage` is now `true` by default (samdark) -- Chg #4310: Removed `$data` from signature of `yii\rbac\ManagerInterface` (samdark) +- Chg #4310: Removed `$data` from signature of `yii\rbac\ManagerInterface` (samdark) - Chg #4318: `yii\helpers\Html::ul()` and `ol()` will return an empty list tag if an empty item array is given (qiangxue) - Chg #4331: `yii\helpers\Url` now uses `UrlManager` to determine base URL when generating URLs (qiangxue) - Chg: Replaced `clearAll()` and `clearAllAssignments()` in `yii\rbac\ManagerInterface` with `removeAll()`, `removeAllRoles()`, `removeAllPermissions()`, `removeAllRules()` and `removeAllAssignments()` (qiangxue) diff --git a/framework/UPGRADE.md b/framework/UPGRADE.md index 9f39e5a..38a6ffe 100644 --- a/framework/UPGRADE.md +++ b/framework/UPGRADE.md @@ -117,7 +117,7 @@ new ones save the following code as `convert.php` that should be placed in the s foreach ($oldData['items'] as $name => $data) { if (isset($data['assignments'])) { foreach ($data['assignments'] as $userId => $assignmentData) { - $assignments[$userId] = $assignmentData['roleName']; + $assignments[$userId][] = $assignmentData['roleName']; } unset($data['assignments']); } @@ -172,3 +172,8 @@ new ones save the following code as `convert.php` that should be placed in the s }, $duration, $dependency); ``` + +* Due to significant changes to security you need to upgrade your code to use `\yii\base\Security` component instead of + helper. If you have any data encrypted it should be re-encrypted. In order to do so you can use old security helper [as + explained by @docsolver at github](https://github.com/yiisoft/yii2/issues/4461#issuecomment-50237807). + diff --git a/framework/assets/yii.activeForm.js b/framework/assets/yii.activeForm.js index 194fb7d..bab3fb5 100644 --- a/framework/assets/yii.activeForm.js +++ b/framework/assets/yii.activeForm.js @@ -44,6 +44,12 @@ // a callback that is called after an attribute is validated. The signature of the callback should be: // function ($form, attribute, messages) afterValidate: undefined, + // a pre-request callback function on AJAX-based validation. The signature of the callback should be: + // function ($form, jqXHR, textStatus) + ajaxBeforeSend: undefined, + // a function to be called when the request finishes on AJAX-based validation. The signature of the callback should be: + // function ($form, jqXHR, textStatus) + ajaxComplete: undefined, // the GET parameter name indicating an AJAX-based validation ajaxParam: 'ajax', // the type of data that you're expecting back from the server @@ -264,7 +270,20 @@ }); }, data.settings.validationDelay); }; - + + /** + * Returns an array prototype with a shortcut method for adding a new deferred. + * The context of the callback will be the deferred object so it can be resolved like ```this.resolve()``` + * @returns Array + */ + var deferredArray = function () { + var array = []; + array.add = function(callback) { + this.push(new $.Deferred(callback)); + }; + return array; + }; + /** * Performs validation. * @param $form jQuery the jquery representation of the form @@ -274,60 +293,78 @@ var validate = function ($form, successCallback, errorCallback) { var data = $form.data('yiiActiveForm'), needAjaxValidation = false, - messages = {}; + messages = {}, + deferreds = deferredArray(); $.each(data.attributes, function () { if (data.submitting || this.status === 2 || this.status === 3) { var msg = []; + messages[this.id] = msg; if (!data.settings.beforeValidate || data.settings.beforeValidate($form, this, msg)) { if (this.validate) { - this.validate(this, getValue($form, this), msg); + this.validate(this, getValue($form, this), msg, deferreds); } - if (msg.length) { - messages[this.id] = msg; - } else if (this.enableAjaxValidation) { + if (this.enableAjaxValidation) { needAjaxValidation = true; } } } }); - if (needAjaxValidation && (!data.submitting || $.isEmptyObject(messages))) { - // Perform ajax validation when at least one input needs it. - // If the validation is triggered by form submission, ajax validation - // should be done only when all inputs pass client validation - var $button = data.submitObject, - extData = '&' + data.settings.ajaxParam + '=' + $form.prop('id'); - if ($button && $button.length && $button.prop('name')) { - extData += '&' + $button.prop('name') + '=' + $button.prop('value'); + $.when.apply(this, deferreds).always(function() { + //Remove empty message arrays + for (var i in messages) { + if (0 === messages[i].length) { + delete messages[i]; + } } - $.ajax({ - url: data.settings.validationUrl, - type: $form.prop('method'), - data: $form.serialize() + extData, - dataType: data.settings.ajaxDataType, - success: function (msgs) { - if (msgs !== null && typeof msgs === 'object') { - $.each(data.attributes, function () { - if (!this.enableAjaxValidation) { - delete msgs[this.id]; - } - }); - successCallback($.extend({}, messages, msgs)); - } else { - successCallback(messages); - } - }, - error: errorCallback - }); - } else if (data.submitting) { - // delay callback so that the form can be submitted without problem - setTimeout(function () { + if (needAjaxValidation && (!data.submitting || $.isEmptyObject(messages))) { + // Perform ajax validation when at least one input needs it. + // If the validation is triggered by form submission, ajax validation + // should be done only when all inputs pass client validation + var $button = data.submitObject, + extData = '&' + data.settings.ajaxParam + '=' + $form.prop('id'); + if ($button && $button.length && $button.prop('name')) { + extData += '&' + $button.prop('name') + '=' + $button.prop('value'); + } + $.ajax({ + url: data.settings.validationUrl, + type: $form.prop('method'), + data: $form.serialize() + extData, + dataType: data.settings.ajaxDataType, + complete: function (jqXHR, textStatus) { + if (data.settings.ajaxComplete) { + data.settings.ajaxComplete($form, jqXHR, textStatus); + } + }, + beforeSend: function (jqXHR, textStatus) { + if (data.settings.ajaxBeforeSend) { + data.settings.ajaxBeforeSend($form, jqXHR, textStatus); + } + }, + success: function (msgs) { + if (msgs !== null && typeof msgs === 'object') { + $.each(data.attributes, function () { + if (!this.enableAjaxValidation) { + delete msgs[this.id]; + } + }); + successCallback($.extend({}, messages, msgs)); + } else { + successCallback(messages); + } + }, + error: errorCallback + }); + } else if (data.submitting) { + // delay callback so that the form can be submitted without problem + setTimeout(function () { + successCallback(messages); + }, 200); + } else { successCallback(messages); - }, 200); - } else { - successCallback(messages); - } + } + }); }; /** diff --git a/framework/assets/yii.validation.js b/framework/assets/yii.validation.js index 106f187..69c0742 100644 --- a/framework/assets/yii.validation.js +++ b/framework/assets/yii.validation.js @@ -69,6 +69,56 @@ yii.validation = (function ($) { } }, + file: function (value, messages, options, attribute) { + var files = $(attribute.input).get(0).files, + index, ext; + + if (options.message && !files) { + pub.addMessage(messages, options.message, value); + } + + if (!options.skipOnEmpty && files.length == 0) { + pub.addMessage(messages, options.uploadRequired, value); + } else if (files.length == 0) { + return; + } + + if (options.maxFiles && options.maxFiles < files.length) { + pub.addMessage(messages, options.tooMany); + } + + $.each(files, function (i, file) { + if (options.extensions && options.extensions.length > 0) { + index = file.name.lastIndexOf('.'); + + if (!~index) { + ext = ''; + } else { + ext = file.name.substr(index + 1, file.name.length).toLowerCase(); + } + + if (!~options.extensions.indexOf(ext)) { + messages.push(options.wrongExtension.replace(/\{file\}/g, file.name)); + } + } + + if (options.mimeTypes && options.mimeTypes.length > 0) { + if (!~options.mimeTypes.indexOf(file.type)) { + messages.push(options.wrongMimeType.replace(/\{file\}/g, file.name)); + } + } + + if (options.maxSize && options.maxSize < file.size) { + messages.push(options.tooBig.replace(/\{file\}/g, file.name)); + } + + if (options.maxSize && options.minSize > file.size) { + messages.push(options.tooSmall.replace(/\{file\}/g, file.name)); + } + + }); + }, + number: function (value, messages, options) { if (options.skipOnEmpty && pub.isEmpty(value)) { return; diff --git a/framework/base/Action.php b/framework/base/Action.php index e1ffdb1..7ac9892 100644 --- a/framework/base/Action.php +++ b/framework/base/Action.php @@ -45,6 +45,7 @@ class Action extends Component */ public $controller; + /** * Constructor. * diff --git a/framework/base/ActionEvent.php b/framework/base/ActionEvent.php index 06e2bd6..897572b 100644 --- a/framework/base/ActionEvent.php +++ b/framework/base/ActionEvent.php @@ -32,6 +32,7 @@ class ActionEvent extends Event */ public $isValid = true; + /** * Constructor. * @param Action $action the action associated with this action event. diff --git a/framework/base/Application.php b/framework/base/Application.php index 7975461..39156fa 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -12,8 +12,9 @@ use Yii; /** * Application is the base class for all application classes. * - * @property \yii\web\AssetManager $assetManager The asset manager component. This property is read-only. - * @property \yii\rbac\ManagerInterface $authManager The auth manager for this application. Null is returned + * @property \yii\web\AssetManager $assetManager The asset manager application component. This property is + * read-only. + * @property \yii\rbac\ManagerInterface $authManager The auth manager application component. Null is returned * if auth manager is not configured. This property is read-only. * @property string $basePath The root directory of the application. * @property \yii\caching\Cache $cache The cache application component. Null if the component is not enabled. @@ -21,23 +22,23 @@ use Yii; * @property \yii\db\Connection $db The database connection. This property is read-only. * @property \yii\web\ErrorHandler|\yii\console\ErrorHandler $errorHandler The error handler application * component. This property is read-only. - * @property \yii\base\Formatter|\yii\i18n\Formatter $formatter The formatter application component. This property is read-only. - * @property \yii\i18n\I18N $i18n The internationalization component. This property is read-only. - * @property \yii\log\Dispatcher $log The log dispatcher component. This property is read-only. - * @property \yii\mail\MailerInterface $mailer The mailer interface. This property is read-only. + * @property \yii\base\Formatter $formatter The formatter application component. This property is read-only. + * @property \yii\i18n\I18N $i18n The internationalization application component. This property is read-only. + * @property \yii\log\Dispatcher $log The log dispatcher application component. This property is read-only. + * @property \yii\mail\MailerInterface $mailer The mailer application component. This property is read-only. * @property \yii\web\Request|\yii\console\Request $request The request component. This property is read-only. * @property \yii\web\Response|\yii\console\Response $response The response component. This property is * read-only. * @property string $runtimePath The directory that stores runtime files. Defaults to the "runtime" * subdirectory under [[basePath]]. - * @property \yii\base\Security $security The security application component. + * @property \yii\base\Security $security The security application component. This property is read-only. * @property string $timeZone The time zone used by this application. * @property string $uniqueId The unique ID of the module. This property is read-only. * @property \yii\web\UrlManager $urlManager The URL manager for this application. This property is read-only. * @property string $vendorPath The directory that stores vendor files. Defaults to "vendor" directory under * [[basePath]]. - * @property View|\yii\web\View $view The view object that is used to render various view files. This property - * is read-only. + * @property View|\yii\web\View $view The view application component that is used to render various view + * files. This property is read-only. * * @author Qiang Xue * @since 2.0 @@ -316,6 +317,7 @@ abstract class Application extends Module /** * Registers the errorHandler component as a PHP error handler. + * @param array $config application config */ protected function registerErrorHandler(&$config) { @@ -476,7 +478,7 @@ abstract class Application extends Module /** * Returns the database connection component. - * @return \yii\db\Connection the database connection + * @return \yii\db\Connection the database connection. */ public function getDb() { @@ -485,7 +487,7 @@ abstract class Application extends Module /** * Returns the log dispatcher component. - * @return \yii\log\Dispatcher the log dispatcher component + * @return \yii\log\Dispatcher the log dispatcher application component. */ public function getLog() { @@ -521,7 +523,7 @@ abstract class Application extends Module /** * Returns the request component. - * @return \yii\web\Request|\yii\console\Request the request component + * @return \yii\web\Request|\yii\console\Request the request component. */ public function getRequest() { @@ -530,7 +532,7 @@ abstract class Application extends Module /** * Returns the response component. - * @return \yii\web\Response|\yii\console\Response the response component + * @return \yii\web\Response|\yii\console\Response the response component. */ public function getResponse() { @@ -539,7 +541,7 @@ abstract class Application extends Module /** * Returns the view object. - * @return View|\yii\web\View the view object that is used to render various view files. + * @return View|\yii\web\View the view application component that is used to render various view files. */ public function getView() { @@ -557,7 +559,7 @@ abstract class Application extends Module /** * Returns the internationalization (i18n) component - * @return \yii\i18n\I18N the internationalization component + * @return \yii\i18n\I18N the internationalization application component. */ public function getI18n() { @@ -566,7 +568,7 @@ abstract class Application extends Module /** * Returns the mailer component. - * @return \yii\mail\MailerInterface the mailer interface + * @return \yii\mail\MailerInterface the mailer application component. */ public function getMailer() { @@ -575,7 +577,7 @@ abstract class Application extends Module /** * Returns the auth manager for this application. - * @return \yii\rbac\ManagerInterface the auth manager for this application. + * @return \yii\rbac\ManagerInterface the auth manager application component. * Null is returned if auth manager is not configured. */ public function getAuthManager() @@ -585,7 +587,7 @@ abstract class Application extends Module /** * Returns the asset manager. - * @return \yii\web\AssetManager the asset manager component + * @return \yii\web\AssetManager the asset manager application component. */ public function getAssetManager() { @@ -594,7 +596,7 @@ abstract class Application extends Module /** * Returns the security component. - * @return \yii\base\Security security component + * @return \yii\base\Security the security application component. */ public function getSecurity() { @@ -602,8 +604,8 @@ abstract class Application extends Module } /** - * Returns the core application components. - * @see set + * Returns the configuration of core application components. + * @see set() */ public function coreComponents() { diff --git a/framework/base/Behavior.php b/framework/base/Behavior.php index 080084f..5750586 100644 --- a/framework/base/Behavior.php +++ b/framework/base/Behavior.php @@ -25,6 +25,7 @@ class Behavior extends Object */ public $owner; + /** * Declares event handlers for the [[owner]]'s events. * diff --git a/framework/base/Component.php b/framework/base/Component.php index 3480e50..72ea35c 100644 --- a/framework/base/Component.php +++ b/framework/base/Component.php @@ -102,10 +102,11 @@ class Component extends Object */ private $_events = []; /** - * @var Behavior[] the attached behaviors (behavior name => behavior) + * @var Behavior[]|null the attached behaviors (behavior name => behavior). This is `null` when not initialized. */ private $_behaviors; + /** * Returns the value of a component property. * This method will check in the following order and act accordingly: @@ -223,7 +224,6 @@ class Component extends Object } } } - return false; } @@ -244,7 +244,6 @@ class Component extends Object $setter = 'set' . $name; if (method_exists($this, $setter)) { $this->$setter(null); - return; } else { // behavior property @@ -252,7 +251,6 @@ class Component extends Object foreach ($this->_behaviors as $behavior) { if ($behavior->canSetProperty($name)) { $behavior->$name = null; - return; } } @@ -281,7 +279,6 @@ class Component extends Object return call_user_func_array([$object, $name], $params); } } - throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()"); } @@ -343,7 +340,6 @@ class Component extends Object } } } - return false; } @@ -374,7 +370,6 @@ class Component extends Object } } } - return false; } @@ -401,7 +396,6 @@ class Component extends Object } } } - return false; } @@ -444,7 +438,6 @@ class Component extends Object public function hasEventHandlers($name) { $this->ensureBehaviors(); - return !empty($this->_events[$name]) || Event::hasHandlers($this, $name); } @@ -517,7 +510,6 @@ class Component extends Object if ($removed) { $this->_events[$name] = array_values($this->_events[$name]); } - return $removed; } } @@ -562,7 +554,6 @@ class Component extends Object public function getBehavior($name) { $this->ensureBehaviors(); - return isset($this->_behaviors[$name]) ? $this->_behaviors[$name] : null; } @@ -573,7 +564,6 @@ class Component extends Object public function getBehaviors() { $this->ensureBehaviors(); - return $this->_behaviors; } @@ -595,7 +585,6 @@ class Component extends Object public function attachBehavior($name, $behavior) { $this->ensureBehaviors(); - return $this->attachBehaviorInternal($name, $behavior); } @@ -627,7 +616,6 @@ class Component extends Object $behavior = $this->_behaviors[$name]; unset($this->_behaviors[$name]); $behavior->detach(); - return $behavior; } else { return null; @@ -681,7 +669,6 @@ class Component extends Object $behavior->attach($this); $this->_behaviors[$name] = $behavior; } - return $behavior; } } diff --git a/framework/base/Controller.php b/framework/base/Controller.php index f2e4804..072984b 100644 --- a/framework/base/Controller.php +++ b/framework/base/Controller.php @@ -36,6 +36,7 @@ class Controller extends Component implements ViewContextInterface * @event ActionEvent an event raised right after executing a controller action. */ const EVENT_AFTER_ACTION = 'afterAction'; + /** * @var string the ID of this controller. */ @@ -61,11 +62,13 @@ class Controller extends Component implements ViewContextInterface * by [[run()]] when it is called by [[Application]] to run an action. */ public $action; + /** * @var View the view object that can be used to render views or view files. */ private $_view; + /** * @param string $id the ID of this controller. * @param Module $module the module that this controller belongs to. diff --git a/framework/base/DynamicModel.php b/framework/base/DynamicModel.php index 366509f..5f91209 100644 --- a/framework/base/DynamicModel.php +++ b/framework/base/DynamicModel.php @@ -57,6 +57,7 @@ class DynamicModel extends Model { private $_attributes = []; + /** * Constructors. * @param array $attributes the dynamic attributes (name-value pairs, or names) being defined diff --git a/framework/base/Event.php b/framework/base/Event.php index 334ba8c..e6a602c 100644 --- a/framework/base/Event.php +++ b/framework/base/Event.php @@ -50,6 +50,7 @@ class Event extends Object private static $_events = []; + /** * Attaches an event handler to a class-level event. * diff --git a/framework/base/Formatter.php b/framework/base/Formatter.php index 980ce99..0ed2cf1 100644 --- a/framework/base/Formatter.php +++ b/framework/base/Formatter.php @@ -77,6 +77,7 @@ class Formatter extends Component 'decimalSeparator' => null, ]; + /** * Initializes the component. */ @@ -444,10 +445,13 @@ class Formatter extends Component * @param integer $value value in bytes to be formatted * @param boolean $verbose if full names should be used (e.g. bytes, kilobytes, ...). * Defaults to false meaning that short names will be used (e.g. B, KB, ...). + * @param boolean $binaryPrefix if binary prefixes should be used for base 1024 + * Defaults to true meaning that binary prefixes are used (e.g. kibibyte/KiB, mebibyte/MiB, ...). + * @link http://en.wikipedia.org/wiki/Binary_prefix * @return string the formatted result * @see sizeFormat */ - public function asSize($value, $verbose = false) + public function asSize($value, $verbose = false, $binaryPrefix = true) { $position = 0; @@ -458,12 +462,29 @@ class Formatter extends Component $value = $value / $this->sizeFormat['base']; $position++; - } while ($position < 6); + } while ($position < 5); $value = round($value, $this->sizeFormat['decimals']); $formattedValue = isset($this->sizeFormat['decimalSeparator']) ? str_replace('.', $this->sizeFormat['decimalSeparator'], $value) : $value; $params = ['n' => $formattedValue]; + if ($binaryPrefix && $this->sizeFormat['base'] === 1024) { + switch ($position) { + case 0: + return $verbose ? Yii::t('yii', '{n, plural, =1{# byte} other{# bytes}}', $params) : Yii::t('yii', '{n} B', $params); + case 1: + return $verbose ? Yii::t('yii', '{n, plural, =1{# kibibyte} other{# kibibytes}}', $params) : Yii::t('yii', '{n} KiB', $params); + case 2: + return $verbose ? Yii::t('yii', '{n, plural, =1{# mebibyte} other{# mebibytes}}', $params) : Yii::t('yii', '{n} MiB', $params); + case 3: + return $verbose ? Yii::t('yii', '{n, plural, =1{# gibibyte} other{# gibibytes}}', $params) : Yii::t('yii', '{n} GiB', $params); + case 4: + return $verbose ? Yii::t('yii', '{n, plural, =1{# tebibyte} other{# tebibytes}}', $params) : Yii::t('yii', '{n} TiB', $params); + default: + return $verbose ? Yii::t('yii', '{n, plural, =1{# pebibyte} other{# pebibytes}}', $params) : Yii::t('yii', '{n} PiB', $params); + } + } + switch ($position) { case 0: return $verbose ? Yii::t('yii', '{n, plural, =1{# byte} other{# bytes}}', $params) : Yii::t('yii', '{n} B', $params); @@ -479,7 +500,7 @@ class Formatter extends Component return $verbose ? Yii::t('yii', '{n, plural, =1{# petabyte} other{# petabytes}}', $params) : Yii::t('yii', '{n} PB', $params); } } - + /** * Formats the value as the time interval between a date and now in human readable form. * diff --git a/framework/base/InlineAction.php b/framework/base/InlineAction.php index 2a0e6b6..6472793 100644 --- a/framework/base/InlineAction.php +++ b/framework/base/InlineAction.php @@ -25,6 +25,7 @@ class InlineAction extends Action */ public $actionMethod; + /** * @param string $id the ID of this action * @param Controller $controller the controller that owns this action diff --git a/framework/base/Model.php b/framework/base/Model.php index 2c03821..aaf3d5a 100644 --- a/framework/base/Model.php +++ b/framework/base/Model.php @@ -83,6 +83,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab */ private $_scenario = self::SCENARIO_DEFAULT; + /** * Returns the validation rules for attributes. * @@ -441,7 +442,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab public function isAttributeRequired($attribute) { foreach ($this->getActiveValidators($attribute) as $validator) { - if ($validator instanceof RequiredValidator) { + if ($validator instanceof RequiredValidator && $validator->when === null) { return true; } } diff --git a/framework/base/Module.php b/framework/base/Module.php index 53348d5..cef423e 100644 --- a/framework/base/Module.php +++ b/framework/base/Module.php @@ -30,7 +30,7 @@ use yii\di\ServiceLocator; * @property string $layoutPath The root directory of layout files. Defaults to "[[viewPath]]/layouts". * @property array $modules The modules (indexed by their IDs). * @property string $uniqueId The unique ID of the module. This property is read-only. - * @property string $viewPath The root directory of view files. Defaults to "[[basePath]]/view". + * @property string $viewPath The root directory of view files. Defaults to "[[basePath]]/views". * * @author Qiang Xue * @since 2.0 @@ -106,6 +106,7 @@ class Module extends ServiceLocator * [[Controller::defaultAction]]. */ public $defaultRoute = 'default'; + /** * @var string the root directory of the module. */ @@ -127,6 +128,7 @@ class Module extends ServiceLocator */ private static $_instances = []; + /** * Constructor. * @param string $id the ID of this module @@ -241,7 +243,7 @@ class Module extends ServiceLocator /** * Returns the directory that contains the view files for this module. - * @return string the root directory of view files. Defaults to "[[basePath]]/view". + * @return string the root directory of view files. Defaults to "[[basePath]]/views". */ public function getViewPath() { @@ -548,10 +550,6 @@ class Module extends ServiceLocator */ public function createControllerByID($id) { - if (!preg_match('%^[a-z0-9\\-_/]+$%', $id)) { - return null; - } - $pos = strrpos($id, '/'); if ($pos === false) { $prefix = ''; @@ -561,6 +559,13 @@ class Module extends ServiceLocator $className = substr($id, $pos + 1); } + if (!preg_match('%^[a-z][a-z0-9\\-_]*$%', $className)) { + return null; + } + if ($prefix !== '' && !preg_match('%^[a-z0-9_/]+$%i', $prefix)) { + return null; + } + $className = str_replace(' ', '', ucwords(str_replace('-', ' ', $className))) . 'Controller'; $className = ltrim($this->controllerNamespace . '\\' . str_replace('/', '\\', $prefix) . $className, '\\'); if (strpos($className, '-') !== false || !class_exists($className)) { diff --git a/framework/base/Request.php b/framework/base/Request.php index 4166e86..3fdfd4d 100644 --- a/framework/base/Request.php +++ b/framework/base/Request.php @@ -23,6 +23,7 @@ abstract class Request extends Component private $_scriptFile; private $_isConsoleRequest; + /** * Resolves the current request into a route and the associated parameters. * @return array the first element is the route, and the second is the associated parameters. diff --git a/framework/base/Response.php b/framework/base/Response.php index d258f0f..043ffe9 100644 --- a/framework/base/Response.php +++ b/framework/base/Response.php @@ -21,6 +21,7 @@ class Response extends Component */ public $exitStatus = 0; + /** * Sends the response to client. */ diff --git a/framework/base/Security.php b/framework/base/Security.php index 29d4ea1..8d079c4 100644 --- a/framework/base/Security.php +++ b/framework/base/Security.php @@ -15,21 +15,11 @@ use Yii; * * In particular, Security supports the following features: * - * - Encryption/decryption: [[encrypt()]] and [[decrypt()]] + * - Encryption/decryption: [[encryptByKey()]], [[decryptByKey()]], [[encryptByPassword()]] and [[decryptByPassword()]] + * - Key derivation using standard algorithms: [[pbkdf2()]] and [[hkdf()]] * - Data tampering prevention: [[hashData()]] and [[validateData()]] * - Password validation: [[generatePasswordHash()]] and [[validatePassword()]] * - * Additionally, Security provides [[getSecretKey()]] to support generating - * named secret keys. These secret keys, once generated, will be stored in a file - * and made available in future requests. - * - * This component provides several configuration parameters, which allow tuning your own balance - * between high security and high performance. - * - * > Tip: you may add several `Security` components with different configurations to your application, - * this allows usage of different encryption strategies for different use cases or migrate encrypted data - * from outdated strategy to the new one. - * * > Note: this class requires 'mcrypt' PHP extension. For the highest security level PHP version >= 5.5.0 is recommended. * * @author Qiang Xue @@ -40,114 +30,221 @@ use Yii; class Security extends Component { /** - * @var integer crypt block size in bytes. - * For AES-128, AES-192, block size is 128-bit (16 bytes). - * For AES-256, block size is 256-bit (32 bytes). - * Recommended value: 32 + * Cipher algorithm for mcrypt module. + * AES has 128-bit block size and three key sizes: 128, 192 and 256 bits. + * mcrypt offers the Rijndael cipher with block sizes of 128, 192 and 256 + * bits but only the 128-bit Rijndael is standardized in AES. + * So to use AES in mycrypt, specify `'rijndael-128'` cipher and mcrypt + * chooses the appropriate AES based on the length of the supplied key. */ - public $cryptBlockSize = 32; + const MCRYPT_CIPHER = 'rijndael-128'; /** - * @var integer crypt key size in bytes. - * For AES-192, key size is 192-bit (24 bytes). - * For AES-256, key size is 256-bit (32 bytes). - * Recommended value: 32 + * Block cipher operation mode for mcrypt module. */ - public $cryptKeySize = 32; + const MCRYPT_MODE = 'cbc'; /** - * @var string derivation hash algorithm name. - * Recommended value: 'sha256' + * Size in bytes of encryption key, message authentication key and KDF salt. */ - public $derivationHash = 'sha256'; + const KEY_SIZE = 16; /** - * @var integer derivation iterations count. - * Recommended value: 1000000 + * Hash algorithm for key derivation. */ - public $derivationIterations = 1000000; + const KDF_HASH = 'sha256'; /** - * @var string strategy, which should be used to derive a key for encryption. - * Available strategies: - * - 'pbkdf2' - PBKDF2 key derivation. This option is recommended, but it requires PHP version >= 5.5.0 - * - 'hmac' - HMAC hash key derivation. + * Hash algorithm for message authentication. + */ + const MAC_HASH = 'sha256'; + /** + * HKDF info value for derivation of message authentication key. + */ + const AUTH_KEY_INFO = 'AuthorizationKey'; + + /** + * @var integer derivation iterations count. + * Set as high as possible to hinder dictionary password attacks. */ - public $deriveKeyStrategy = 'hmac'; + public $derivationIterations = 100000; /** * @var string strategy, which should be used to generate password hash. * Available strategies: - * - 'password_hash' - use of PHP `password_hash()` function with PASSWORD_DEFAULT algorithm. This option is recommended, - * but it requires PHP version >= 5.5.0 + * - 'password_hash' - use of PHP `password_hash()` function with PASSWORD_DEFAULT algorithm. + * This option is recommended, but it requires PHP version >= 5.5.0 * - 'crypt' - use PHP `crypt()` function. */ public $passwordHashStrategy = 'crypt'; + + private $_cryptModule; + + /** - * @var boolean whether to generate unique salt while deriving encryption key. - * If enabled (recommended) this option increases encrypted text length, but provide more security. - * If disabled this option reduces encrypted text length, but also reduces security. + * Encrypts data using a password. + * Derives keys for encryption and authentication from the password using PBKDF2 and a random salt, + * which is deliberately slow to protect against dictionary attacks. Use [[encryptByKey()]] to + * encrypt fast using a cryptographic key rather than a password. Key derivation time is + * determined by [[$derivationIterations]], which should be set as high as possible. + * The encrypted data includes a keyed message authentication code (MAC) so there is no need + * to hash input or output data. + * > Note: Avoid encrypting with passwords wherever possible. Nothing can protect against + * poor-quality or compromosed passwords. + * @param string $data the data to encrypt + * @param string $password the password to use for encryption + * @return string the encrypted data + * @see decryptByPassword() + * @see encryptByKey() */ - public $useDeriveKeyUniqueSalt = true; + public function encryptByPassword($data, $password) + { + return $this->encrypt($data, true, $password, null); + } + /** - * @var string the path or alias of a file that stores the secret keys automatically generated by [[getSecretKey()]]. - * The file must be writable by Web server process. It contains a JSON hash of key names and key values. + * Encrypts data using a cyptograhic key. + * Derives keys for encryption and authentication from the input key using HKDF and a random salt, + * which is very fast relative to [[encryptByPassword()]]. The input key must be properly + * random -- use [[generateRandomKey()]] to generate keys. + * The encrypted data includes a keyed message authentication code (MAC) so there is no need + * to hash input or output data. + * @param string $data the data to encrypt + * @param string $inputKey the input to use for encryption and authentication + * @param string $info optional context and application specific information, see [[hkdf()]] + * @return string the encrypted data + * @see decryptByPassword() + * @see encryptByKey() */ - public $secretKeyFile = '@runtime/keys.json'; + public function encryptByKey($data, $inputKey, $info = null) + { + return $this->encrypt($data, false, $inputKey, $info); + } + + /** + * Verifies and decrypts data encrypted with [[encryptByPassword()]]. + * @param string $data the encrypted data to decrypt + * @param string $password the password to use for decryption + * @return bool|string the decrypted data or false on authentication failure + * @see encryptByPassword() + */ + public function decryptByPassword($data, $password) + { + return $this->decrypt($data, true, $password, null); + } + + /** + * Verifies and decrypts data encrypted with [[encryptByPassword()]]. + * @param string $data the encrypted data to decrypt + * @param string $inputKey the input to use for encryption and authentication + * @param string $info optional context and application specific information, see [[hkdf()]] + * @return bool|string the decrypted data or false on authentication failure + * @see encryptByKey() + */ + public function decryptByKey($data, $inputKey, $info = null) + { + return $this->decrypt($data, false, $inputKey, $info); + } + + /** + * Initializes the mcrypt module. + * @return resource the mcrypt module handle. + * @throws InvalidConfigException if mcrypt extension is not installed + * @throws Exception if mcrypt initialization fails + */ + protected function getCryptModule() + { + if ($this->_cryptModule === null) { + if (!extension_loaded('mcrypt')) { + throw new InvalidConfigException('The mcrypt PHP extension is not installed.'); + } + + $this->_cryptModule = @mcrypt_module_open(self::MCRYPT_CIPHER, '', self::MCRYPT_MODE, ''); + if ($this->_cryptModule === false) { + $this->_cryptModule = null; + throw new Exception('Failed to initialize the mcrypt module.'); + } + } + + return $this->_cryptModule; + } + + public function __destruct() + { + if ($this->_cryptModule !== null) { + mcrypt_module_close($this->_cryptModule); + $this->_cryptModule = null; + } + } /** * Encrypts data. - * @param string $data data to be encrypted. - * @param string $password the encryption password + * @param string $data data to be encrypted + * @param bool $passwordBased set true to use password-based key derivation + * @param string $secret the encryption password or key + * @param string $info context/application specific information, e.g. a user ID + * See [RFC 5869 Section 3.2](https://tools.ietf.org/html/rfc5869#section-3.2) for more details. * @return string the encrypted data * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized * @see decrypt() */ - public function encrypt($data, $password) + protected function encrypt($data, $passwordBased, $secret, $info) { - $module = $this->openCryptModule(); + $module = $this->getCryptModule(); + + $keySalt = $this->generateRandomKey(self::KEY_SIZE); + if ($passwordBased) { + $key = $this->pbkdf2(self::KDF_HASH, $secret, $keySalt, $this->derivationIterations, self::KEY_SIZE); + } else { + $key = $this->hkdf(self::KDF_HASH, $secret, $keySalt, $info, self::KEY_SIZE); + } + $data = $this->addPadding($data); $ivSize = mcrypt_enc_get_iv_size($module); $iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM); - if ($this->useDeriveKeyUniqueSalt) { - $keySalt = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM); - $encrypted = $keySalt; - } else { - $keySalt = $iv; - $encrypted = ''; - } - $key = $this->deriveKey($password, $keySalt); mcrypt_generic_init($module, $key, $iv); - $encrypted .= $iv . mcrypt_generic($module, $data); + $encrypted = mcrypt_generic($module, $data); mcrypt_generic_deinit($module); - mcrypt_module_close($module); - return $encrypted; + $authKey = $this->hkdf(self::KDF_HASH, $key, null, self::AUTH_KEY_INFO, self::KEY_SIZE); + $hashed = $this->hashData($iv . $encrypted, $authKey); + + /* + * Output: [keySalt][MAC][IV][ciphertext] + * - keySalt is KEY_SIZE bytes long + * - MAC: message authentication code, length same as the output of MAC_HASH + * - IV: initialization vector, length set by CRYPT_CIPHER and CRYPT_MODE, mcrypt_enc_get_iv_size() + */ + return $keySalt . $hashed; } /** - * Decrypts data - * @param string $data data to be decrypted. - * @param string $password the decryption password - * @return string the decrypted data - * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized + * Decrypts data. + * @param string $data encrypted data to be decrypted. + * @param bool $passwordBased set true to use password-based key derivation + * @param string $secret the decryption password or key + * @param string $info context/application specific information, @see encrypt() + * @return bool|string the decrypted data or false on authentication failure * @see encrypt() */ - public function decrypt($data, $password) + protected function decrypt($data, $passwordBased, $secret, $info) { - if ($data === null) { - return null; + $keySalt = StringHelper::byteSubstr($data, 0, self::KEY_SIZE); + if ($passwordBased) { + $key = $this->pbkdf2(self::KDF_HASH, $secret, $keySalt, $this->derivationIterations, self::KEY_SIZE); + } else { + $key = $this->hkdf(self::KDF_HASH, $secret, $keySalt, $info, self::KEY_SIZE); } - $module = $this->openCryptModule(); + + $authKey = $this->hkdf(self::KDF_HASH, $key, null, self::AUTH_KEY_INFO, self::KEY_SIZE); + $data = $this->validateData(StringHelper::byteSubstr($data, self::KEY_SIZE, null), $authKey); + if ($data === false) { + return false; + } + + $module = $this->getCryptModule(); $ivSize = mcrypt_enc_get_iv_size($module); $iv = StringHelper::byteSubstr($data, 0, $ivSize); - $keySalt = $iv; - $encrypted = StringHelper::byteSubstr($data, $ivSize, StringHelper::byteLength($data)); - if ($this->useDeriveKeyUniqueSalt) { - $iv = StringHelper::byteSubstr($encrypted, 0, $ivSize); - $encrypted = StringHelper::byteSubstr($encrypted, $ivSize, StringHelper::byteLength($encrypted)); - } - $key = $this->deriveKey($password, $keySalt); + $encrypted = StringHelper::byteSubstr($data, $ivSize, null); mcrypt_generic_init($module, $key, $iv); $decrypted = mdecrypt_generic($module, $encrypted); mcrypt_generic_deinit($module); - mcrypt_module_close($module); return $this->stripPadding($decrypted); } @@ -159,7 +256,9 @@ class Security extends Component */ protected function addPadding($data) { - $pad = $this->cryptBlockSize - (StringHelper::byteLength($data) % $this->cryptBlockSize); + $module = $this->getCryptModule(); + $blockSize = mcrypt_enc_get_block_size($module); + $pad = $blockSize - (StringHelper::byteLength($data) % $blockSize); return $data . str_repeat(chr($pad), $pad); } @@ -182,70 +281,137 @@ class Security extends Component } /** - * Derives a key from the given password (PBKDF2). - * @param string $password the source password + * Derives a key from the given input key using the standard HKDF algorithm. + * Implements HKDF spcified in [RFC 5869](https://tools.ietf.org/html/rfc5869). + * Recommend use one of the SHA-2 hash algorithms: sha224, sha256, sha384 or sha512. + * @param string $algo a hash algorithm supported by `hash_hmac()`, e.g. 'SHA-256' + * @param string $inputKey the source key * @param string $salt the random salt - * @throws InvalidConfigException if unsupported derive key strategy is configured. + * @param string $info optional info to bind the derived key material to application- + * and context-specific information, e.g. a user ID or API version, see + * [RFC 5869](https://tools.ietf.org/html/rfc5869) + * @param int $length length of the output key in bytes. If 0, the output key is + * the length of the hash algorithm output. + * @throws InvalidParamException * @return string the derived key */ - protected function deriveKey($password, $salt) + public function hkdf($algo, $inputKey, $salt = null, $info = null, $length = 0) { - switch ($this->deriveKeyStrategy) { - case 'pbkdf2': - return $this->deriveKeyPbkdf2($password, $salt); - case 'hmac': - return $this->deriveKeyHmac($password, $salt); - default: - throw new InvalidConfigException("Unknown derive key strategy '{$this->deriveKeyStrategy}'"); + $test = @hash_hmac($algo, '', '', true); + if (!$test) { + throw new InvalidParamException('Failed to generate HMAC with hash algorithm: ' . $algo); + } + $hashLength = StringHelper::byteLength($test); + if (is_string($length) && preg_match('{^\d{1,16}$}', $length)) { + $length = (int) $length; + } + if (!is_integer($length) || $length < 0 || $length > 255 * $hashLength) { + throw new InvalidParamException('Invalid length'); + } + $blocks = $length !== 0 ? ceil($length / $hashLength) : 1; + + if ($salt === null) { + $salt = str_repeat("\0", $hashLength); + } + $prKey = hash_hmac($algo, $inputKey, $salt, true); + + $hmac = ''; + $outputKey = ''; + for ($i = 1; $i <= $blocks; $i++) { + $hmac = hash_hmac($algo, $hmac . $info . chr($i), $prKey, true); + $outputKey .= $hmac; + } + + if ($length !== 0) { + $outputKey = StringHelper::byteSubstr($outputKey, 0, $length); } + return $outputKey; } /** - * Derives a key from the given password using PBKDF2. + * Derives a key from the given password using the standard PBKDF2 algorithm. + * Implements HKDF2 specified in [RFC 2898](http://tools.ietf.org/html/rfc2898#section-5.2) + * Recommend use one of the SHA-2 hash algorithms: sha224, sha256, sha384 or sha512. + * @param string $algo a hash algorithm supported by `hash_hmac()`, e.g. 'SHA-256' * @param string $password the source password * @param string $salt the random salt - * @throws InvalidConfigException if environment does not allows PBKDF2. + * @param int $iterations the number of iterations of the hash algorithm. Set as high as + * possible to hinder dictionary password attacks. + * @param int $length length of the output key in bytes. If 0, the output key is + * the length of the hash algorithm output. + * @throws InvalidParamException * @return string the derived key */ - protected function deriveKeyPbkdf2($password, $salt) + public function pbkdf2($algo, $password, $salt, $iterations, $length = 0) { if (function_exists('hash_pbkdf2')) { - return hash_pbkdf2($this->derivationHash, $password, $salt, $this->derivationIterations, $this->cryptKeySize, true); - } else { - throw new InvalidConfigException('Security::$deriveKeyStrategy is set to "pbkdf2", which requires PHP >= 5.5.0. Either upgrade your run-time environment or use another strategy.'); + $outputKey = hash_pbkdf2($algo, $password, $salt, $iterations, $length, true); + if ($outputKey === false) { + throw new InvalidParamException('Invalid parameters to hash_pbkdf2()'); + } + return $outputKey; } - } - /** - * Derives a key from the given password using HMAC. - * @param string $password the source password - * @param string $salt the random salt - * @return string the derived key - */ - protected function deriveKeyHmac($password, $salt) - { - $hmac = hash_hmac($this->derivationHash, $salt . pack('N', 1), $password, true); - $xorsum = $hmac; - for ($i = 1; $i < $this->derivationIterations; $i++) { - $hmac = hash_hmac($this->derivationHash, $hmac, $password, true); - $xorsum ^= $hmac; + // todo: is there a nice way to reduce the code repetition in hkdf() and pbkdf2()? + $test = @hash_hmac($algo, '', '', true); + if (!$test) { + throw new InvalidParamException('Failed to generate HMAC with hash algorithm: ' . $algo); + } + if (is_string($iterations) && preg_match('{^\d{1,16}$}', $iterations)) { + $iterations = (int) $iterations; + } + if (!is_integer($iterations) || $iterations < 1) { + throw new InvalidParamException('Invalid iterations'); + } + if (is_string($length) && preg_match('{^\d{1,16}$}', $length)) { + $length = (int) $length; + } + if (!is_integer($length) || $length < 0) { + throw new InvalidParamException('Invalid length'); } - return substr($xorsum, 0, $this->cryptKeySize); + $hashLength = StringHelper::byteLength($test); + $blocks = $length !== 0 ? ceil($length / $hashLength) : 1; + + $outputKey = ''; + for ($j = 1; $j <= $blocks; $j++) { + $hmac = hash_hmac($algo, $salt . pack('N', $j), $password, true); + $xorsum = $hmac; + for ($i = 1; $i < $iterations; $i++) { + $hmac = hash_hmac($algo, $hmac, $password, true); + $xorsum ^= $hmac; + } + $outputKey .= $xorsum; + } + + if ($length !== 0) { + $outputKey = StringHelper::byteSubstr($outputKey, 0, $length); + } + return $outputKey; } /** * Prefixes data with a keyed hash value so that it can later be detected if it is tampered. + * There is no need to hash inputs or outputs of [[encryptByKey()]] or [[encryptByPassword()]] + * as those methods perform the task. * @param string $data the data to be protected - * @param string $key the secret key to be used for generating hash - * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" - * function to see the supported hashing algorithms on your system. + * @param string $key the secret key to be used for generating hash. Should be a secure + * cryptographic key. + * @param boolean $rawHash whether the generated hash value is in raw binary format. If false, lowercase + * hex digits will be generated. + * @throws InvalidConfigException * @return string the data prefixed with the keyed hash * @see validateData() - * @see getSecretKey() + * @see generateRandomKey() + * @see hkdf() + * @see pbkdf2() */ - public function hashData($data, $key, $algorithm = 'sha256') + public function hashData($data, $key, $rawHash = false) { - return hash_hmac($algorithm, $data, $key) . $data; + $hash = hash_hmac(self::MAC_HASH, $data, $key, $rawHash); + if (!$hash) { + throw new InvalidConfigException('Failed to generate HMAC with hash algorithm: ' . self::MAC_HASH); + } + return $hash . $data; } /** @@ -253,68 +419,46 @@ class Security extends Component * @param string $data the data to be validated. The data must be previously * generated by [[hashData()]]. * @param string $key the secret key that was previously used to generate the hash for the data in [[hashData()]]. - * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" * function to see the supported hashing algorithms on your system. This must be the same * as the value passed to [[hashData()]] when generating the hash for the data. + * @param boolean $rawHash this should take the same value as when you generate the data using [[hashData()]]. + * It indicates whether the hash value in the data is in binary format. If false, it means the hash value consists + * of lowercase hex digits only. + * hex digits will be generated. + * @throws InvalidConfigException * @return string the real data with the hash stripped off. False if the data is tampered. * @see hashData() */ - public function validateData($data, $key, $algorithm = 'sha256') + public function validateData($data, $key, $rawHash = false) { - $hashSize = StringHelper::byteLength(hash_hmac($algorithm, 'test', $key)); - $n = StringHelper::byteLength($data); - if ($n >= $hashSize) { - $hash = StringHelper::byteSubstr($data, 0, $hashSize); - $pureData = StringHelper::byteSubstr($data, $hashSize, $n - $hashSize); + $test = @hash_hmac(self::MAC_HASH, '', '', $rawHash); + if (!$test) { + throw new InvalidConfigException('Failed to generate HMAC with hash algorithm: ' . self::MAC_HASH); + } + $hashLength = StringHelper::byteLength($test); + if (StringHelper::byteLength($data) >= $hashLength) { + $hash = StringHelper::byteSubstr($data, 0, $hashLength); + $pureData = StringHelper::byteSubstr($data, $hashLength, null); - $calculatedHash = hash_hmac($algorithm, $pureData, $key); + $calculatedHash = hash_hmac(self::MAC_HASH, $pureData, $key, $rawHash); if ($this->compareString($hash, $calculatedHash)) { return $pureData; - } else { - return false; } - } else { - return false; } - } - - private $_keys; - - /** - * Returns a secret key associated with the specified name. - * If the secret key does not exist, it will be automatically generated and saved in [[secretKeyFile]]. - * @param string $name the name that is associated with the secret key - * @param integer $length the length of the key that should be generated if not exists - * @param boolean $regenerate whether to regenerate a secret if it already exists - * @return string the secret key associated with the specified name - */ - public function getSecretKey($name, $length = 32, $regenerate = false) - { - $keyFile = Yii::getAlias($this->secretKeyFile); - - if ($this->_keys === null) { - $this->_keys = is_file($keyFile) ? json_decode(file_get_contents($keyFile), true) : []; - } - - if (!isset($this->_keys[$name]) || $regenerate) { - $this->_keys[$name] = $this->generateRandomKey($length); - file_put_contents($keyFile, json_encode($this->_keys)); - } - - return $this->_keys[$name]; + return false; } /** * Generates specified number of random bytes. * Note that output may not be ASCII. - * @see generateRandomKey() if you need a string. + * @see generateRandomString() if you need a string. * * @param integer $length the number of bytes to generate * @throws Exception on failure. * @return string the generated random bytes */ - public function generateRandomBytes($length = 32) + public function generateRandomKey($length = 32) { if (!extension_loaded('mcrypt')) { throw new InvalidConfigException('The mcrypt PHP extension is not installed.'); @@ -328,43 +472,24 @@ class Security extends Component /** * Generates a random string of specified length. - * The string generated matches [A-Za-z0-9_.-]+ + * The string generated matches [A-Za-z0-9_-]+ and is transparent to URL-encoding. * * @param integer $length the length of the key in characters * @throws Exception Exception on failure. * @return string the generated random key */ - public function generateRandomKey($length = 32) - { - $bytes = $this->generateRandomBytes($length); - return strtr(StringHelper::byteSubstr(base64_encode($bytes), 0, $length), '+/=', '_-.'); - } - - /** - * Opens the mcrypt module. - * @return resource the mcrypt module handle. - * @throws InvalidConfigException if mcrypt extension is not installed - * @throws Exception if mcrypt initialization fails - */ - protected function openCryptModule() + public function generateRandomString($length = 32) { - if (!extension_loaded('mcrypt')) { - throw new InvalidConfigException('The mcrypt PHP extension is not installed.'); - } - // AES version depending on crypt block size - $algorithmName = 'rijndael-' . ($this->cryptBlockSize * 8); - $module = @mcrypt_module_open($algorithmName, '', 'cbc', ''); - if ($module === false) { - throw new Exception('Failed to initialize the mcrypt module.'); - } - - return $module; + $bytes = $this->generateRandomKey($length); + // '=' character(s) returned by base64_encode() are always discarded because + // they are guaranteed to be after position $length in the base64_encode() output. + return strtr(substr(base64_encode($bytes), 0, $length), '+/', '_-'); } /** * Generates a secure hash from a password and a random salt. * - * The generated hash can be stored in database (e.g. `CHAR(64) CHARACTER SET latin1` on MySQL). + * The generated hash can be stored in database. * Later when a password needs to be validated, the hash can be fetched and passed * to [[validatePassword()]]. For example, * @@ -387,11 +512,12 @@ class Security extends Component * the longer it takes to generate the hash and to verify a password against it. Higher cost * therefore slows down a brute-force attack. For best protection against brute for attacks, * set it to the highest value that is tolerable on production servers. The time taken to - * compute the hash doubles for every increment by one of $cost. So, for example, if the - * hash takes 1 second to compute when $cost is 14 then then the compute time varies as - * 2^($cost - 14) seconds. + * compute the hash doubles for every increment by one of $cost. * @throws Exception on bad password parameter or cost parameter - * @return string The password hash string, ASCII and not longer than 64 characters. + * @throws InvalidConfigException + * @return string The password hash string. When [[passwordHashStrategy]] is set to 'crypt', + * the output is alwaus 60 ASCII characters, when set to 'password_hash' the output length + * might increase in future versions of PHP (http://php.net/manual/en/function.password-hash.php) * @see validatePassword() */ public function generatePasswordHash($password, $cost = 13) @@ -401,12 +527,13 @@ class Security extends Component if (!function_exists('password_hash')) { throw new InvalidConfigException('Password hash key strategy "password_hash" requires PHP >= 5.5.0, either upgrade your environment or use another strategy.'); } + /** @noinspection PhpUndefinedConstantInspection */ return password_hash($password, PASSWORD_DEFAULT, ['cost' => $cost]); case 'crypt': $salt = $this->generateSalt($cost); $hash = crypt($password, $salt); - - if (!is_string($hash) || strlen($hash) < 32) { + // strlen() is safe since crypt() returns only ascii + if (!is_string($hash) || strlen($hash) !== 60) { throw new Exception('Unknown error occurred while generating hash.'); } return $hash; @@ -443,7 +570,7 @@ class Security extends Component case 'crypt': $test = crypt($password, $hash); $n = strlen($test); - if ($n < 32 || $n !== strlen($hash)) { + if ($n !== 60) { return false; } return $this->compareString($test, $hash); @@ -471,14 +598,9 @@ class Security extends Component throw new InvalidParamException('Cost must be between 4 and 31.'); } - // Get 20 * 8bits of random entropy - $rand = $this->generateRandomBytes(20); - - // Add the microtime for a little more entropy. - $rand .= microtime(true); - // Mix the bits cryptographically into a 20-byte binary string. - $rand = sha1($rand, true); - // Form the prefix that specifies Blowfish algorithm and cost parameter. + // Get a 20-byte random string + $rand = $this->generateRandomKey(20); + // Form the prefix that specifies Blowfish (bcrypt) algorithm and cost parameter. $salt = sprintf("$2y$%02d$", $cost); // Append the random salt data in the required base64 format. $salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22)); @@ -490,16 +612,19 @@ class Security extends Component * Performs string comparison using timing attack resistant approach. * @see http://codereview.stackexchange.com/questions/13512 * @param string $expected string to compare. - * @param string $actual string to compare. + * @param string $actual user-supplied string. * @return boolean whether strings are equal. */ - protected function compareString($expected, $actual) + public function compareString($expected, $actual) { - // timing attack resistant approach: - $diff = 0; - for ($i = 0; $i < StringHelper::byteLength($actual); $i++) { - $diff |= (ord($actual[$i]) ^ ord($expected[$i])); + $expected .= "\0"; + $actual .= "\0"; + $expectedLength = StringHelper::byteLength($expected); + $actualLength = StringHelper::byteLength($actual); + $diff = $expectedLength - $actualLength; + for ($i = 0; $i < $actualLength; $i++) { + $diff |= (ord($actual[$i]) ^ ord($expected[$i % $expectedLength])); } return $diff === 0; } -} +} diff --git a/framework/base/Theme.php b/framework/base/Theme.php index 5337dd4..b89877b 100644 --- a/framework/base/Theme.php +++ b/framework/base/Theme.php @@ -79,6 +79,7 @@ class Theme extends Component */ public $pathMap; + /** * Initializes the theme. * @throws InvalidConfigException if [[basePath]] is not set. diff --git a/framework/base/Widget.php b/framework/base/Widget.php index 11b9209..6a1f6f5 100644 --- a/framework/base/Widget.php +++ b/framework/base/Widget.php @@ -34,7 +34,6 @@ class Widget extends Component implements ViewContextInterface * @see getId() */ public static $autoIdPrefix = 'w'; - /** * @var Widget[] the widgets that are currently being rendered (not ended). This property * is maintained by [[begin()]] and [[end()]] methods. @@ -42,6 +41,7 @@ class Widget extends Component implements ViewContextInterface */ public static $stack = []; + /** * Begins a widget. * This method creates an instance of the calling class. It will apply the configuration diff --git a/framework/behaviors/AttributeBehavior.php b/framework/behaviors/AttributeBehavior.php index 0f93a62..1ec315d 100644 --- a/framework/behaviors/AttributeBehavior.php +++ b/framework/behaviors/AttributeBehavior.php @@ -26,7 +26,7 @@ use yii\base\Event; * public function behaviors() * { * return [ - * 'attributeStamp' => [ + * [ * 'class' => AttributeBehavior::className(), * 'attributes' => [ * ActiveRecord::EVENT_BEFORE_INSERT => 'attribute1', @@ -74,6 +74,7 @@ class AttributeBehavior extends Behavior */ public $value; + /** * @inheritdoc */ diff --git a/framework/behaviors/BlameableBehavior.php b/framework/behaviors/BlameableBehavior.php index 87aab37..1b23577 100644 --- a/framework/behaviors/BlameableBehavior.php +++ b/framework/behaviors/BlameableBehavior.php @@ -14,7 +14,7 @@ use yii\db\BaseActiveRecord; /** * BlameableBehavior automatically fills the specified attributes with the current user ID. * - * To use BlameableBehavior, simply insert the following code to your ActiveRecord class: + * To use BlameableBehavior, insert the following code to your ActiveRecord class: * * ```php * use yii\behaviors\BlameableBehavior; @@ -30,7 +30,7 @@ use yii\db\BaseActiveRecord; * By default, BlameableBehavior will fill the `created_by` and `updated_by` attributes with the current user ID * when the associated AR object is being inserted; it will fill the `updated_by` attribute * with the current user ID when the AR object is being updated. If your attribute names are different, you may configure - * the [[attributes]] property like the following: + * the [[createdByAttribute]] and [[updatedByAttribute]] properties like the following: * * ```php * public function behaviors() @@ -38,10 +38,8 @@ use yii\db\BaseActiveRecord; * return [ * [ * 'class' => BlameableBehavior::className(), - * 'attributes' => [ - * ActiveRecord::EVENT_BEFORE_INSERT => 'author_id', - * ActiveRecord::EVENT_BEFORE_UPDATE => 'updater_id', - * ], + * 'createdByAttribute' => 'author_id', + * 'updatedByAttribute' => 'updater_id', * ], * ]; * } @@ -49,22 +47,19 @@ use yii\db\BaseActiveRecord; * * @author Luciano Baraglia * @author Qiang Xue + * @author Alexander Kochetov * @since 2.0 */ class BlameableBehavior extends AttributeBehavior { /** - * @var array list of attributes that are to be automatically filled with the current user ID. - * The array keys are the ActiveRecord events upon which the attributes are to be filled with the user ID, - * and the array values are the corresponding attribute(s) to be updated. You can use a string to represent - * a single attribute, or an array to represent a list of attributes. - * The default setting is to update both of the `created_by` and `updated_by` attributes upon AR insertion, - * and update the `updated_by` attribute upon AR updating. + * @var string the attribute that will receive current user ID value */ - public $attributes = [ - BaseActiveRecord::EVENT_BEFORE_INSERT => ['created_by', 'updated_by'], - BaseActiveRecord::EVENT_BEFORE_UPDATE => 'updated_by', - ]; + public $createdByAttribute = 'created_by'; + /** + * @var string the attribute that will receive current user ID value + */ + public $updatedByAttribute = 'updated_by'; /** * @var callable the value that will be assigned to the attributes. This should be a valid * PHP callable whose return value will be assigned to the current attribute(s). @@ -80,6 +75,22 @@ class BlameableBehavior extends AttributeBehavior */ public $value; + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + + if (empty($this->attributes)) { + $this->attributes = [ + BaseActiveRecord::EVENT_BEFORE_INSERT => [$this->createdByAttribute, $this->updatedByAttribute], + BaseActiveRecord::EVENT_BEFORE_UPDATE => $this->updatedByAttribute, + ]; + } + } + /** * Evaluates the value of the user. * The return result of this method will be assigned to the current attribute(s). diff --git a/framework/behaviors/SluggableBehavior.php b/framework/behaviors/SluggableBehavior.php index c55f46d..8dd0a12 100644 --- a/framework/behaviors/SluggableBehavior.php +++ b/framework/behaviors/SluggableBehavior.php @@ -31,6 +31,21 @@ use yii\helpers\Inflector; * } * ``` * + * By default, SluggableBehavior will fill the `slug` attribute with a value that can be used a slug in a URL + * when the associated AR object is being validated. If your attribute name is different, you may configure + * the [[slugAttribute]] property like the following: + * + * ```php + * public function behaviors() + * { + * return [ + * [ + * 'class' => SluggableBehavior::className(), + * 'slugAttribute' => 'alias', + * ], + * ]; + * } + * ``` * @author Alexander Kochetov * @since 2.0 */ @@ -58,6 +73,7 @@ class SluggableBehavior extends AttributeBehavior */ public $value; + /** * @inheritdoc */ diff --git a/framework/behaviors/TimestampBehavior.php b/framework/behaviors/TimestampBehavior.php index 7b5615f..9dc5348 100644 --- a/framework/behaviors/TimestampBehavior.php +++ b/framework/behaviors/TimestampBehavior.php @@ -13,7 +13,7 @@ use yii\db\Expression; /** * TimestampBehavior automatically fills the specified attributes with the current timestamp. * - * To use TimestampBehavior, simply insert the following code to your ActiveRecord class: + * To use TimestampBehavior, insert the following code to your ActiveRecord class: * * ```php * use yii\behaviors\TimestampBehavior; @@ -31,7 +31,7 @@ use yii\db\Expression; * with the timestamp when the AR object is being updated. The timestamp value is obtained by `time()`. * * If your attribute names are different or you want to use a different way of calculating the timestamp, - * you may configure the [[attributes]] and [[value]] properties like the following: + * you may configure the [[createdAtAttribute]], [[updatedAtAttribute]] and [[value]] properties like the following: * * ```php * use yii\db\Expression; @@ -39,12 +39,10 @@ use yii\db\Expression; * public function behaviors() * { * return [ - * 'timestamp' => [ + * [ * 'class' => TimestampBehavior::className(), - * 'attributes' => [ - * ActiveRecord::EVENT_BEFORE_INSERT => 'creation_time', - * ActiveRecord::EVENT_BEFORE_UPDATE => 'update_time', - * ], + * 'createdAtAttribute' => 'create_time', + * 'updatedAtAttribute' => 'update_time', * 'value' => new Expression('NOW()'), * ], * ]; @@ -59,22 +57,19 @@ use yii\db\Expression; * ``` * * @author Qiang Xue + * @author Alexander Kochetov * @since 2.0 */ class TimestampBehavior extends AttributeBehavior { /** - * @var array list of attributes that are to be automatically filled with timestamps. - * The array keys are the ActiveRecord events upon which the attributes are to be filled with timestamps, - * and the array values are the corresponding attribute(s) to be updated. You can use a string to represent - * a single attribute, or an array to represent a list of attributes. - * The default setting is to update both of the `created_at` and `updated_at` attributes upon AR insertion, - * and update the `updated_at` attribute upon AR updating. + * @var string the attribute that will receive timestamp value */ - public $attributes = [ - BaseActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'], - BaseActiveRecord::EVENT_BEFORE_UPDATE => 'updated_at', - ]; + public $createdAtAttribute = 'created_at'; + /** + * @var string the attribute that will receive timestamp value + */ + public $updatedAtAttribute = 'updated_at'; /** * @var callable|Expression The expression that will be used for generating the timestamp. * This can be either an anonymous function that returns the timestamp value, @@ -83,6 +78,22 @@ class TimestampBehavior extends AttributeBehavior */ public $value; + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + + if (empty($this->attributes)) { + $this->attributes = [ + BaseActiveRecord::EVENT_BEFORE_INSERT => [$this->createdAtAttribute, $this->updatedAtAttribute], + BaseActiveRecord::EVENT_BEFORE_UPDATE => $this->updatedAtAttribute, + ]; + } + } + /** * @inheritdoc */ diff --git a/framework/caching/ChainedDependency.php b/framework/caching/ChainedDependency.php index 7f238d0..4e67aee 100644 --- a/framework/caching/ChainedDependency.php +++ b/framework/caching/ChainedDependency.php @@ -32,6 +32,7 @@ class ChainedDependency extends Dependency */ public $dependOnAll = true; + /** * Evaluates the dependency by generating and saving the data related with dependency. * @param Cache $cache the cache component that is currently evaluating this dependency diff --git a/framework/caching/DbCache.php b/framework/caching/DbCache.php index 560c70a..4d72e5b 100644 --- a/framework/caching/DbCache.php +++ b/framework/caching/DbCache.php @@ -72,6 +72,7 @@ class DbCache extends Cache */ public $gcProbability = 100; + /** * Initializes the DbCache component. * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. diff --git a/framework/caching/DbDependency.php b/framework/caching/DbDependency.php index 28fa6fd..e5c1676 100644 --- a/framework/caching/DbDependency.php +++ b/framework/caching/DbDependency.php @@ -37,6 +37,7 @@ class DbDependency extends Dependency */ public $params = []; + /** * Generates the data needed to determine if dependency has been changed. * This method returns the value of the global state. diff --git a/framework/caching/Dependency.php b/framework/caching/Dependency.php index 8b25640..c6c2475 100644 --- a/framework/caching/Dependency.php +++ b/framework/caching/Dependency.php @@ -36,6 +36,7 @@ abstract class Dependency extends \yii\base\Object */ private static $_reusableData = []; + /** * Evaluates the dependency by generating and saving the data related with dependency. * This method is invoked by cache before writing data into it. diff --git a/framework/caching/ExpressionDependency.php b/framework/caching/ExpressionDependency.php index da45c00..43cfac7 100644 --- a/framework/caching/ExpressionDependency.php +++ b/framework/caching/ExpressionDependency.php @@ -34,6 +34,7 @@ class ExpressionDependency extends Dependency */ public $params; + /** * Generates the data needed to determine if dependency has been changed. * This method returns the result of the PHP expression. diff --git a/framework/caching/FileCache.php b/framework/caching/FileCache.php index 99bb08b..d3113fb 100644 --- a/framework/caching/FileCache.php +++ b/framework/caching/FileCache.php @@ -68,6 +68,7 @@ class FileCache extends Cache */ public $dirMode = 0775; + /** * Initializes this component by ensuring the existence of the cache path. */ diff --git a/framework/caching/FileDependency.php b/framework/caching/FileDependency.php index 6ed0d6f..743aa85 100644 --- a/framework/caching/FileDependency.php +++ b/framework/caching/FileDependency.php @@ -27,6 +27,7 @@ class FileDependency extends Dependency */ public $fileName; + /** * Generates the data needed to determine if dependency has been changed. * This method returns the file's last modification time. diff --git a/framework/caching/MemCache.php b/framework/caching/MemCache.php index 64deef8..b46be12 100644 --- a/framework/caching/MemCache.php +++ b/framework/caching/MemCache.php @@ -83,6 +83,17 @@ class MemCache extends Cache */ public $options; /** + * @var string memcached sasl username. This property is used only when [[useMemcached]] is true. + * @see http://php.net/manual/en/memcached.setsaslauthdata.php + */ + public $username; + /** + * @var string memcached sasl password. This property is used only when [[useMemcached]] is true. + * @see http://php.net/manual/en/memcached.setsaslauthdata.php + */ + public $password; + + /** * @var \Memcache|\Memcached the Memcache instance */ private $_cache = null; @@ -200,6 +211,10 @@ class MemCache extends Cache if ($this->useMemcached) { $this->_cache = $this->persistentId !== null ? new \Memcached($this->persistentId) : new \Memcached; + if ($this->username !== null || $this->password !== null) { + $this->_cache->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $this->_cache->setSaslAuthData($this->username, $this->password); + } if (!empty($this->options)) { $this->_cache->setOptions($this->options); } diff --git a/framework/caching/TagDependency.php b/framework/caching/TagDependency.php index 5a6c487..9aa75c5 100644 --- a/framework/caching/TagDependency.php +++ b/framework/caching/TagDependency.php @@ -7,7 +7,6 @@ namespace yii\caching; - /** * TagDependency associates a cached data item with one or multiple [[tags]]. * @@ -23,6 +22,7 @@ class TagDependency extends Dependency */ public $tags = []; + /** * Generates the data needed to determine if dependency has been changed. * This method does nothing in this class. diff --git a/framework/captcha/Captcha.php b/framework/captcha/Captcha.php index 20ca9b7..a9aeb39 100644 --- a/framework/captcha/Captcha.php +++ b/framework/captcha/Captcha.php @@ -57,6 +57,7 @@ class Captcha extends InputWidget */ public $options = ['class' => 'form-control']; + /** * Initializes the widget. */ diff --git a/framework/captcha/CaptchaAction.php b/framework/captcha/CaptchaAction.php index a97b87e..ac9d5e9 100644 --- a/framework/captcha/CaptchaAction.php +++ b/framework/captcha/CaptchaAction.php @@ -42,6 +42,7 @@ class CaptchaAction extends Action * The name of the GET parameter indicating whether the CAPTCHA image should be regenerated. */ const REFRESH_GET_VAR = 'refresh'; + /** * @var integer how many times should the same CAPTCHA be displayed. Defaults to 3. * A value less than or equal to 0 means the test is unlimited (available since version 1.1.2). @@ -98,6 +99,7 @@ class CaptchaAction extends Action */ public $fixedVerifyCode; + /** * Initializes the action. * @throws InvalidConfigException if the font file does not exist. diff --git a/framework/captcha/CaptchaValidator.php b/framework/captcha/CaptchaValidator.php index 0ddeb48..7d122b3 100644 --- a/framework/captcha/CaptchaValidator.php +++ b/framework/captcha/CaptchaValidator.php @@ -39,6 +39,7 @@ class CaptchaValidator extends Validator */ public $captchaAction = 'site/captcha'; + /** * @inheritdoc */ diff --git a/framework/classes.php b/framework/classes.php index 94cba2f..cb7631e 100644 --- a/framework/classes.php +++ b/framework/classes.php @@ -66,9 +66,9 @@ return [ 'yii\caching\ExpressionDependency' => YII_PATH . '/caching/ExpressionDependency.php', 'yii\caching\FileCache' => YII_PATH . '/caching/FileCache.php', 'yii\caching\FileDependency' => YII_PATH . '/caching/FileDependency.php', - 'yii\caching\TagDependency' => YII_PATH . '/caching/TagDependency.php', 'yii\caching\MemCache' => YII_PATH . '/caching/MemCache.php', 'yii\caching\MemCacheServer' => YII_PATH . '/caching/MemCacheServer.php', + 'yii\caching\TagDependency' => YII_PATH . '/caching/TagDependency.php', 'yii\caching\WinCache' => YII_PATH . '/caching/WinCache.php', 'yii\caching\XCache' => YII_PATH . '/caching/XCache.php', 'yii\caching\ZendDataCache' => YII_PATH . '/caching/ZendDataCache.php', @@ -130,6 +130,7 @@ return [ 'yii\filters\AccessControl' => YII_PATH . '/filters/AccessControl.php', 'yii\filters\AccessRule' => YII_PATH . '/filters/AccessRule.php', 'yii\filters\ContentNegotiator' => YII_PATH . '/filters/ContentNegotiator.php', + 'yii\filters\Cors' => YII_PATH . '/filters/Cors.php', 'yii\filters\HttpCache' => YII_PATH . '/filters/HttpCache.php', 'yii\filters\PageCache' => YII_PATH . '/filters/PageCache.php', 'yii\filters\RateLimitInterface' => YII_PATH . '/filters/RateLimitInterface.php', @@ -157,7 +158,6 @@ return [ 'yii\helpers\BaseInflector' => YII_PATH . '/helpers/BaseInflector.php', 'yii\helpers\BaseJson' => YII_PATH . '/helpers/BaseJson.php', 'yii\helpers\BaseMarkdown' => YII_PATH . '/helpers/BaseMarkdown.php', - 'yii\helpers\BaseSecurity' => YII_PATH . '/helpers/BaseSecurity.php', 'yii\helpers\BaseStringHelper' => YII_PATH . '/helpers/BaseStringHelper.php', 'yii\helpers\BaseUrl' => YII_PATH . '/helpers/BaseUrl.php', 'yii\helpers\BaseVarDumper' => YII_PATH . '/helpers/BaseVarDumper.php', diff --git a/framework/console/Application.php b/framework/console/Application.php index a55ea17..88838a7 100644 --- a/framework/console/Application.php +++ b/framework/console/Application.php @@ -69,6 +69,7 @@ class Application extends \yii\base\Application */ public $controller; + /** * @inheritdoc */ diff --git a/framework/console/Controller.php b/framework/console/Controller.php index 362e39c..3c21de6 100644 --- a/framework/console/Controller.php +++ b/framework/console/Controller.php @@ -36,13 +36,13 @@ class Controller extends \yii\base\Controller * @var boolean whether to run the command interactively. */ public $interactive = true; - /** * @var boolean whether to enable ANSI color in the output. * If not set, ANSI color will only be enabled for terminals that support it. */ public $color; + /** * Returns a value indicating whether ANSI color is enabled. * diff --git a/framework/console/Markdown.php b/framework/console/Markdown.php index 9621ed7..2e9954d 100644 --- a/framework/console/Markdown.php +++ b/framework/console/Markdown.php @@ -31,6 +31,7 @@ class Markdown extends \cebe\markdown\Parser '_', // underscore ]; + /** * @inheritDoc */ @@ -44,6 +45,10 @@ class Markdown extends \cebe\markdown\Parser /** * Consume lines for a fenced code block + * + * @param array $lines + * @param integer $current + * @return array */ protected function consumeFencedCode($lines, $current) { @@ -70,12 +75,18 @@ class Markdown extends \cebe\markdown\Parser /** * Renders a code block + * + * @param array $block + * @return string */ protected function renderCode($block) { return Console::ansiFormat(implode("\n", $block['content']), [Console::BG_GREY]) . "\n"; } + /** + * @inheritdoc + */ protected function renderParagraph($block) { return rtrim($this->parseInline(implode("\n", $block['content']))) . "\n"; @@ -97,6 +108,8 @@ class Markdown extends \cebe\markdown\Parser /** * Parses an inline code span `` ` ``. + * @param string $text + * @return array */ protected function parseCode($text) { @@ -120,6 +133,8 @@ class Markdown extends \cebe\markdown\Parser /** * Parses empathized and strong elements. + * @param string $text + * @return array */ protected function parseEmphStrong($text) { @@ -146,6 +161,8 @@ class Markdown extends \cebe\markdown\Parser /** * Parses the strikethrough feature. + * @param string $markdown + * @return array */ protected function parseStrike($markdown) { @@ -160,6 +177,8 @@ class Markdown extends \cebe\markdown\Parser /** * Parses escaped special characters. + * @param string $text + * @return array */ protected function parseEscape($text) { diff --git a/framework/console/Request.php b/framework/console/Request.php index fa017e4..5a594b7 100644 --- a/framework/console/Request.php +++ b/framework/console/Request.php @@ -22,6 +22,7 @@ class Request extends \yii\base\Request { private $_params; + /** * Returns the command line arguments. * @return array the command line arguments. It does not include the entry script name. diff --git a/framework/console/controllers/AssetController.php b/framework/console/controllers/AssetController.php index 87e10e4..9c7bffb 100644 --- a/framework/console/controllers/AssetController.php +++ b/framework/console/controllers/AssetController.php @@ -92,6 +92,7 @@ class AssetController extends Controller */ private $_assetManager = []; + /** * Returns the asset manager instance. * @throws \yii\console\Exception on invalid configuration. diff --git a/framework/console/controllers/BaseMigrateController.php b/framework/console/controllers/BaseMigrateController.php index 68015de..9737012 100644 --- a/framework/console/controllers/BaseMigrateController.php +++ b/framework/console/controllers/BaseMigrateController.php @@ -41,6 +41,7 @@ abstract class BaseMigrateController extends Controller */ public $templateFile; + /** * @inheritdoc */ @@ -363,6 +364,7 @@ abstract class BaseMigrateController extends Controller * * @param integer $limit the maximum number of migrations to be displayed. * If it is 0, the whole migration history will be displayed. + * @throws \yii\console\Exception if invalid limit value passed */ public function actionHistory($limit = 10) { @@ -371,7 +373,7 @@ abstract class BaseMigrateController extends Controller } else { $limit = (int) $limit; if ($limit < 1) { - throw new Exception("The step argument must be greater than 0."); + throw new Exception("The limit must be greater than 0."); } } @@ -406,6 +408,7 @@ abstract class BaseMigrateController extends Controller * * @param integer $limit the maximum number of new migrations to be displayed. * If it is 0, all available new migrations will be displayed. + * @throws \yii\console\Exception if invalid limit value passed */ public function actionNew($limit = 10) { @@ -414,7 +417,7 @@ abstract class BaseMigrateController extends Controller } else { $limit = (int) $limit; if ($limit < 1) { - throw new Exception("The step argument must be greater than 0."); + throw new Exception("The limit must be greater than 0."); } } diff --git a/framework/console/controllers/HelpController.php b/framework/console/controllers/HelpController.php index 7b7d00c..7bcad78 100644 --- a/framework/console/controllers/HelpController.php +++ b/framework/console/controllers/HelpController.php @@ -156,7 +156,7 @@ class HelpController extends Controller if (is_dir($controllerPath)) { $files = scandir($controllerPath); foreach ($files as $file) { - if (strcmp(substr($file, -14), 'Controller.php') === 0) { + if (!empty($file) && substr_compare($file, 'Controller.php', -14) === 0) { $controllerClass = $module->controllerNamespace . '\\' . substr(basename($file), 0, -4); if ($this->validateControllerClass($controllerClass)) { $commands[] = $prefix . Inflector::camel2id(substr(basename($file), 0, -14)); diff --git a/framework/console/controllers/MessageController.php b/framework/console/controllers/MessageController.php index 519a09b..b97c970 100644 --- a/framework/console/controllers/MessageController.php +++ b/framework/console/controllers/MessageController.php @@ -41,6 +41,7 @@ class MessageController extends Controller */ public $defaultAction = 'extract'; + /** * Creates a configuration file for the "extract" command. * @@ -209,11 +210,11 @@ class MessageController extends Controller } } - echo $savedFlag ? "saved.\n" : "nothing new...skipped.\n"; + echo $savedFlag ? "saved.\n" : "Nothing new...skipped.\n"; echo $removeUnused ? "Deleting obsoleted messages..." : "Updating obsoleted messages..."; if (empty($obsolete)) { - echo "nothing obsoleted...skipped.\n"; + echo "Nothing obsoleted...skipped.\n"; } else { if ($removeUnused) { $db->createCommand() @@ -286,12 +287,10 @@ class MessageController extends Controller foreach ($messages as $category => $msgs) { $file = str_replace("\\", '/', "$dirName/$category.php"); $path = dirname($file); - if (!is_dir($path)) { - mkdir($path, 0755, true); - } + FileHelper::createDirectory($path); $msgs = array_values(array_unique($msgs)); echo "Saving messages to $file...\n"; - $this->saveMessagesCategoryToPHP($msgs, $file, $overwrite, $removeUnused, $sort); + $this->saveMessagesCategoryToPHP($msgs, $file, $overwrite, $removeUnused, $sort, $category); } } @@ -303,23 +302,23 @@ class MessageController extends Controller * @param boolean $overwrite if existing file should be overwritten without backup * @param boolean $removeUnused if obsolete translations should be removed * @param boolean $sort if translations should be sorted + * @param string $category message category */ - protected function saveMessagesCategoryToPHP($messages, $fileName, $overwrite, $removeUnused, $sort) + protected function saveMessagesCategoryToPHP($messages, $fileName, $overwrite, $removeUnused, $sort, $category) { if (is_file($fileName)) { - $translated = require($fileName); + $existingMessages = require($fileName); sort($messages); - ksort($translated); - if (array_keys($translated) == $messages) { - echo "nothing new...skipped.\n"; - - return self::EXIT_CODE_NORMAL; + ksort($existingMessages); + if (array_keys($existingMessages) == $messages) { + echo "Nothing new in \"$category\" category... Nothing to save.\n"; + return; } $merged = []; $untranslated = []; foreach ($messages as $message) { - if (array_key_exists($message, $translated) && strlen($translated[$message]) > 0) { - $merged[$message] = $translated[$message]; + if (array_key_exists($message, $existingMessages) && strlen($existingMessages[$message]) > 0) { + $merged[$message] = $existingMessages[$message]; } else { $untranslated[] = $message; } @@ -330,10 +329,10 @@ class MessageController extends Controller foreach ($untranslated as $message) { $todo[$message] = ''; } - ksort($translated); - foreach ($translated as $message => $translation) { + ksort($existingMessages); + foreach ($existingMessages as $message => $translation) { if (!isset($merged[$message]) && !isset($todo[$message]) && !$removeUnused) { - if (substr($translation, 0, 2) === '@@' && substr($translation, -2) === '@@') { + if (!empty($translation) && strncmp($translation, '@@', 2) === 0 && substr_compare($translation, '@@', -2) === 0) { $todo[$message] = $translation; } else { $todo[$message] = '@@' . $translation . '@@'; @@ -355,7 +354,6 @@ class MessageController extends Controller } ksort($merged); } - echo "Saved.\n"; $array = VarDumper::export($merged); @@ -383,6 +381,7 @@ return $array; EOD; file_put_contents($fileName, $content); + echo "Saved.\n"; } /** @@ -398,10 +397,7 @@ EOD; protected function saveMessagesToPO($messages, $dirName, $overwrite, $removeUnused, $sort, $catalog) { $file = str_replace("\\", '/', "$dirName/$catalog.po"); - $path = dirname($file); - if (!is_dir($path)) { - mkdir($path, 0755, true); - } + FileHelper::createDirectory(dirname($file)); echo "Saving messages to $file...\n"; $poFile = new GettextPoFile(); @@ -411,6 +407,7 @@ EOD; $notTranslatedYet = []; $todos = []; + $hasSomethingToWrite = false; foreach ($messages as $category => $msgs) { $msgs = array_values(array_unique($msgs)); @@ -420,8 +417,14 @@ EOD; sort($msgs); ksort($existingMessages); if (array_keys($existingMessages) == $msgs) { - echo "Nothing new... skipped.\n"; - return self::EXIT_CODE_NORMAL; + echo "Nothing new in \"$category\" category...\n"; + + sort($msgs); + foreach ($msgs as $message) { + $merged[$category . chr(4) . $message] = ''; + } + ksort($merged); + continue; } // merge existing message translations with new message translations @@ -443,7 +446,7 @@ EOD; // add obsolete unused messages foreach ($existingMessages as $message => $translation) { if (!isset($merged[$category . chr(4) . $message]) && !isset($todos[$category . chr(4) . $message]) && !$removeUnused) { - if (substr($translation, 0, 2) === '@@' && substr($translation, -2) === '@@') { + if (!empty($translation) && substr($translation, 0, 2) === '@@' && substr($translation, -2) === '@@') { $todos[$category . chr(4) . $message] = $translation; } else { $todos[$category . chr(4) . $message] = '@@' . $translation . '@@'; @@ -459,8 +462,6 @@ EOD; if ($overwrite === false) { $file .= '.merged'; } - - echo "Translation merged.\n"; } else { sort($msgs); foreach ($msgs as $message) { @@ -468,8 +469,14 @@ EOD; } ksort($merged); } + echo "Category \"$category\" merged.\n"; + $hasSomethingToWrite = true; + } + if ($hasSomethingToWrite) { + $poFile->save($file, $merged); + echo "Saved.\n"; + } else { + echo "Nothing to save.\n"; } - $poFile->save($file, $merged); - echo "Saved.\n"; } } diff --git a/framework/console/controllers/MigrateController.php b/framework/console/controllers/MigrateController.php index 3925fd5..464e707 100644 --- a/framework/console/controllers/MigrateController.php +++ b/framework/console/controllers/MigrateController.php @@ -68,6 +68,7 @@ class MigrateController extends BaseMigrateController */ public $db = 'db'; + /** * @inheritdoc */ diff --git a/framework/data/BaseDataProvider.php b/framework/data/BaseDataProvider.php index b3fbb90..5f61cc0 100644 --- a/framework/data/BaseDataProvider.php +++ b/framework/data/BaseDataProvider.php @@ -43,6 +43,7 @@ abstract class BaseDataProvider extends Component implements DataProviderInterfa private $_models; private $_totalCount; + /** * Prepares the data models that will be made available in the current page. * @return array the available data models diff --git a/framework/data/Pagination.php b/framework/data/Pagination.php index db18c30..78fdd5a 100644 --- a/framework/data/Pagination.php +++ b/framework/data/Pagination.php @@ -134,12 +134,14 @@ class Pagination extends Object implements Linkable * the maximal page size. If this is false, it means [[pageSize]] should always return the value of [[defaultPageSize]]. */ public $pageSizeLimit = [1, 50]; + /** * @var integer number of items on each page. * If it is less than 1, it means the page size is infinite, and thus a single page contains all items. */ private $_pageSize; + /** * @return integer number of pages */ diff --git a/framework/data/Sort.php b/framework/data/Sort.php index 0a8c448..f8abac5 100644 --- a/framework/data/Sort.php +++ b/framework/data/Sort.php @@ -81,7 +81,6 @@ class Sort extends Object * Defaults to false, which means each time the data can only be sorted by one attribute. */ public $enableMultiSort = false; - /** * @var array list of attributes that are allowed to be sorted. Its syntax can be * described using the following example: diff --git a/framework/data/SqlDataProvider.php b/framework/data/SqlDataProvider.php index b0e9313..3a261c9 100644 --- a/framework/data/SqlDataProvider.php +++ b/framework/data/SqlDataProvider.php @@ -82,6 +82,7 @@ class SqlDataProvider extends BaseDataProvider */ public $key; + /** * Initializes the DB connection component. * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. diff --git a/framework/db/ActiveQuery.php b/framework/db/ActiveQuery.php index 06778f7..dbaa1ad 100644 --- a/framework/db/ActiveQuery.php +++ b/framework/db/ActiveQuery.php @@ -242,24 +242,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface { $row = parent::one($db); if ($row !== false) { - if ($this->asArray) { - $model = $row; - } else { - /* @var $class ActiveRecord */ - $class = $this->modelClass; - $model = $class::instantiate($row); - $class::populateRecord($model, $row); - } - if (!empty($this->with)) { - $models = [$model]; - $this->findWith($this->with, $models); - $model = $models[0]; - } - if (!$this->asArray) { - $model->afterFind(); - } - - return $model; + $models = $this->prepareResult([$row]); + return reset($models) ?: null; } else { return null; } diff --git a/framework/db/ActiveRecord.php b/framework/db/ActiveRecord.php index 2025ca4..4027dd1 100644 --- a/framework/db/ActiveRecord.php +++ b/framework/db/ActiveRecord.php @@ -96,6 +96,7 @@ class ActiveRecord extends BaseActiveRecord */ const OP_ALL = 0x07; + /** * Loads default values from database table schema * diff --git a/framework/db/BaseActiveRecord.php b/framework/db/BaseActiveRecord.php index 619a148..d0ec5cc 100644 --- a/framework/db/BaseActiveRecord.php +++ b/framework/db/BaseActiveRecord.php @@ -686,6 +686,8 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface /** * @see update() + * @param array $attributes attributes to update + * @return integer number of rows updated * @throws StaleObjectException */ protected function updateInternal($attributes = null) @@ -1042,7 +1044,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface foreach ($row as $name => $value) { if (isset($columns[$name])) { $record->_attributes[$name] = $value; - } else { + } elseif ($record->canSetProperty($name)) { $record->$name = $value; } } diff --git a/framework/db/BatchQueryResult.php b/framework/db/BatchQueryResult.php index 9eb77bd..e5a1606 100644 --- a/framework/db/BatchQueryResult.php +++ b/framework/db/BatchQueryResult.php @@ -49,6 +49,7 @@ class BatchQueryResult extends Object implements \Iterator * If false, a whole batch of rows will be returned in each iteration. */ public $each = false; + /** * @var DataReader the data reader associated with this batch query. */ @@ -66,6 +67,7 @@ class BatchQueryResult extends Object implements \Iterator */ private $_key; + /** * Destructor. */ diff --git a/framework/db/ColumnSchema.php b/framework/db/ColumnSchema.php index b094103..128bc38 100644 --- a/framework/db/ColumnSchema.php +++ b/framework/db/ColumnSchema.php @@ -78,6 +78,7 @@ class ColumnSchema extends Object */ public $comment; + /** * Converts the input value according to [[phpType]] after retrieval from the database. * If the value is null or an [[Expression]], it will not be converted. diff --git a/framework/db/Connection.php b/framework/db/Connection.php index 4a2856d..a11e39a 100644 --- a/framework/db/Connection.php +++ b/framework/db/Connection.php @@ -110,15 +110,22 @@ use yii\caching\Cache; * ], * ~~~ * - * * @property string $driverName Name of the DB driver. * @property boolean $isActive Whether the DB connection is established. This property is read-only. * @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the * sequence object. This property is read-only. + * @property PDO $masterPdo The PDO instance for the currently active master connection. This property is + * read-only. * @property QueryBuilder $queryBuilder The query builder for the current DB connection. This property is * read-only. + * @property array $queryCacheInfo The current query cache information, or null if query cache is not enabled. + * This property is read-only. * @property Schema $schema The schema information for the database opened by this connection. This property * is read-only. + * @property Connection $slave The currently active slave connection. Null is returned if there is slave + * available and `$fallbackToMaster` is false. This property is read-only. + * @property PDO $slavePdo The PDO instance for the currently active slave connection. Null is returned if no + * slave connection is available and `$fallbackToMaster` is false. This property is read-only. * @property Transaction $transaction The currently active transaction. Null if no active transaction. This * property is read-only. * diff --git a/framework/db/DataReader.php b/framework/db/DataReader.php index fce00bc..ebe8351 100644 --- a/framework/db/DataReader.php +++ b/framework/db/DataReader.php @@ -58,6 +58,7 @@ class DataReader extends \yii\base\Object implements \Iterator, \Countable private $_row; private $_index = -1; + /** * Constructor. * @param Command $command the command generating the query result diff --git a/framework/db/Exception.php b/framework/db/Exception.php index 4ed0cc5..2c04f82 100644 --- a/framework/db/Exception.php +++ b/framework/db/Exception.php @@ -21,6 +21,7 @@ class Exception extends \yii\base\Exception */ public $errorInfo = []; + /** * Constructor. * @param string $message PDO error message @@ -42,6 +43,9 @@ class Exception extends \yii\base\Exception return 'Database Exception'; } + /** + * @return string readable representation of exception + */ public function __toString() { return parent::__toString() . PHP_EOL diff --git a/framework/db/Expression.php b/framework/db/Expression.php index e378be2..8e725ec 100644 --- a/framework/db/Expression.php +++ b/framework/db/Expression.php @@ -36,6 +36,7 @@ class Expression extends \yii\base\Object */ public $params = []; + /** * Constructor. * @param string $expression the DB expression diff --git a/framework/db/Migration.php b/framework/db/Migration.php index 3533f24..6326636 100644 --- a/framework/db/Migration.php +++ b/framework/db/Migration.php @@ -44,6 +44,7 @@ class Migration extends Component implements MigrationInterface */ public $db = 'db'; + /** * Initializes the migration. * This method will set [[db]] to be the 'db' application component, if it is null. diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index df17442..33068a5 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -41,6 +41,7 @@ class QueryBuilder extends \yii\base\Object * Child classes should override this property to declare supported type mappings. */ public $typeMap = []; + /** * @var array map of query condition to builder methods. * These methods are used by [[buildCondition]] to build SQL conditions from array syntax. @@ -689,6 +690,13 @@ class QueryBuilder extends \yii\base\Object return implode($this->separator, $joins); } + /** + * Quotes table names passed + * + * @param array $tables + * @param array $params + * @return array + */ private function quoteTableNames($tables, &$params) { foreach ($tables as $i => $table) { @@ -1077,6 +1085,15 @@ class QueryBuilder extends \yii\base\Object } } + /** + * Builds SQL for IN condition + * + * @param string $operator + * @param array $columns + * @param array $values + * @param array $params + * @return string SQL + */ protected function buildCompositeInCondition($operator, $columns, $values, &$params) { $vss = []; diff --git a/framework/db/QueryTrait.php b/framework/db/QueryTrait.php index 9a5f109..0244ff6 100644 --- a/framework/db/QueryTrait.php +++ b/framework/db/QueryTrait.php @@ -342,6 +342,12 @@ trait QueryTrait return $this; } + /** + * Normalizes format of ORDER BY data + * + * @param array|string $columns + * @return array + */ protected function normalizeOrderBy($columns) { if (is_array($columns)) { diff --git a/framework/db/Schema.php b/framework/db/Schema.php index 128690f..983d560 100644 --- a/framework/db/Schema.php +++ b/framework/db/Schema.php @@ -64,6 +64,14 @@ abstract class Schema extends Object */ public $defaultSchema; /** + * @var array map of DB errors and corresponding exceptions + * If left part is found in DB error message exception class from the right part is used. + */ + public $exceptionMap = [ + 'SQLSTATE[23' => 'yii\db\IntegrityException', + ]; + + /** * @var array list of ALL table names in the database */ private $_tableNames = []; @@ -76,13 +84,6 @@ abstract class Schema extends Object */ private $_builder; - /** - * @var array map of DB errors and corresponding exceptions - * If left part is found in DB error message exception class from the right part is used. - */ - public $exceptionMap = [ - 'SQLSTATE[23' => 'yii\db\IntegrityException', - ]; /** * Loads the metadata for the specified table. diff --git a/framework/db/TableSchema.php b/framework/db/TableSchema.php index 818a8cb..311c68a 100644 --- a/framework/db/TableSchema.php +++ b/framework/db/TableSchema.php @@ -59,6 +59,7 @@ class TableSchema extends Object */ public $columns = []; + /** * Gets the named column metadata. * This is a convenient method for retrieving a named column even if it does not exist. diff --git a/framework/db/Transaction.php b/framework/db/Transaction.php index cdbbb17..ebba0ed 100644 --- a/framework/db/Transaction.php +++ b/framework/db/Transaction.php @@ -67,6 +67,7 @@ class Transaction extends \yii\base\Object * @var Connection the database connection that this transaction is associated with. */ public $db; + /** * @var integer the nesting level of the transaction. 0 means the outermost level. */ diff --git a/framework/db/cubrid/QueryBuilder.php b/framework/db/cubrid/QueryBuilder.php index effeb3e..3602ebc 100644 --- a/framework/db/cubrid/QueryBuilder.php +++ b/framework/db/cubrid/QueryBuilder.php @@ -39,6 +39,7 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_MONEY => 'decimal(19,4)', ]; + /** * Creates a SQL statement for resetting the sequence value of a table's primary key. * The sequence will be reset such that the primary key of the next new row inserted diff --git a/framework/db/cubrid/Schema.php b/framework/db/cubrid/Schema.php index 677bce2..1a4655c 100644 --- a/framework/db/cubrid/Schema.php +++ b/framework/db/cubrid/Schema.php @@ -64,7 +64,6 @@ class Schema extends \yii\db\Schema 'sequence' => self::TYPE_STRING, 'enum' => self::TYPE_STRING, ]; - /** * @var array map of DB errors and corresponding exceptions * If left part is found in DB error message exception class from the right part is used. @@ -73,6 +72,7 @@ class Schema extends \yii\db\Schema 'Operation would have caused one or more unique constraint violations' => 'yii\db\IntegrityException', ]; + /** * @inheritdoc */ diff --git a/framework/db/mssql/PDO.php b/framework/db/mssql/PDO.php index 2b1ab34..9ea53aa 100644 --- a/framework/db/mssql/PDO.php +++ b/framework/db/mssql/PDO.php @@ -66,6 +66,9 @@ class PDO extends \PDO * Retrieve a database connection attribute. * It is necessary to override PDO's method as some MSSQL PDO driver (e.g. dblib) does not * support getting attributes + * @param integer $attribute One of the PDO::ATTR_* constants. + * @return mixed A successful call returns the value of the requested PDO attribute. + * An unsuccessful call returns null. */ public function getAttribute($attribute) { diff --git a/framework/db/mssql/QueryBuilder.php b/framework/db/mssql/QueryBuilder.php index b368ddd..39fa5cf 100644 --- a/framework/db/mssql/QueryBuilder.php +++ b/framework/db/mssql/QueryBuilder.php @@ -17,8 +17,6 @@ use yii\base\InvalidParamException; */ class QueryBuilder extends \yii\db\QueryBuilder { - protected $_oldMssql; - /** * @var array mapping from abstract column types (keys) to physical column types (values). */ @@ -233,7 +231,12 @@ class QueryBuilder extends \yii\db\QueryBuilder } /** - * @return boolean if MSSQL used is old + * @var boolean whether MSSQL used is old. + */ + private $_oldMssql; + + /** + * @return boolean whether MSSQL used is old. * @throws \yii\base\InvalidConfigException * @throws \yii\db\Exception */ diff --git a/framework/db/mssql/Schema.php b/framework/db/mssql/Schema.php index 5a72595..994402a 100644 --- a/framework/db/mssql/Schema.php +++ b/framework/db/mssql/Schema.php @@ -35,11 +35,9 @@ class Schema extends \yii\db\Schema 'int' => self::TYPE_INTEGER, 'tinyint' => self::TYPE_SMALLINT, 'money' => self::TYPE_MONEY, - // approximate numbers 'float' => self::TYPE_FLOAT, 'real' => self::TYPE_FLOAT, - // date and time 'date' => self::TYPE_DATE, 'datetimeoffset' => self::TYPE_DATETIME, @@ -47,22 +45,18 @@ class Schema extends \yii\db\Schema 'smalldatetime' => self::TYPE_DATETIME, 'datetime' => self::TYPE_DATETIME, 'time' => self::TYPE_TIME, - // character strings 'char' => self::TYPE_STRING, 'varchar' => self::TYPE_STRING, 'text' => self::TYPE_TEXT, - // unicode character strings 'nchar' => self::TYPE_STRING, 'nvarchar' => self::TYPE_STRING, 'ntext' => self::TYPE_TEXT, - // binary strings 'binary' => self::TYPE_BINARY, 'varbinary' => self::TYPE_BINARY, 'image' => self::TYPE_BINARY, - // other data types // 'cursor' type cannot be used with tables 'timestamp' => self::TYPE_TIMESTAMP, @@ -73,6 +67,7 @@ class Schema extends \yii\db\Schema 'table' => self::TYPE_STRING, ]; + /** * @inheritdoc */ diff --git a/framework/db/mysql/QueryBuilder.php b/framework/db/mysql/QueryBuilder.php index 18f5ec4..1f93e02 100644 --- a/framework/db/mysql/QueryBuilder.php +++ b/framework/db/mysql/QueryBuilder.php @@ -40,6 +40,7 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_MONEY => 'decimal(19,4)', ]; + /** * Builds a SQL statement for renaming a column. * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. diff --git a/framework/db/mysql/Schema.php b/framework/db/mysql/Schema.php index d863cd0..66392cd 100644 --- a/framework/db/mysql/Schema.php +++ b/framework/db/mysql/Schema.php @@ -52,6 +52,7 @@ class Schema extends \yii\db\Schema 'enum' => self::TYPE_STRING, ]; + /** * Quotes a table name for use in a query. * A simple table name has no schema prefix. diff --git a/framework/db/oci/QueryBuilder.php b/framework/db/oci/QueryBuilder.php index 7f8037a..91bdd39 100644 --- a/framework/db/oci/QueryBuilder.php +++ b/framework/db/oci/QueryBuilder.php @@ -18,8 +18,31 @@ use yii\db\Connection; */ class QueryBuilder extends \yii\db\QueryBuilder { + /** + * @var array mapping from abstract column types (keys) to physical column types (values). + */ + public $typeMap = [ + Schema::TYPE_PK => 'NUMBER(10) NOT NULL PRIMARY KEY', + Schema::TYPE_BIGPK => 'NUMBER(20) NOT NULL PRIMARY KEY', + Schema::TYPE_STRING => 'VARCHAR2(255)', + Schema::TYPE_TEXT => 'CLOB', + Schema::TYPE_SMALLINT => 'NUMBER(5)', + Schema::TYPE_INTEGER => 'NUMBER(10)', + Schema::TYPE_BIGINT => 'NUMBER(20)', + Schema::TYPE_FLOAT => 'NUMBER', + Schema::TYPE_DECIMAL => 'NUMBER', + Schema::TYPE_DATETIME => 'TIMESTAMP', + Schema::TYPE_TIMESTAMP => 'TIMESTAMP', + Schema::TYPE_TIME => 'TIMESTAMP', + Schema::TYPE_DATE => 'DATE', + Schema::TYPE_BINARY => 'BLOB', + Schema::TYPE_BOOLEAN => 'NUMBER(1)', + Schema::TYPE_MONEY => 'NUMBER(19,4)', + ]; + private $_sql; + /** * @inheritdoc */ @@ -50,6 +73,9 @@ class QueryBuilder extends \yii\db\QueryBuilder return [$this->_sql, $params]; } + /** + * @inheritdoc + */ public function buildLimit($limit, $offset) { $filters = []; diff --git a/framework/db/oci/Schema.php b/framework/db/oci/Schema.php index ab4cde1..8de7b71 100644 --- a/framework/db/oci/Schema.php +++ b/framework/db/oci/Schema.php @@ -15,26 +15,14 @@ use yii\db\ColumnSchema; /** * Schema is the class for retrieving metadata from an Oracle database * - * @todo mapping from physical types to abstract types + * @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the + * sequence object. This property is read-only. * * @author Qiang Xue * @since 2.0 */ class Schema extends \yii\db\Schema { - const TYPE_PK = 'NUMBER(10) NOT NULL PRIMARY KEY'; - const TYPE_STRING = 'VARCHAR2(255)'; - const TYPE_TEXT = 'CLOB'; - const TYPE_INTEGER = 'NUMBER(10)'; - const TYPE_FLOAT = 'NUMBER'; - const TYPE_DECIMAL = 'NUMBER'; - const TYPE_DATETIME = 'TIMESTAMP'; - const TYPE_TIMESTAMP = 'TIMESTAMP'; - const TYPE_TIME = 'TIMESTAMP'; - const TYPE_DATE = 'DATE'; - const TYPE_BINARY = 'BLOB'; - const TYPE_BOOLEAN = 'NUMBER(1)'; - const TYPE_MONEY = 'NUMBER(19,4)'; /** * @inheritdoc */ @@ -172,7 +160,6 @@ EOD; * @internal param \yii\db\TableSchema $table ->name the table schema * @return string whether the sequence exists */ - protected function getTableSequenceName($tablename){ $seq_name_sql="select ud.referenced_name as sequence_name @@ -184,7 +171,7 @@ EOD; return $this->db->createCommand($seq_name_sql)->queryScalar(); } - /* + /** * @Overrides method in class 'Schema' * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php -> Oracle does not support this * @@ -205,6 +192,12 @@ EOD; } } + /** + * Creates ColumnSchema instance + * + * @param array $column + * @return ColumnSchema + */ protected function createColumn($column) { $c = new ColumnSchema(); @@ -227,6 +220,10 @@ EOD; return $c; } + /** + * Finds constraints and fills them into TableSchema object passed + * @param TableSchema $table + */ protected function findConstraints($table) { $sql = << 'boolean', Schema::TYPE_MONEY => 'numeric(19,4)', ]; + /** * @var array map of query condition to builder methods. * These methods are used by [[buildCondition]] to build SQL conditions from array syntax. diff --git a/framework/db/pgsql/Schema.php b/framework/db/pgsql/Schema.php index 8db814a..9d9f433 100644 --- a/framework/db/pgsql/Schema.php +++ b/framework/db/pgsql/Schema.php @@ -107,6 +107,7 @@ class Schema extends \yii\db\Schema 'xml' => self::TYPE_STRING ]; + /** * Creates a query builder for the PostgreSQL database. * @return QueryBuilder query builder instance diff --git a/framework/db/sqlite/Schema.php b/framework/db/sqlite/Schema.php index c7e1333..39acfcf 100644 --- a/framework/db/sqlite/Schema.php +++ b/framework/db/sqlite/Schema.php @@ -58,6 +58,7 @@ class Schema extends \yii\db\Schema 'enum' => self::TYPE_STRING, ]; + /** * Quotes a table name for use in a query. * A simple table name has no schema prefix. diff --git a/framework/di/Instance.php b/framework/di/Instance.php index 19ce6f6..5bdf3b8 100644 --- a/framework/di/Instance.php +++ b/framework/di/Instance.php @@ -57,6 +57,7 @@ class Instance */ public $id; + /** * Constructor. * @param string $id the component ID diff --git a/framework/di/ServiceLocator.php b/framework/di/ServiceLocator.php index ea391e0..23d89cb 100644 --- a/framework/di/ServiceLocator.php +++ b/framework/di/ServiceLocator.php @@ -58,6 +58,7 @@ class ServiceLocator extends Component */ private $_definitions = []; + /** * Getter magic method. * This method is overridden to support accessing components like reading properties. diff --git a/framework/filters/AccessRule.php b/framework/filters/AccessRule.php index d831e2f..fa94671 100644 --- a/framework/filters/AccessRule.php +++ b/framework/filters/AccessRule.php @@ -90,6 +90,7 @@ class AccessRule extends Component */ public $denyCallback; + /** * Checks whether the Web user is allowed to perform the specified action. * @param Action $action the action to be performed diff --git a/framework/filters/Cors.php b/framework/filters/Cors.php index 1d0fbe8..d73a7b2 100644 --- a/framework/filters/Cors.php +++ b/framework/filters/Cors.php @@ -54,7 +54,6 @@ use yii\web\Response; * } * ``` * - * * @author Philippe Gaultier * @since 2.0 */ @@ -83,6 +82,7 @@ class Cors extends ActionFilter 'Access-Control-Max-Age' => 86400, ]; + /** * @inheritdoc */ diff --git a/framework/filters/VerbFilter.php b/framework/filters/VerbFilter.php index cafd294..b2cdb10 100644 --- a/framework/filters/VerbFilter.php +++ b/framework/filters/VerbFilter.php @@ -70,6 +70,7 @@ class VerbFilter extends Behavior */ public $actions = []; + /** * Declares event handlers for the [[owner]]'s events. * @return array events (array keys) and the corresponding event handler methods (array values). diff --git a/framework/filters/auth/HttpBearerAuth.php b/framework/filters/auth/HttpBearerAuth.php index 28ca561..f8fe5cd 100644 --- a/framework/filters/auth/HttpBearerAuth.php +++ b/framework/filters/auth/HttpBearerAuth.php @@ -36,6 +36,7 @@ class HttpBearerAuth extends AuthMethod */ public $realm = 'api'; + /** * @inheritdoc */ diff --git a/framework/filters/auth/QueryParamAuth.php b/framework/filters/auth/QueryParamAuth.php index d0066eb..25548a0 100644 --- a/framework/filters/auth/QueryParamAuth.php +++ b/framework/filters/auth/QueryParamAuth.php @@ -23,6 +23,7 @@ class QueryParamAuth extends AuthMethod */ public $tokenParam = 'access-token'; + /** * @inheritdoc */ diff --git a/framework/grid/CheckboxColumn.php b/framework/grid/CheckboxColumn.php index ddb30db..94035de 100644 --- a/framework/grid/CheckboxColumn.php +++ b/framework/grid/CheckboxColumn.php @@ -53,6 +53,7 @@ class CheckboxColumn extends Column */ public $multiple = true; + /** * @inheritdoc * @throws \yii\base\InvalidConfigException if [[name]] is not set. @@ -63,7 +64,7 @@ class CheckboxColumn extends Column if (empty($this->name)) { throw new InvalidConfigException('The "name" property must be set.'); } - if (substr($this->name, -2) !== '[]') { + if (substr_compare($this->name, '[]', -2)) { $this->name .= '[]'; } } diff --git a/framework/grid/GridView.php b/framework/grid/GridView.php index 79d426f..5945270 100644 --- a/framework/grid/GridView.php +++ b/framework/grid/GridView.php @@ -191,7 +191,6 @@ class GridView extends BaseListView * This is mainly used by [[Html::error()]] when rendering an error message next to every filter input field. */ public $filterErrorOptions = ['class' => 'help-block']; - /** * @var string the layout that determines how different sections of the list view should be organized. * The following tokens will be replaced with the corresponding section contents: @@ -204,6 +203,7 @@ class GridView extends BaseListView */ public $layout = "{summary}\n{items}\n{pager}"; + /** * Initializes the grid view. * This method will initialize required property values and instantiate [[columns]] objects. diff --git a/framework/grid/SerialColumn.php b/framework/grid/SerialColumn.php index 50a3f6b..c13b731 100644 --- a/framework/grid/SerialColumn.php +++ b/framework/grid/SerialColumn.php @@ -29,6 +29,7 @@ class SerialColumn extends Column { public $header = '#'; + /** * @inheritdoc */ diff --git a/framework/helpers/BaseConsole.php b/framework/helpers/BaseConsole.php index d0a8394..98f1659 100644 --- a/framework/helpers/BaseConsole.php +++ b/framework/helpers/BaseConsole.php @@ -453,6 +453,8 @@ class BaseConsole /** * Converts Markdown to be better readable in console environments by applying some ANSI format + * @param string $markdown + * @return string */ public static function markdownToAnsi($markdown) { diff --git a/framework/helpers/BaseFileHelper.php b/framework/helpers/BaseFileHelper.php index 54c7d6f..5bb52bc 100644 --- a/framework/helpers/BaseFileHelper.php +++ b/framework/helpers/BaseFileHelper.php @@ -27,6 +27,7 @@ class BaseFileHelper const PATTERN_MUSTBEDIR = 8; const PATTERN_NEGATIVE = 16; + /** * Normalizes a file/directory path. * The normalization does the following work: @@ -42,7 +43,7 @@ class BaseFileHelper */ public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR) { - $path = rtrim(strtr($path, ['/' => $ds, '\\' => $ds]), $ds); + $path = rtrim(strtr($path, '/\\', $ds . $ds), $ds); if (strpos($ds . $path, "{$ds}.") === false && strpos($path, "{$ds}{$ds}") === false) { return $path; } @@ -602,10 +603,8 @@ class BaseFileHelper $result['flags'] |= self::PATTERN_NEGATIVE; $pattern = StringHelper::byteSubstr($pattern, 1, StringHelper::byteLength($pattern)); } - $len = StringHelper::byteLength($pattern); - if ($len && StringHelper::byteSubstr($pattern, -1, 1) == '/') { + if (StringHelper::byteLength($pattern) && StringHelper::byteSubstr($pattern, -1, 1) == '/') { $pattern = StringHelper::byteSubstr($pattern, 0, -1); - $len--; $result['flags'] |= self::PATTERN_MUSTBEDIR; } if (strpos($pattern, '/') === false) { diff --git a/framework/helpers/BaseHtml.php b/framework/helpers/BaseHtml.php index 0e3dc82..87ef5dd 100644 --- a/framework/helpers/BaseHtml.php +++ b/framework/helpers/BaseHtml.php @@ -80,6 +80,7 @@ class BaseHtml 'media', ]; + /** * Encodes special characters into HTML entities. * The [[\yii\base\Application::charset|application charset]] will be used for encoding. @@ -797,13 +798,13 @@ class BaseHtml if (!array_key_exists('size', $options)) { $options['size'] = 4; } - if (!empty($options['multiple']) && substr($name, -2) !== '[]') { + if (!empty($options['multiple']) && !empty($name) && substr_compare($name, '[]', -2)) { $name .= '[]'; } $options['name'] = $name; if (isset($options['unselect'])) { // add a hidden field so that if the list box has no option being selected, it still submits a value - if (substr($name, -2) === '[]') { + if (!empty($name) && substr_compare($name, '[]', -2) === 0) { $name = substr($name, 0, -2); } $hidden = static::hiddenInput($name, $options['unselect']); diff --git a/framework/helpers/BaseInflector.php b/framework/helpers/BaseInflector.php index 30087d2..6c5ed02 100644 --- a/framework/helpers/BaseInflector.php +++ b/framework/helpers/BaseInflector.php @@ -216,7 +216,6 @@ class BaseInflector 'wildebeest' => 'wildebeest', 'Yengeese' => 'Yengeese', ]; - /** * @var array fallback map for transliteration used by [[slug()]] when intl isn't available. */ @@ -233,6 +232,7 @@ class BaseInflector 'ÿ' => 'y', ]; + /** * Converts a word to its plural form. * Note that this is for English only! @@ -326,7 +326,7 @@ class BaseInflector * For example, 'PostTag' will be converted to 'post-tag'. * @param string $name the string to be converted * @param string $separator the character used to concatenate the words in the ID - * @param string $strict whether to insert a separator between two consecutive uppercase chars, defaults to false + * @param boolean|string $strict whether to insert a separator between two consecutive uppercase chars, defaults to false * @return string the resulting ID */ public static function camel2id($name, $separator = '-', $strict = false) diff --git a/framework/helpers/BaseMarkdown.php b/framework/helpers/BaseMarkdown.php index e080fe3..8ceaf3c 100644 --- a/framework/helpers/BaseMarkdown.php +++ b/framework/helpers/BaseMarkdown.php @@ -45,6 +45,7 @@ class BaseMarkdown */ public static $defaultFlavor = 'original'; + /** * Converts markdown into HTML. * diff --git a/framework/helpers/BaseStringHelper.php b/framework/helpers/BaseStringHelper.php index 180d260..d04d0fb 100644 --- a/framework/helpers/BaseStringHelper.php +++ b/framework/helpers/BaseStringHelper.php @@ -34,13 +34,14 @@ class BaseStringHelper * This method ensures the string is treated as a byte array by using `mb_substr()`. * @param string $string the input string. Must be one character or longer. * @param integer $start the starting position - * @param integer $length the desired portion length + * @param integer $length the desired portion length. If not specified or `null`, there will be + * no limit on length i.e. the output will be until the end of the string. * @return string the extracted part of string, or FALSE on failure or an empty string. * @see http://www.php.net/manual/en/function.substr.php */ - public static function byteSubstr($string, $start, $length) + public static function byteSubstr($string, $start, $length = null) { - return mb_substr($string, $start, $length, '8bit'); + return mb_substr($string, $start, $length === null ? mb_strlen($string, '8bit') : $length, '8bit'); } /** diff --git a/framework/helpers/BaseVarDumper.php b/framework/helpers/BaseVarDumper.php index 2ed84fa..4006bcc 100644 --- a/framework/helpers/BaseVarDumper.php +++ b/framework/helpers/BaseVarDumper.php @@ -21,6 +21,7 @@ class BaseVarDumper private static $_output; private static $_depth; + /** * Displays a variable. * This method achieves the similar functionality as var_dump and print_r @@ -114,7 +115,7 @@ class BaseVarDumper $spaces = str_repeat(' ', $level * 4); self::$_output .= "$className#$id\n" . $spaces . '('; foreach ((array) $var as $key => $value) { - $keyDisplay = strtr(trim($key), ["\0" => ':']); + $keyDisplay = strtr(trim($key), "\0", ':'); self::$_output .= "\n" . $spaces . " [$keyDisplay] => "; self::dumpInternal($value, $level + 1); } diff --git a/framework/i18n/DbMessageSource.php b/framework/i18n/DbMessageSource.php index 33726f4..773aa9a 100644 --- a/framework/i18n/DbMessageSource.php +++ b/framework/i18n/DbMessageSource.php @@ -85,6 +85,7 @@ class DbMessageSource extends MessageSource */ public $enableCaching = false; + /** * Initializes the DbMessageSource component. * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. diff --git a/framework/i18n/Formatter.php b/framework/i18n/Formatter.php index 98f5287..592c755 100644 --- a/framework/i18n/Formatter.php +++ b/framework/i18n/Formatter.php @@ -79,6 +79,7 @@ class Formatter extends \yii\base\Formatter */ public $currencyCode; + /** * Initializes the component. * This method will check if the "intl" PHP extension is installed and set the diff --git a/framework/i18n/GettextMessageSource.php b/framework/i18n/GettextMessageSource.php index 4ddd2ba..97d0bc1 100644 --- a/framework/i18n/GettextMessageSource.php +++ b/framework/i18n/GettextMessageSource.php @@ -48,6 +48,7 @@ class GettextMessageSource extends MessageSource */ public $useBigEndian = false; + /** * Loads the message translation for the specified language and category. * If translation for specific locale code such as `en-US` isn't found it diff --git a/framework/i18n/GettextMoFile.php b/framework/i18n/GettextMoFile.php index 03beaca..300d486 100644 --- a/framework/i18n/GettextMoFile.php +++ b/framework/i18n/GettextMoFile.php @@ -48,6 +48,7 @@ class GettextMoFile extends GettextFile */ public $useBigEndian = false; + /** * Loads messages from an MO file. * @param string $filePath file path @@ -107,7 +108,8 @@ class GettextMoFile extends GettextFile $id = $this->readString($fileHandle, $sourceLengths[$i], $sourceOffsets[$i]); $separatorPosition = strpos($id, chr(4)); - if (($context && $separatorPosition !== false && substr($id, 0, $separatorPosition) === $context) || + + if (($context && $separatorPosition !== false && strncmp($id, $context, $separatorPosition) === 0) || (!$context && $separatorPosition === false)) { if ($separatorPosition !== false) { $id = substr($id, $separatorPosition+1); diff --git a/framework/i18n/I18N.php b/framework/i18n/I18N.php index 2a4ab6c..9ae858c 100644 --- a/framework/i18n/I18N.php +++ b/framework/i18n/I18N.php @@ -48,6 +48,7 @@ class I18N extends Component */ public $translations; + /** * Initializes the component by configuring the default message categories. */ diff --git a/framework/i18n/MessageFormatter.php b/framework/i18n/MessageFormatter.php index baea084..65285fa 100644 --- a/framework/i18n/MessageFormatter.php +++ b/framework/i18n/MessageFormatter.php @@ -47,6 +47,7 @@ class MessageFormatter extends Component private $_errorCode = 0; private $_errorMessage = ''; + /** * Get the error code from the last operation * @link http://php.net/manual/en/messageformatter.geterrorcode.php @@ -390,7 +391,8 @@ class MessageFormatter extends Component return false; } $selector = trim($plural[$i++]); - if ($i == 1 && substr($selector, 0, 7) == 'offset:') { + + if ($i == 1 && strncmp($selector, 'offset:', 7) === 0) { $offset = (int) trim(mb_substr($selector, 7, ($pos = mb_strpos(str_replace(["\n", "\r", "\t"], ' ', $selector), ' ', 7)) - 7)); $selector = trim(mb_substr($selector, $pos + 1)); } diff --git a/framework/i18n/MessageSource.php b/framework/i18n/MessageSource.php index f334d7b..34b5ce8 100644 --- a/framework/i18n/MessageSource.php +++ b/framework/i18n/MessageSource.php @@ -40,6 +40,7 @@ class MessageSource extends Component private $_messages = []; + /** * Initializes this component. */ diff --git a/framework/i18n/PhpMessageSource.php b/framework/i18n/PhpMessageSource.php index 573f01e..0c60f45 100644 --- a/framework/i18n/PhpMessageSource.php +++ b/framework/i18n/PhpMessageSource.php @@ -51,6 +51,7 @@ class PhpMessageSource extends MessageSource */ public $fileMap; + /** * Loads the message translation for the specified language and category. * If translation for specific locale code such as `en-US` isn't found it diff --git a/framework/log/Logger.php b/framework/log/Logger.php index f833ff5..f53eb78 100644 --- a/framework/log/Logger.php +++ b/framework/log/Logger.php @@ -74,7 +74,6 @@ class Logger extends Component */ const LEVEL_PROFILE_END = 0x60; - /** * @var array logged messages. This property is managed by [[log()]] and [[flush()]]. * Each log message is of the following structure: diff --git a/framework/log/SyslogTarget.php b/framework/log/SyslogTarget.php index 5813d61..9704e0b 100644 --- a/framework/log/SyslogTarget.php +++ b/framework/log/SyslogTarget.php @@ -22,7 +22,6 @@ class SyslogTarget extends Target * @var string syslog identity */ public $identity; - /** * @var integer syslog facility. */ diff --git a/framework/log/Target.php b/framework/log/Target.php index 45421aa..821e32d 100644 --- a/framework/log/Target.php +++ b/framework/log/Target.php @@ -105,7 +105,12 @@ abstract class Target extends Component if (($context = $this->getContextMessage()) !== '') { $this->messages[] = [$context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME]; } + // set exportInterval to 0 to avoid triggering export again while exporting + $oldExportInterval = $this->exportInterval; + $this->exportInterval = 0; $this->export(); + $this->exportInterval = $oldExportInterval; + $this->messages = []; } } @@ -199,7 +204,7 @@ abstract class Target extends Component $matched = empty($categories); foreach ($categories as $category) { - if ($message[2] === $category || substr($category, -1) === '*' && strpos($message[2], rtrim($category, '*')) === 0) { + if ($message[2] === $category || !empty($category) && substr_compare($category, '*', -1) === 0 && strpos($message[2], rtrim($category, '*')) === 0) { $matched = true; break; } diff --git a/framework/mail/BaseMailer.php b/framework/mail/BaseMailer.php index ea20069..40b5f74 100644 --- a/framework/mail/BaseMailer.php +++ b/framework/mail/BaseMailer.php @@ -39,6 +39,7 @@ abstract class BaseMailer extends Component implements MailerInterface, ViewCont * @event MailEvent an event raised right after send. */ const EVENT_AFTER_SEND = 'afterSend'; + /** * @var string|boolean HTML layout view name. This is the layout used to render HTML mail body. * The property can take the following values: @@ -105,6 +106,7 @@ abstract class BaseMailer extends Component implements MailerInterface, ViewCont */ private $_viewPath; + /** * @param array|View $view view instance or its array configuration that will be used to * render message bodies. diff --git a/framework/messages/config.php b/framework/messages/config.php index 299e2ef..53400b7 100644 --- a/framework/messages/config.php +++ b/framework/messages/config.php @@ -7,7 +7,7 @@ return [ 'messagePath' => __DIR__, // array, required, list of language codes that the extracted messages // should be translated to. For example, ['zh-CN', 'de']. - 'languages' => ['ar', 'bg', 'ca', 'da', 'de', 'el', 'es', 'et', 'fa-IR', 'fi', 'fr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt', 'lv', 'nl', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru', 'sk', 'sr', 'sr-Latn', 'th', 'uk', 'vi', 'zh-CN'], + 'languages' => ['ar', 'bg', 'ca', 'da', 'de', 'el', 'es', 'et', 'fa-IR', 'fi', 'fr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt', 'lv', 'nl', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru', 'sk', 'sr', 'sr-Latn', 'th', 'uk', 'vi', 'zh-CN','zh-TW'], // string, the name of the function for translating messages. // Defaults to 'Yii::t'. This is used as a mark to find the messages to be // translated. You may use a string for single function name or an array for diff --git a/framework/messages/ru/yii.php b/framework/messages/ru/yii.php index b957a6a..abdd53a 100644 --- a/framework/messages/ru/yii.php +++ b/framework/messages/ru/yii.php @@ -84,7 +84,7 @@ return [ 'the input value' => 'введённое значение', '{attribute} "{value}" has already been taken.' => '{attribute} «{value}» уже занят.', '{attribute} cannot be blank.' => 'Необходимо заполнить «{attribute}».', - '{attribute} is invalid.' => 'Значение «{attribute}» не верно.', + '{attribute} is invalid.' => 'Значение «{attribute}» неверно.', '{attribute} is not a valid URL.' => 'Значение «{attribute}» не является правильным URL.', '{attribute} is not a valid email address.' => 'Значение «{attribute}» не является правильным email адресом.', '{attribute} must be "{requiredValue}".' => 'Значение «{attribute}» должно быть равно «{requiredValue}».', diff --git a/framework/messages/zh-TW/yii.php b/framework/messages/zh-TW/yii.php new file mode 100644 index 0000000..b4cbc50 --- /dev/null +++ b/framework/messages/zh-TW/yii.php @@ -0,0 +1,80 @@ + '(未設定)', + 'An internal server error occurred.' => '內部系統錯誤。', + 'Are you sure you want to delete this item?' => '您確定要刪除此項嗎?', + 'Delete' => '刪除', + 'Error' => '錯誤', + 'File upload failed.' => '未能上傳檔案。', + 'Home' => '首頁', + 'Invalid data received for parameter "{param}".' => '"{param}" 參數資料不正確。', + 'Login Required' => '需要登入', + 'Missing required arguments: {params}' => '參數不齊全:{params}', + 'Missing required parameters: {params}' => '參數不齊全:{params}', + 'No' => '否', + 'No help for unknown command "{command}".' => '子命令 "{command}" 發生未知的錯誤。', + 'No help for unknown sub-command "{command}".' => '子命令 "{command}" 發生未知的錯誤。', + 'No results found.' => '沒有資料。', + 'Only files with these extensions are allowed: {extensions}.' => '只可以使用以下擴充名的檔案:{extensions}。', + 'Page not found.' => '找不到頁面。', + 'Please fix the following errors:' => '請修正以下錯誤:', + 'Please upload a file.' => '請上傳一個檔案。', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => '第 {begin, number}-{end, number} 項,共 {totalCount, number} 項資料.', + 'The file "{file}" is not an image.' => '檔案 "{file}" 不是一個圖片檔案。', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => '檔案 "{file}" 太大。它的大小不可以超過 {limit, number} 位元組。', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => '檔案 "{file}" 太小。它的大小不可以小於 {limit, number} 位元組。', + 'The format of {attribute} is invalid.' => '屬性 {attribute} 的格式不正確。', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '圖像 "{file}" 太大。它的高度不可以超過 {limit, number} 像素。', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '圖像 "{file}" 太大。它的寬度不可以超過 {limit, number} 像素。', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '圖像 "{file}" 太小。它的高度不可以小於 {limit, number} 像素。', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '圖像 "{file}" 太小。它的寬度不可以小於 {limit, number} 像素。', + 'The verification code is incorrect.' => '驗證碼不正確。', + 'Total {count, number} {count, plural, one{item} other{items}}.' => '總計 {count, number} 項資料。', + 'Unable to verify your data submission.' => '您提交的資料無法被驗證。', + 'Unknown command "{command}".' => '未知的指令 "{command}"。', + 'Unknown option: --{name}' => '未知的選項:--{name}', + 'Update' => '更新', + 'View' => '查看', + 'Yes' => '是', + 'You are not allowed to perform this action.' => '您沒有執行此操作的權限。', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => '您最多可以上載 {limit, number} 個檔案。', + 'the input value' => '該輸入', + '{attribute} "{value}" has already been taken.' => '{attribute} 的值 "{value}" 已經被佔用了。', + '{attribute} cannot be blank.' => '{attribute} 不能為空白。', + '{attribute} is invalid.' => '{attribute} 不正確。', + '{attribute} is not a valid URL.' => '{attribute} 不是一個正確的網址。', + '{attribute} is not a valid email address.' => '{attribute} 不是一個正確的電郵地址。', + '{attribute} must be "{requiredValue}".' => '{attribute}必須為 "{requiredValue}"。', + '{attribute} must be a number.' => '{attribute} 必須為數字。', + '{attribute} must be a string.' => '{attribute} 必須為字串。', + '{attribute} must be an integer.' => '{attribute} 必須為整數。', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} 必須為 "{true}" 或 "{false}"。', + '{attribute} must be greater than "{compareValue}".' => '{attribute} 必須大於 "{compareValue}"。', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} 必須大或等於 "{compareValue}"。', + '{attribute} must be less than "{compareValue}".' => '{attribute} 必須小於 "{compareValue}"。', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} 必須少或等於 "{compareValue}"。', + '{attribute} must be no greater than {max}.' => '{attribute} 不可以大於 {max}。', + '{attribute} must be no less than {min}.' => '{attribute} 不可以少於 {min}。', + '{attribute} must be repeated exactly.' => '{attribute} 必須重複一致。', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} 不可以等於 "{compareValue}"。', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} 應該包含至少 {min, number} 個字符。', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} 只能包含最多 {max, number} 個字符。', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} 應該包含 {length, number} 個字符。', +]; diff --git a/framework/mutex/DbMutex.php b/framework/mutex/DbMutex.php index 0321901..80daa1f 100644 --- a/framework/mutex/DbMutex.php +++ b/framework/mutex/DbMutex.php @@ -29,6 +29,7 @@ abstract class DbMutex extends Mutex */ public $db = 'db'; + /** * Initializes generic database table based mutex implementation. * @throws InvalidConfigException if [[db]] is invalid. diff --git a/framework/mutex/FileMutex.php b/framework/mutex/FileMutex.php index 0ee41e3..b58fe34 100644 --- a/framework/mutex/FileMutex.php +++ b/framework/mutex/FileMutex.php @@ -58,11 +58,13 @@ class FileMutex extends Mutex * but read-only for other users. */ public $dirMode = 0775; + /** * @var resource[] stores all opened lock files. Keys are lock names and values are file handles. */ private $_files = []; + /** * Initializes mutex component implementation dedicated for UNIX, GNU/Linux, Mac OS X, and other UNIX-like * operating systems. @@ -70,7 +72,7 @@ class FileMutex extends Mutex */ public function init() { - if (stripos(php_uname('s'), 'win') === 0) { + if (DIRECTORY_SEPARATOR === '\\') { throw new InvalidConfigException('FileMutex does not have MS Windows operating system support.'); } $this->mutexPath = Yii::getAlias($this->mutexPath); diff --git a/framework/mutex/Mutex.php b/framework/mutex/Mutex.php index 25e7fcd..31e94b1 100644 --- a/framework/mutex/Mutex.php +++ b/framework/mutex/Mutex.php @@ -38,11 +38,13 @@ abstract class Mutex extends Component * acquire in this process must be released in any case (regardless any kind of errors or exceptions). */ public $autoRelease = true; + /** * @var string[] names of the locks acquired in the current PHP process. */ private $_locks = []; + /** * Initializes the mutex component. */ diff --git a/framework/rbac/BaseManager.php b/framework/rbac/BaseManager.php index cd3dbaf..3ea53f9 100644 --- a/framework/rbac/BaseManager.php +++ b/framework/rbac/BaseManager.php @@ -24,6 +24,7 @@ abstract class BaseManager extends Component implements ManagerInterface */ public $defaultRoles = []; + /** * Returns the named auth item. * @param string $name the auth item name. diff --git a/framework/rbac/DbManager.php b/framework/rbac/DbManager.php index 7d3a44e..8b40476 100644 --- a/framework/rbac/DbManager.php +++ b/framework/rbac/DbManager.php @@ -41,22 +41,18 @@ class DbManager extends BaseManager * with a DB connection object. */ public $db = 'db'; - /** * @var string the name of the table storing authorization items. Defaults to "auth_item". */ public $itemTable = '{{%auth_item}}'; - /** * @var string the name of the table storing authorization item hierarchy. Defaults to "auth_item_child". */ public $itemChildTable = '{{%auth_item_child}}'; - /** * @var string the name of the table storing authorization item assignments. Defaults to "auth_assignment". */ public $assignmentTable = '{{%auth_assignment}}'; - /** * @var string the name of the table storing rules. Defaults to "auth_rule". */ @@ -70,7 +66,6 @@ class DbManager extends BaseManager public function init() { parent::init(); - $this->db = Instance::ensure($this->db, Connection::className()); } @@ -350,7 +345,7 @@ class DbManager extends BaseManager $query = (new Query)->select('b.*') ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable]) ->where('a.item_name=b.name') - ->andWhere(['a.user_id' => $userId]); + ->andWhere(['a.user_id' => (string)$userId]); $roles = []; foreach ($query->all($this->db) as $row) { @@ -388,7 +383,7 @@ class DbManager extends BaseManager { $query = (new Query)->select('item_name') ->from($this->assignmentTable) - ->where(['user_id' => $userId]); + ->where(['user_id' => (string)$userId]); $childrenList = $this->getChildrenList(); $result = []; @@ -475,7 +470,7 @@ class DbManager extends BaseManager public function getAssignment($roleName, $userId) { $row = (new Query)->from($this->assignmentTable) - ->where(['user_id' => $userId, 'item_name' => $roleName]) + ->where(['user_id' => (string)$userId, 'item_name' => $roleName]) ->one($this->db); if ($row === false) { @@ -496,7 +491,7 @@ class DbManager extends BaseManager { $query = (new Query) ->from($this->assignmentTable) - ->where(['user_id' => $userId]); + ->where(['user_id' => (string)$userId]); $assignments = []; foreach ($query->all($this->db) as $row) { @@ -595,7 +590,7 @@ class DbManager extends BaseManager /** * @inheritdoc */ - public function assign($role, $userId, $rule = null) + public function assign($role, $userId) { $assignment = new Assignment([ 'userId' => $userId, @@ -619,7 +614,7 @@ class DbManager extends BaseManager public function revoke($role, $userId) { return $this->db->createCommand() - ->delete($this->assignmentTable, ['user_id' => $userId, 'item_name' => $role->name]) + ->delete($this->assignmentTable, ['user_id' => (string)$userId, 'item_name' => $role->name]) ->execute() > 0; } @@ -629,7 +624,7 @@ class DbManager extends BaseManager public function revokeAll($userId) { return $this->db->createCommand() - ->delete($this->assignmentTable, ['user_id' => $userId]) + ->delete($this->assignmentTable, ['user_id' => (string)$userId]) ->execute() > 0; } diff --git a/framework/rbac/ManagerInterface.php b/framework/rbac/ManagerInterface.php index 8d61798..a97d5a4 100644 --- a/framework/rbac/ManagerInterface.php +++ b/framework/rbac/ManagerInterface.php @@ -165,12 +165,10 @@ interface ManagerInterface * * @param Role $role * @param string|integer $userId the user ID (see [[\yii\web\User::id]]) - * @param Rule $rule the rule to be associated with this assignment. If not null, the rule - * will be executed when [[allow()]] is called to check the user permission. * @return Assignment the role assignment information. * @throws \Exception if the role has already been assigned to the user */ - public function assign($role, $userId, $rule = null); + public function assign($role, $userId); /** * Revokes a role from a user. diff --git a/framework/rbac/PhpManager.php b/framework/rbac/PhpManager.php index a6ff2eb..6469804 100644 --- a/framework/rbac/PhpManager.php +++ b/framework/rbac/PhpManager.php @@ -47,7 +47,6 @@ class PhpManager extends BaseManager * @see saveToFile() */ public $assignmentFile = '@app/rbac/assignments.php'; - /** * @var string the path of the PHP script that contains the authorization rules. * This can be either a file path or a path alias to the file. @@ -56,6 +55,7 @@ class PhpManager extends BaseManager * @see saveToFile() */ public $ruleFile = '@app/rbac/rules.php'; + /** * @var Item[] */ @@ -223,7 +223,7 @@ class PhpManager extends BaseManager /** * @inheritdoc */ - public function assign($role, $userId, $ruleName = null) + public function assign($role, $userId) { if (!isset($this->items[$role->name])) { throw new InvalidParamException("Unknown role '{$role->name}'."); @@ -644,12 +644,14 @@ class PhpManager extends BaseManager } } - foreach ($assignments as $userId => $role) { - $this->assignments[$userId][$role] = new Assignment([ - 'userId' => $userId, - 'roleName' => $role, - 'createdAt' => $assignmentsMtime, - ]); + foreach ($assignments as $userId => $roles) { + foreach ($roles as $role) { + $this->assignments[$userId][$role] = new Assignment([ + 'userId' => $userId, + 'roleName' => $role, + 'createdAt' => $assignmentsMtime, + ]); + } } foreach ($rules as $name => $ruleData) { @@ -730,7 +732,7 @@ class PhpManager extends BaseManager foreach ($this->assignments as $userId => $assignments) { foreach ($assignments as $name => $assignment) { /* @var $assignment Assignment */ - $assignmentData[$userId] = $assignment->roleName; + $assignmentData[$userId][] = $assignment->roleName; } } $this->saveToFile($assignmentData, $this->assignmentFile); diff --git a/framework/rbac/Rule.php b/framework/rbac/Rule.php index 4a9dd54..55936ff 100644 --- a/framework/rbac/Rule.php +++ b/framework/rbac/Rule.php @@ -30,6 +30,7 @@ abstract class Rule extends Object */ public $updatedAt; + /** * Executes the rule. * diff --git a/framework/rbac/migrations/m140506_102106_rbac_init.php b/framework/rbac/migrations/m140506_102106_rbac_init.php index 28af9ac..f98209f 100644 --- a/framework/rbac/migrations/m140506_102106_rbac_init.php +++ b/framework/rbac/migrations/m140506_102106_rbac_init.php @@ -1,9 +1,20 @@ + * @since 2.0 + */ class m140506_102106_rbac_init extends \yii\db\Migration { /** diff --git a/framework/rbac/migrations/schema-mssql.sql b/framework/rbac/migrations/schema-mssql.sql index 4862011..5e1596f 100644 --- a/framework/rbac/migrations/schema-mssql.sql +++ b/framework/rbac/migrations/schema-mssql.sql @@ -9,10 +9,10 @@ * @since 2.0 */ -drop table if exists [auth_assignment]; -drop table if exists [auth_item_child]; -drop table if exists [auth_item]; -drop table if exists [auth_rule]; +drop table [auth_assignment]; +drop table [auth_item_child]; +drop table [auth_item]; +drop table [auth_rule]; create table [auth_rule] ( @@ -33,10 +33,11 @@ create table [auth_item] [created_at] integer, [updated_at] integer, primary key ([name]), - foreign key ([rule_name]) references [auth_rule] ([name]) on delete set null on update cascade, - key [type] ([type]) + foreign key ([rule_name]) references [auth_rule] ([name]) on delete set null on update cascade ); +create index [idx-auth_item-type] on [auth_item] ([type]); + create table [auth_item_child] ( [parent] varchar(64) not null, diff --git a/framework/rest/Action.php b/framework/rest/Action.php index f26adf9..7bacffd 100644 --- a/framework/rest/Action.php +++ b/framework/rest/Action.php @@ -56,6 +56,7 @@ class Action extends \yii\base\Action */ public $checkAccess; + /** * @inheritdoc */ diff --git a/framework/rest/ActiveController.php b/framework/rest/ActiveController.php index f87b888..bdef9ea 100644 --- a/framework/rest/ActiveController.php +++ b/framework/rest/ActiveController.php @@ -52,6 +52,7 @@ class ActiveController extends Controller */ public $createScenario = Model::SCENARIO_DEFAULT; + /** * @inheritdoc */ diff --git a/framework/rest/CreateAction.php b/framework/rest/CreateAction.php index af24808..209482e 100644 --- a/framework/rest/CreateAction.php +++ b/framework/rest/CreateAction.php @@ -28,6 +28,7 @@ class CreateAction extends Action */ public $viewAction = 'view'; + /** * Creates a new model. * @return \yii\db\ActiveRecordInterface the model newly created diff --git a/framework/rest/DeleteAction.php b/framework/rest/DeleteAction.php index 98e3c5a..7b70eb0 100644 --- a/framework/rest/DeleteAction.php +++ b/framework/rest/DeleteAction.php @@ -19,6 +19,7 @@ class DeleteAction extends Action { /** * Deletes a model. + * @param mixed $id id of the model to be deleted. */ public function run($id) { diff --git a/framework/rest/IndexAction.php b/framework/rest/IndexAction.php index fb27c52..3597d7f 100644 --- a/framework/rest/IndexAction.php +++ b/framework/rest/IndexAction.php @@ -31,6 +31,7 @@ class IndexAction extends Action */ public $prepareDataProvider; + /** * @return ActiveDataProvider */ diff --git a/framework/rest/OptionsAction.php b/framework/rest/OptionsAction.php index ad466c1..bf95fbe 100644 --- a/framework/rest/OptionsAction.php +++ b/framework/rest/OptionsAction.php @@ -26,6 +26,7 @@ class OptionsAction extends \yii\base\Action */ public $resourceOptions = ['GET', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']; + /** * Responds to the OPTIONS request. * @param string $id diff --git a/framework/rest/Serializer.php b/framework/rest/Serializer.php index 81ba651..aacf753 100644 --- a/framework/rest/Serializer.php +++ b/framework/rest/Serializer.php @@ -98,6 +98,7 @@ class Serializer extends Component */ public $response; + /** * @inheritdoc */ diff --git a/framework/rest/UpdateAction.php b/framework/rest/UpdateAction.php index 116530f..2de06e2 100644 --- a/framework/rest/UpdateAction.php +++ b/framework/rest/UpdateAction.php @@ -24,6 +24,7 @@ class UpdateAction extends Action */ public $scenario = Model::SCENARIO_DEFAULT; + /** * Updates an existing model. * @param string $id the primary key of the model. diff --git a/framework/rest/UrlRule.php b/framework/rest/UrlRule.php index e6edcba..565da5b 100644 --- a/framework/rest/UrlRule.php +++ b/framework/rest/UrlRule.php @@ -135,6 +135,7 @@ class UrlRule extends CompositeUrlRule */ public $pluralize = true; + /** * @inheritdoc */ diff --git a/framework/test/ActiveFixture.php b/framework/test/ActiveFixture.php index 780900c..e46126c 100644 --- a/framework/test/ActiveFixture.php +++ b/framework/test/ActiveFixture.php @@ -44,11 +44,13 @@ class ActiveFixture extends BaseActiveFixture * name of the table associated with this fixture. You can set this property to be false to prevent loading any data. */ public $dataFile; + /** * @var TableSchema the table schema for the table associated with this fixture */ private $_table; + /** * @inheritdoc */ diff --git a/framework/test/BaseActiveFixture.php b/framework/test/BaseActiveFixture.php index b35cd6a..64fc37d 100644 --- a/framework/test/BaseActiveFixture.php +++ b/framework/test/BaseActiveFixture.php @@ -34,11 +34,13 @@ abstract class BaseActiveFixture extends DbFixture implements \IteratorAggregate * to be returned by [[getData()]]. You can set this property to be false to prevent loading any data. */ public $dataFile; + /** * @var \yii\db\ActiveRecord[] the loaded AR models */ private $_models = []; + /** * Returns the AR model by the specified model name. * A model name is the key of the corresponding data row in [[data]]. diff --git a/framework/test/DbFixture.php b/framework/test/DbFixture.php index 01af892..b33f6e7 100644 --- a/framework/test/DbFixture.php +++ b/framework/test/DbFixture.php @@ -29,6 +29,7 @@ abstract class DbFixture extends Fixture */ public $db = 'db'; + /** * @inheritdoc */ diff --git a/framework/test/Fixture.php b/framework/test/Fixture.php index e7a4157..7c9255a 100644 --- a/framework/test/Fixture.php +++ b/framework/test/Fixture.php @@ -35,6 +35,7 @@ class Fixture extends Component */ public $depends = []; + /** * Loads the fixture. * This method is called before performing every test method. diff --git a/framework/test/FixtureTrait.php b/framework/test/FixtureTrait.php index ccef8b3..e67d12d 100644 --- a/framework/test/FixtureTrait.php +++ b/framework/test/FixtureTrait.php @@ -32,6 +32,7 @@ trait FixtureTrait */ private $_fixtures; + /** * Declares the fixtures that are needed by the current test case. * The return value of this method must be an array of fixture configurations. For example, diff --git a/framework/test/InitDbFixture.php b/framework/test/InitDbFixture.php index b399979..8b21da7 100644 --- a/framework/test/InitDbFixture.php +++ b/framework/test/InitDbFixture.php @@ -40,6 +40,7 @@ class InitDbFixture extends DbFixture */ public $schemas = ['']; + /** * @inheritdoc */ diff --git a/framework/validators/BooleanValidator.php b/framework/validators/BooleanValidator.php index d885909..2b902c8 100644 --- a/framework/validators/BooleanValidator.php +++ b/framework/validators/BooleanValidator.php @@ -35,6 +35,7 @@ class BooleanValidator extends Validator */ public $strict = false; + /** * @inheritdoc */ diff --git a/framework/validators/CompareValidator.php b/framework/validators/CompareValidator.php index 73abadd..b246690 100644 --- a/framework/validators/CompareValidator.php +++ b/framework/validators/CompareValidator.php @@ -68,6 +68,7 @@ class CompareValidator extends Validator */ public $message; + /** * @inheritdoc */ diff --git a/framework/validators/DateValidator.php b/framework/validators/DateValidator.php index 7819f80..fbcb711 100644 --- a/framework/validators/DateValidator.php +++ b/framework/validators/DateValidator.php @@ -31,6 +31,7 @@ class DateValidator extends Validator */ public $timestampAttribute; + /** * @inheritdoc */ diff --git a/framework/validators/DefaultValueValidator.php b/framework/validators/DefaultValueValidator.php index 528fa3b..0d80311 100644 --- a/framework/validators/DefaultValueValidator.php +++ b/framework/validators/DefaultValueValidator.php @@ -37,6 +37,7 @@ class DefaultValueValidator extends Validator */ public $skipOnEmpty = false; + /** * @inheritdoc */ diff --git a/framework/validators/EmailValidator.php b/framework/validators/EmailValidator.php index 27bddee..ee8c77e 100644 --- a/framework/validators/EmailValidator.php +++ b/framework/validators/EmailValidator.php @@ -50,6 +50,7 @@ class EmailValidator extends Validator */ public $enableIDN = false; + /** * @inheritdoc */ diff --git a/framework/validators/ExistValidator.php b/framework/validators/ExistValidator.php index 5b8b748..2bd1841 100644 --- a/framework/validators/ExistValidator.php +++ b/framework/validators/ExistValidator.php @@ -66,6 +66,7 @@ class ExistValidator extends Validator */ public $allowArray = false; + /** * @inheritdoc */ diff --git a/framework/validators/FileValidator.php b/framework/validators/FileValidator.php index dec0a46..9900a88 100644 --- a/framework/validators/FileValidator.php +++ b/framework/validators/FileValidator.php @@ -327,4 +327,61 @@ class FileValidator extends Validator return true; } + /** + * @inheritdoc + */ + public function clientValidateAttribute($object, $attribute, $view) { + $label = $object->getAttributeLabel($attribute); + + if ( $this->message !== null ){ + $options['message'] = Yii::$app->getI18n()->format($this->message, [ + 'attribute' => $label, + ], Yii::$app->language); + } + + $options['skipOnEmpty'] = $this->skipOnEmpty; + + if ( !$this->skipOnEmpty ) { + $options['uploadRequired'] = Yii::$app->getI18n()->format($this->uploadRequired, [], Yii::$app->language); + } + + if ( $this->mimeTypes !== null ) { + $options['mimeTypes'] = $this->mimeTypes; + $options['wrongMimeType'] = Yii::$app->getI18n()->format($this->wrongMimeType, [ + 'mimeTypes' => join(', ', $this->mimeTypes) + ], Yii::$app->language); + } + + if ( $this->extensions !== null ) { + $options['extensions'] = $this->extensions; + $options['wrongExtension'] = Yii::$app->getI18n()->format($this->wrongExtension, [ + 'extensions' => join(', ', $this->extensions) + ], Yii::$app->language); + } + + if ( $this->minSize !== null ) { + $options['minSize'] = $this->minSize; + $options['tooSmall'] = Yii::$app->getI18n()->format($this->tooSmall, [ + 'limit' => $this->minSize + ], Yii::$app->language); + } + + if ( $this->maxSize !== null ) { + $options['maxSize'] = $this->maxSize; + $options['tooBig'] = Yii::$app->getI18n()->format($this->tooBig, [ + 'limit' => $this->maxSize + ], Yii::$app->language); + } + + if ( $this->maxFiles !== null ) { + $options['maxFiles'] = $this->maxFiles; + $options['tooMany'] = Yii::$app->getI18n()->format($this->tooMany, [ + 'limit' => $this->maxFiles + ], Yii::$app->language); + } + + ValidationAsset::register($view); + + return 'yii.validation.file(value, messages, ' . json_encode($options) . ', attribute);'; + } } diff --git a/framework/validators/FilterValidator.php b/framework/validators/FilterValidator.php index ec70d45..edc171d 100644 --- a/framework/validators/FilterValidator.php +++ b/framework/validators/FilterValidator.php @@ -50,6 +50,7 @@ class FilterValidator extends Validator */ public $skipOnEmpty = false; + /** * @inheritdoc */ diff --git a/framework/validators/InlineValidator.php b/framework/validators/InlineValidator.php index 26bdc2d..0e5cd33 100644 --- a/framework/validators/InlineValidator.php +++ b/framework/validators/InlineValidator.php @@ -54,6 +54,7 @@ class InlineValidator extends Validator */ public $clientValidate; + /** * @inheritdoc */ diff --git a/framework/validators/NumberValidator.php b/framework/validators/NumberValidator.php index 0d008fc..e299e96 100644 --- a/framework/validators/NumberValidator.php +++ b/framework/validators/NumberValidator.php @@ -53,6 +53,7 @@ class NumberValidator extends Validator */ public $numberPattern = '/^\s*[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\s*$/'; + /** * @inheritdoc */ diff --git a/framework/validators/RangeValidator.php b/framework/validators/RangeValidator.php index 1c8482e..d58a8b8 100644 --- a/framework/validators/RangeValidator.php +++ b/framework/validators/RangeValidator.php @@ -40,6 +40,7 @@ class RangeValidator extends Validator */ public $allowArray = false; + /** * @inheritdoc */ diff --git a/framework/validators/RegularExpressionValidator.php b/framework/validators/RegularExpressionValidator.php index 8f31f1b..dee81ff 100644 --- a/framework/validators/RegularExpressionValidator.php +++ b/framework/validators/RegularExpressionValidator.php @@ -32,6 +32,7 @@ class RegularExpressionValidator extends Validator */ public $not = false; + /** * @inheritdoc */ diff --git a/framework/validators/RequiredValidator.php b/framework/validators/RequiredValidator.php index 957f15a..9f8a119 100644 --- a/framework/validators/RequiredValidator.php +++ b/framework/validators/RequiredValidator.php @@ -49,6 +49,7 @@ class RequiredValidator extends Validator */ public $message; + /** * @inheritdoc */ diff --git a/framework/validators/StringValidator.php b/framework/validators/StringValidator.php index 14a30eb..a75a56a 100644 --- a/framework/validators/StringValidator.php +++ b/framework/validators/StringValidator.php @@ -60,6 +60,7 @@ class StringValidator extends Validator */ public $encoding; + /** * @inheritdoc */ diff --git a/framework/validators/UniqueValidator.php b/framework/validators/UniqueValidator.php index 2a5b14d..9173d36 100644 --- a/framework/validators/UniqueValidator.php +++ b/framework/validators/UniqueValidator.php @@ -59,6 +59,7 @@ class UniqueValidator extends Validator */ public $filter; + /** * @inheritdoc */ diff --git a/framework/validators/Validator.php b/framework/validators/Validator.php index a351e77..b15d30d 100644 --- a/framework/validators/Validator.php +++ b/framework/validators/Validator.php @@ -81,7 +81,6 @@ class Validator extends Component 'unique' => 'yii\validators\UniqueValidator', 'url' => 'yii\validators\UrlValidator', ]; - /** * @var array|string attributes to be validated by this validator. For multiple attributes, * please specify them as an array; for single attribute, you may use either a string or an array. @@ -173,6 +172,7 @@ class Validator extends Component */ public $whenClient; + /** * Creates a validator object. * @param mixed $type the validator type. This can be a built-in validator name, @@ -181,6 +181,7 @@ class Validator extends Component * @param array|string $attributes list of attributes to be validated. This can be either an array of * the attribute names or a string of comma-separated attribute names. * @param array $params initial values to be applied to the validator properties + * @throws \yii\base\InvalidConfigException if type can't be recognized * @return Validator the validator */ public static function createValidator($type, $object, $attributes, $params = []) diff --git a/framework/views/messageConfig.php b/framework/views/messageConfig.php index 22fa10a..5afe7c6 100644 --- a/framework/views/messageConfig.php +++ b/framework/views/messageConfig.php @@ -15,8 +15,6 @@ return [ // with the existing ones. Defaults to false, which means the new (untranslated) // messages will be separated from the old (translated) ones. 'sort' => false, - // boolean, whether the message file should be overwritten with the merged messages - 'overwrite' => true, // boolean, whether to remove messages that no longer appear in the source code. // Defaults to false, which means each of these messages will be enclosed with a pair of '@@' marks. 'removeUnused' => false, @@ -47,6 +45,8 @@ return [ 'format' => 'php', // Root directory containing message translations. 'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . 'messages', + // boolean, whether the message file should be overwritten with the merged messages + 'overwrite' => true, /* @@ -67,5 +67,7 @@ return [ 'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . 'messages', // Name of the file that will be used for translations. 'catalog' => 'messages', + // boolean, whether the message file should be overwritten with the merged messages + 'overwrite' => true, */ ]; diff --git a/framework/web/Application.php b/framework/web/Application.php index ec58a0a..c2dc60a 100644 --- a/framework/web/Application.php +++ b/framework/web/Application.php @@ -50,6 +50,7 @@ class Application extends \yii\base\Application */ public $controller; + /** * @inheritdoc */ @@ -123,7 +124,7 @@ class Application extends \yii\base\Application /** * Returns the session component. - * @return Session the session component + * @return Session the session component. */ public function getSession() { @@ -132,7 +133,7 @@ class Application extends \yii\base\Application /** * Returns the user component. - * @return User the user component + * @return User the user component. */ public function getUser() { diff --git a/framework/web/AssetConverter.php b/framework/web/AssetConverter.php index 5f0f364..c1d74c2 100644 --- a/framework/web/AssetConverter.php +++ b/framework/web/AssetConverter.php @@ -35,6 +35,7 @@ class AssetConverter extends Component implements AssetConverterInterface 'ts' => ['js', 'tsc --out {to} {from}'], ]; + /** * Converts a given asset file into a CSS or JS file. * @param string $asset the asset file path, relative to $basePath diff --git a/framework/web/CacheSession.php b/framework/web/CacheSession.php index 82fed10..00ebae7 100644 --- a/framework/web/CacheSession.php +++ b/framework/web/CacheSession.php @@ -47,6 +47,7 @@ class CacheSession extends Session */ public $cache = 'cache'; + /** * Initializes the application component. */ diff --git a/framework/web/CompositeUrlRule.php b/framework/web/CompositeUrlRule.php index 6e8b758..997a0ae 100644 --- a/framework/web/CompositeUrlRule.php +++ b/framework/web/CompositeUrlRule.php @@ -24,6 +24,7 @@ abstract class CompositeUrlRule extends Object implements UrlRuleInterface */ protected $rules = []; + /** * Creates the URL rules that should be contained within this composite rule. * @return UrlRuleInterface[] the URL rules diff --git a/framework/web/Controller.php b/framework/web/Controller.php index 82deb92..24df5b6 100644 --- a/framework/web/Controller.php +++ b/framework/web/Controller.php @@ -29,6 +29,7 @@ class Controller extends \yii\base\Controller */ public $actionParams = []; + /** * Renders a view in response to an AJAX request. * diff --git a/framework/web/Cookie.php b/framework/web/Cookie.php index b48b370..924eb02 100644 --- a/framework/web/Cookie.php +++ b/framework/web/Cookie.php @@ -47,6 +47,7 @@ class Cookie extends \yii\base\Object */ public $httpOnly = true; + /** * Magic method to turn a cookie object into a string without having to explicitly access [[value]]. * diff --git a/framework/web/CookieCollection.php b/framework/web/CookieCollection.php index ea7f926..88de86b 100644 --- a/framework/web/CookieCollection.php +++ b/framework/web/CookieCollection.php @@ -34,6 +34,7 @@ class CookieCollection extends Object implements \IteratorAggregate, \ArrayAcces */ private $_cookies = []; + /** * Constructor. * @param array $cookies the cookies that this collection initially contains. This should be diff --git a/framework/web/DbSession.php b/framework/web/DbSession.php index a8dac63..96fa8b2 100644 --- a/framework/web/DbSession.php +++ b/framework/web/DbSession.php @@ -68,6 +68,7 @@ class DbSession extends Session */ public $sessionTable = '{{%session}}'; + /** * Initializes the DbSession component. * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. diff --git a/framework/web/ErrorAction.php b/framework/web/ErrorAction.php index a4cc7de..46d5543 100644 --- a/framework/web/ErrorAction.php +++ b/framework/web/ErrorAction.php @@ -66,6 +66,12 @@ class ErrorAction extends Action */ public $defaultMessage; + + /** + * Runs the action + * + * @return string result content + */ public function run() { if (($exception = Yii::$app->getErrorHandler()->exception) === null) { diff --git a/framework/web/HtmlResponseFormatter.php b/framework/web/HtmlResponseFormatter.php index 623cab3..8bc2eaf 100644 --- a/framework/web/HtmlResponseFormatter.php +++ b/framework/web/HtmlResponseFormatter.php @@ -24,6 +24,7 @@ class HtmlResponseFormatter extends Component implements ResponseFormatterInterf */ public $contentType = 'text/html'; + /** * Formats the specified response. * @param Response $response the response to be formatted. diff --git a/framework/web/HttpException.php b/framework/web/HttpException.php index 53ecd00..f8b73cb 100644 --- a/framework/web/HttpException.php +++ b/framework/web/HttpException.php @@ -34,6 +34,7 @@ class HttpException extends UserException */ public $statusCode; + /** * Constructor. * @param integer $status HTTP status code, such as 404, 500, etc. diff --git a/framework/web/JsExpression.php b/framework/web/JsExpression.php index 61cbce3..088e752 100644 --- a/framework/web/JsExpression.php +++ b/framework/web/JsExpression.php @@ -25,6 +25,7 @@ class JsExpression extends Object */ public $expression; + /** * Constructor. * @param string $expression the JavaScript expression represented by this object diff --git a/framework/web/JsonParser.php b/framework/web/JsonParser.php index 04508ca..8daa4f6 100644 --- a/framework/web/JsonParser.php +++ b/framework/web/JsonParser.php @@ -37,6 +37,7 @@ class JsonParser implements RequestParserInterface */ public $throwException = true; + /** * Parses a HTTP request body. * @param string $rawBody the raw HTTP request body. diff --git a/framework/web/JsonResponseFormatter.php b/framework/web/JsonResponseFormatter.php index 7953517..fececc1 100644 --- a/framework/web/JsonResponseFormatter.php +++ b/framework/web/JsonResponseFormatter.php @@ -28,6 +28,7 @@ class JsonResponseFormatter extends Component implements ResponseFormatterInterf */ public $useJsonp = false; + /** * Formats the specified response. * @param Response $response the response to be formatted. diff --git a/framework/web/Link.php b/framework/web/Link.php index 43c723f..0403f04 100644 --- a/framework/web/Link.php +++ b/framework/web/Link.php @@ -52,6 +52,7 @@ class Link extends Object */ public $hreflang; + /** * Serializes a list of links into proper array format. * @param array $links the links to be serialized diff --git a/framework/web/Request.php b/framework/web/Request.php index 316fe80..17bae3d 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -75,7 +75,8 @@ use yii\helpers\StringHelper; * @property string $url The currently requested relative URL. Note that the URI returned is URL-encoded. * @property string $userAgent User agent, null if not present. This property is read-only. * @property string $userHost User host name, null if cannot be determined. This property is read-only. - * @property string $userIP User IP address. This property is read-only. + * @property string $userIP User IP address. Null is returned if the user IP address cannot be detected. This + * property is read-only. * * @author Qiang Xue * @since 2.0 @@ -163,6 +164,7 @@ class Request extends \yii\base\Request */ private $_headers; + /** * Resolves the current request into a route and the associated parameters. * @return array the first element is the route, and the second is the associated parameters. @@ -1228,12 +1230,11 @@ class Request extends \yii\base\Request { if ($this->_csrfCookie === null) { $this->_csrfCookie = $this->getCookies()->get($this->csrfParam); - if ($this->_csrfCookie === null) { + if ($this->_csrfCookie === null || empty($this->_csrfCookie->value)) { $this->_csrfCookie = $this->createCsrfCookie(); Yii::$app->getResponse()->getCookies()->add($this->_csrfCookie); } } - return $this->_csrfCookie->value; } @@ -1277,7 +1278,7 @@ class Request extends \yii\base\Request if ($n1 > $n2) { $token2 = str_pad($token2, $n1, $token2); } elseif ($n1 < $n2) { - $token1 = str_pad($token1, $n2, $token1); + $token1 = str_pad($token1, $n2, $n1 === 0 ? ' ' : $token1); } return $token1 ^ $token2; @@ -1289,7 +1290,6 @@ class Request extends \yii\base\Request public function getCsrfTokenFromHeader() { $key = 'HTTP_' . str_replace('-', '_', strtoupper(self::CSRF_HEADER)); - return isset($_SERVER[$key]) ? $_SERVER[$key] : null; } @@ -1303,8 +1303,7 @@ class Request extends \yii\base\Request { $options = $this->csrfCookie; $options['name'] = $this->csrfParam; - $options['value'] = Yii::$app->getSecurity()->generateRandomKey(); - + $options['value'] = Yii::$app->getSecurity()->generateRandomString(); return new Cookie($options); } @@ -1329,6 +1328,13 @@ class Request extends \yii\base\Request || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken); } + /** + * Validates CSRF token + * + * @param string $token + * @param string $trueToken + * @return boolean + */ private function validateCsrfTokenInternal($token, $trueToken) { $token = base64_decode(str_replace('.', '+', $token)); diff --git a/framework/web/Response.php b/framework/web/Response.php index adc43a2..bffc488 100644 --- a/framework/web/Response.php +++ b/framework/web/Response.php @@ -235,6 +235,7 @@ class Response extends \yii\base\Response */ private $_headers; + /** * Initializes this component. */ diff --git a/framework/web/Session.php b/framework/web/Session.php index 5e06b79..bccb0c1 100644 --- a/framework/web/Session.php +++ b/framework/web/Session.php @@ -79,6 +79,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co * @var \SessionHandlerInterface|array an object implementing the SessionHandlerInterface or a configuration array. If set, will be used to provide persistency instead of build-in methods. */ public $handler; + /** * @var array parameter-value pairs to override default session cookie parameters that are used for session_set_cookie_params() function * Array may have the following possible keys: 'lifetime', 'path', 'domain', 'secure', 'httponly' @@ -86,6 +87,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co */ private $_cookieParams = ['httponly' => true]; + /** * Initializes the application component. * This method is required by IApplicationComponent and is invoked by application. diff --git a/framework/web/SessionIterator.php b/framework/web/SessionIterator.php index 7e4c9c0..69911d3 100644 --- a/framework/web/SessionIterator.php +++ b/framework/web/SessionIterator.php @@ -24,6 +24,7 @@ class SessionIterator implements \Iterator */ private $_key; + /** * Constructor. */ diff --git a/framework/web/UploadedFile.php b/framework/web/UploadedFile.php index 9e00ec0..39f196d 100644 --- a/framework/web/UploadedFile.php +++ b/framework/web/UploadedFile.php @@ -28,8 +28,6 @@ use yii\helpers\Html; */ class UploadedFile extends Object { - private static $_files; - /** * @var string the original name of the file being uploaded */ @@ -56,6 +54,8 @@ class UploadedFile extends Object */ public $error; + private static $_files; + /** * String output. diff --git a/framework/web/UrlManager.php b/framework/web/UrlManager.php index 3aeb23f..76ef2bc 100644 --- a/framework/web/UrlManager.php +++ b/framework/web/UrlManager.php @@ -31,9 +31,11 @@ use yii\caching\Cache; * ] * ~~~ * - * @property string $baseUrl The base URL that is used by [[createUrl()]] to prepend URLs it creates. + * @property string $baseUrl The base URL that is used by [[createUrl()]] to prepend to created URLs. * @property string $hostInfo The host info (e.g. "http://www.example.com") that is used by - * [[createAbsoluteUrl()]] to prepend URLs it creates. + * [[createAbsoluteUrl()]] to prepend to created URLs. + * @property string $scriptUrl The entry script URL that is used by [[createUrl()]] to prepend to created + * URLs. * * @author Qiang Xue * @since 2.0 @@ -128,6 +130,7 @@ class UrlManager extends Component private $_scriptUrl; private $_hostInfo; + /** * Initializes UrlManager. */ @@ -238,7 +241,7 @@ class UrlManager extends Component $suffix = (string) $this->suffix; if ($suffix !== '' && $pathInfo !== '') { $n = strlen($this->suffix); - if (substr($pathInfo, -$n) === $this->suffix) { + if (substr_compare($pathInfo, $this->suffix, -$n) === 0) { $pathInfo = substr($pathInfo, 0, -$n); if ($pathInfo === '') { // suffix alone is not allowed @@ -366,10 +369,10 @@ class UrlManager extends Component } /** - * Returns the base URL that is used by [[createUrl()]] to prepend to the URLs it creates. + * Returns the base URL that is used by [[createUrl()]] to prepend to created URLs. * It defaults to [[Request::baseUrl]]. * This is mainly used when [[enablePrettyUrl]] is true and [[showScriptName]] is false. - * @return string the base URL that is used by [[createUrl()]] to prepend to the URLs it creates. + * @return string the base URL that is used by [[createUrl()]] to prepend to created URLs. * @throws InvalidConfigException if running in console application and [[baseUrl]] is not configured. */ public function getBaseUrl() @@ -387,9 +390,9 @@ class UrlManager extends Component } /** - * Sets the base URL that is used by [[createUrl()]] to prepend to the URLs it creates. + * Sets the base URL that is used by [[createUrl()]] to prepend to created URLs. * This is mainly used when [[enablePrettyUrl]] is true and [[showScriptName]] is false. - * @param string $value the base URL that is used by [[createUrl()]] to prepend URLs it creates. + * @param string $value the base URL that is used by [[createUrl()]] to prepend to created URLs. */ public function setBaseUrl($value) { @@ -397,10 +400,10 @@ class UrlManager extends Component } /** - * Returns the entry script URL that is used by [[createUrl()]] to prepend to the URLs it creates. + * Returns the entry script URL that is used by [[createUrl()]] to prepend to created URLs. * It defaults to [[Request::scriptUrl]]. * This is mainly used when [[enablePrettyUrl]] is false or [[showScriptName]] is true. - * @return string the entry script URL that is used by [[createUrl()]] to prepend to the URLs it creates. + * @return string the entry script URL that is used by [[createUrl()]] to prepend to created URLs. * @throws InvalidConfigException if running in console application and [[scriptUrl]] is not configured. */ public function getScriptUrl() @@ -418,9 +421,9 @@ class UrlManager extends Component } /** - * Sets the entry script URL that is used by [[createUrl()]] to prepend to the URLs it creates. + * Sets the entry script URL that is used by [[createUrl()]] to prepend to created URLs. * This is mainly used when [[enablePrettyUrl]] is false or [[showScriptName]] is true. - * @param string $value the entry script URL that is used by [[createUrl()]] to prepend URLs it creates. + * @param string $value the entry script URL that is used by [[createUrl()]] to prepend to created URLs. */ public function setScriptUrl($value) { @@ -428,8 +431,8 @@ class UrlManager extends Component } /** - * Returns the host info that is used by [[createAbsoluteUrl()]] to prepend URLs it creates. - * @return string the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend URLs it creates. + * Returns the host info that is used by [[createAbsoluteUrl()]] to prepend to created URLs. + * @return string the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend to created URLs. * @throws InvalidConfigException if running in console application and [[hostInfo]] is not configured. */ public function getHostInfo() @@ -447,8 +450,8 @@ class UrlManager extends Component } /** - * Sets the host info that is used by [[createAbsoluteUrl()]] to prepend URLs it creates. - * @param string $value the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend URLs it creates. + * Sets the host info that is used by [[createAbsoluteUrl()]] to prepend to created URLs. + * @param string $value the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend to created URLs. */ public function setHostInfo($value) { diff --git a/framework/web/UrlRule.php b/framework/web/UrlRule.php index fdb0ddf..18d5d32 100644 --- a/framework/web/UrlRule.php +++ b/framework/web/UrlRule.php @@ -215,7 +215,7 @@ class UrlRule extends Object implements UrlRuleInterface $suffix = (string) ($this->suffix === null ? $manager->suffix : $this->suffix); if ($suffix !== '' && $pathInfo !== '') { $n = strlen($suffix); - if (substr($pathInfo, -$n) === $suffix) { + if (substr_compare($pathInfo, $suffix, -$n) === 0) { $pathInfo = substr($pathInfo, 0, -$n); if ($pathInfo === '') { // suffix alone is not allowed diff --git a/framework/web/User.php b/framework/web/User.php index e1ef9ca..9257f8f 100644 --- a/framework/web/User.php +++ b/framework/web/User.php @@ -138,6 +138,7 @@ class User extends Component private $_access = []; + /** * Initializes the application component. */ diff --git a/framework/web/View.php b/framework/web/View.php index 5438ef1..cccfd1d 100644 --- a/framework/web/View.php +++ b/framework/web/View.php @@ -49,7 +49,6 @@ class View extends \yii\base\View * @event Event an event that is triggered by [[endBody()]]. */ const EVENT_END_BODY = 'endBody'; - /** * The location of registered JavaScript code block or files. * This means the location is in the head section. @@ -131,6 +130,7 @@ class View extends \yii\base\View private $_assetManager; + /** * Marks the position of an HTML head section. */ diff --git a/framework/web/ViewAction.php b/framework/web/ViewAction.php index 401da0c..8c6a1a6 100644 --- a/framework/web/ViewAction.php +++ b/framework/web/ViewAction.php @@ -34,7 +34,6 @@ class ViewAction extends Action * @var string the name of the GET parameter that contains the requested view name. */ public $viewParam = 'view'; - /** * @var string the name of the default view when [[\yii\web\ViewAction::$viewParam]] GET parameter is not provided * by user. Defaults to 'index'. This should be in the format of 'path/to/view', similar to that given in the @@ -42,7 +41,6 @@ class ViewAction extends Action * @see \yii\web\ViewAction::$viewPrefix */ public $defaultView = 'index'; - /** * @var string a string to be prefixed to the user-specified view name to form a complete view name. * For example, if a user requests for `tutorial/chap1`, the corresponding view name will @@ -51,7 +49,6 @@ class ViewAction extends Action * @see \yii\base\View::findViewFile() */ public $viewPrefix = 'pages'; - /** * @var mixed the name of the layout to be applied to the requested view. * This will be assigned to [[\yii\base\Controller::$layout]] before the view is rendered. @@ -60,6 +57,7 @@ class ViewAction extends Action */ public $layout; + /** * Runs the action. * This method displays the view requested by the user. diff --git a/framework/web/XmlResponseFormatter.php b/framework/web/XmlResponseFormatter.php index b9766bc..507810e 100644 --- a/framework/web/XmlResponseFormatter.php +++ b/framework/web/XmlResponseFormatter.php @@ -45,6 +45,7 @@ class XmlResponseFormatter extends Component implements ResponseFormatterInterfa */ public $itemTag = 'item'; + /** * Formats the specified response. * @param Response $response the response to be formatted. diff --git a/framework/widgets/ActiveField.php b/framework/widgets/ActiveField.php index 956e0d9..636db97 100644 --- a/framework/widgets/ActiveField.php +++ b/framework/widgets/ActiveField.php @@ -703,7 +703,7 @@ class ActiveField extends Component } } if (!empty($validators)) { - $options['validate'] = new JsExpression("function (attribute, value, messages) {" . implode('', $validators) . '}'); + $options['validate'] = new JsExpression("function (attribute, value, messages, deferred) {" . implode('', $validators) . '}'); } } diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php index 5794d8a..320d553 100644 --- a/framework/widgets/ActiveForm.php +++ b/framework/widgets/ActiveForm.php @@ -152,12 +152,33 @@ class ActiveForm extends Widget */ public $afterValidate; /** + * @var string|JsExpression a JS pre-request callback function on AJAX-based validation. + * The signature of the callback should be: + * + * ~~~ + * function ($form, jqXHR, textStatus) { + * } + * ~~~ + */ + public $ajaxBeforeSend; + /** + * @var string|JsExpression a JS callback to be called when the request finishes on AJAX-based validation. + * The signature of the callback should be: + * + * ~~~ + * function ($form, jqXHR, textStatus) { + * } + * ~~~ + */ + public $ajaxComplete; + /** * @var array the client validation options for individual attributes. Each element of the array * represents the validation options for a particular attribute. * @internal */ public $attributes = []; + /** * Initializes the widget. * This renders the form open tag. @@ -208,7 +229,7 @@ class ActiveForm extends Widget if ($this->validationUrl !== null) { $options['validationUrl'] = Url::to($this->validationUrl); } - foreach (['beforeSubmit', 'beforeValidate', 'afterValidate'] as $name) { + foreach (['beforeSubmit', 'beforeValidate', 'afterValidate', 'ajaxBeforeSend', 'ajaxComplete'] as $name) { if (($value = $this->$name) !== null) { $options[$name] = $value instanceof JsExpression ? $value : new JsExpression($value); } diff --git a/framework/widgets/BaseListView.php b/framework/widgets/BaseListView.php index 213f20b..3dc5bc7 100644 --- a/framework/widgets/BaseListView.php +++ b/framework/widgets/BaseListView.php @@ -84,6 +84,7 @@ abstract class BaseListView extends Widget */ public $layout = "{summary}\n{items}\n{pager}"; + /** * Renders the data models. * @return string the rendering result. diff --git a/framework/widgets/Block.php b/framework/widgets/Block.php index c1813ff..ea96619 100644 --- a/framework/widgets/Block.php +++ b/framework/widgets/Block.php @@ -21,6 +21,7 @@ class Block extends Widget */ public $renderInPlace = false; + /** * Starts recording a block. */ diff --git a/framework/widgets/Breadcrumbs.php b/framework/widgets/Breadcrumbs.php index c301946..0cdc1f4 100644 --- a/framework/widgets/Breadcrumbs.php +++ b/framework/widgets/Breadcrumbs.php @@ -101,6 +101,7 @@ class Breadcrumbs extends Widget */ public $activeItemTemplate = "
  • {link}
  • \n"; + /** * Renders the widget. */ diff --git a/framework/widgets/ContentDecorator.php b/framework/widgets/ContentDecorator.php index 160cd84..2ab9e72 100644 --- a/framework/widgets/ContentDecorator.php +++ b/framework/widgets/ContentDecorator.php @@ -26,6 +26,7 @@ class ContentDecorator extends Widget */ public $params = []; + /** * Starts recording a clip. */ diff --git a/framework/widgets/DetailView.php b/framework/widgets/DetailView.php index 350f65f..3e9ffa9 100644 --- a/framework/widgets/DetailView.php +++ b/framework/widgets/DetailView.php @@ -101,6 +101,7 @@ class DetailView extends Widget */ public $formatter; + /** * Initializes the detail view. * This method will initialize required property values. diff --git a/framework/widgets/FragmentCache.php b/framework/widgets/FragmentCache.php index 910b1b6..94f9f62 100644 --- a/framework/widgets/FragmentCache.php +++ b/framework/widgets/FragmentCache.php @@ -73,6 +73,7 @@ class FragmentCache extends Widget */ public $dynamicPlaceholders; + /** * Initializes the FragmentCache object. */ @@ -150,6 +151,13 @@ class FragmentCache extends Widget return $this->_content; } + /** + * Replaces placeholders in content by results of evaluated dynamic statemens + * + * @param string $content + * @param array $placeholders + * @return string final content + */ protected function updateDynamicContent($content, $placeholders) { foreach ($placeholders as $name => $statements) { diff --git a/framework/widgets/InputWidget.php b/framework/widgets/InputWidget.php index 197b8b0..50bcd9e 100644 --- a/framework/widgets/InputWidget.php +++ b/framework/widgets/InputWidget.php @@ -47,6 +47,7 @@ class InputWidget extends Widget */ public $options = []; + /** * Initializes the widget. * If you override this method, make sure you call the parent implementation first. diff --git a/framework/widgets/LinkPager.php b/framework/widgets/LinkPager.php index 86ca762..9298c08 100644 --- a/framework/widgets/LinkPager.php +++ b/framework/widgets/LinkPager.php @@ -103,6 +103,7 @@ class LinkPager extends Widget */ public $hideOnSinglePage = true; + /** * Initializes the pager. */ diff --git a/framework/widgets/LinkSorter.php b/framework/widgets/LinkSorter.php index 30706dc..7d9ca8f 100644 --- a/framework/widgets/LinkSorter.php +++ b/framework/widgets/LinkSorter.php @@ -39,6 +39,7 @@ class LinkSorter extends Widget */ public $options = ['class' => 'sorter']; + /** * Initializes the sorter. */ diff --git a/framework/widgets/ListView.php b/framework/widgets/ListView.php index 76a52e3..107c6b6 100644 --- a/framework/widgets/ListView.php +++ b/framework/widgets/ListView.php @@ -60,6 +60,7 @@ class ListView extends BaseListView */ public $options = ['class' => 'list-view']; + /** * Renders all data models. * @return string the rendering result diff --git a/framework/widgets/Pjax.php b/framework/widgets/Pjax.php index 8039afd..35e37f9 100644 --- a/framework/widgets/Pjax.php +++ b/framework/widgets/Pjax.php @@ -85,6 +85,7 @@ class Pjax extends Widget */ public $clientOptions; + /** * @inheritdoc */ diff --git a/tests/unit/data/travis/sphinx-setup.sh b/tests/unit/data/travis/sphinx-setup.sh index c07bd04..ba09595 100755 --- a/tests/unit/data/travis/sphinx-setup.sh +++ b/tests/unit/data/travis/sphinx-setup.sh @@ -1,13 +1,7 @@ #!/bin/sh - SCRIPT=$(readlink -f "$0") CWD=$(dirname "$SCRIPT") -# install sphinxsearch: -echo 'yes' | sudo add-apt-repository ppa:builds/sphinxsearch-daily -sudo apt-get update -sudo apt-get install sphinxsearch - # log files sudo mkdir /var/log/sphinx sudo touch /var/log/sphinx/searchd.log diff --git a/tests/unit/extensions/gii/GeneratorsTest.php b/tests/unit/extensions/gii/GeneratorsTest.php new file mode 100644 index 0000000..f8cb16c --- /dev/null +++ b/tests/unit/extensions/gii/GeneratorsTest.php @@ -0,0 +1,105 @@ +template = 'default'; + $generator->controller = 'test'; + + if ($generator->validate()) { + $generator->generate(); + } else { + print_r($generator->getErrors()); + } + } + + public function testExtensionGenerator() + { + $generator = new ExtensionGenerator(); + $generator->template = 'default'; + $generator->vendorName = 'samdark'; + $generator->namespace = 'samdark\\'; + $generator->license = 'BSD'; + $generator->title = 'Sample extension'; + $generator->description = 'This is sample description.'; + $generator->authorName = 'Alexander Makarov'; + $generator->authorEmail = 'sam@rmcreative.ru'; + + if ($generator->validate()) { + $generator->generate(); + } else { + print_r($generator->getErrors()); + } + } + + public function testModelGenerator() + { + $generator = new ModelGenerator(); + $generator->template = 'default'; + $generator->tableName = 'profile'; + $generator->modelClass = 'Profile'; + + if ($generator->validate()) { + $generator->generate(); + } else { + print_r($generator->getErrors()); + } + } + + public function testModuleGenerator() + { + $generator = new ModuleGenerator(); + $generator->template = 'default'; + $generator->moduleID = 'test'; + $generator->moduleClass = 'app\modules\test\Module'; + + if ($generator->validate()) { + $generator->generate(); + } else { + print_r($generator->getErrors()); + } + } + + + public function testFormGenerator() + { + $generator = new FormGenerator(); + $generator->template = 'default'; + $generator->modelClass = 'yiiunit\extensions\gii\Profile'; + $generator->viewName = 'profile'; + $generator->viewPath = '@yiiunit/runtime'; + + if ($generator->validate()) { + $generator->generate(); + } else { + print_r($generator->getErrors()); + } + } + + public function testCRUDGenerator() + { + $generator = new CRUDGenerator(); + $generator->template = 'default'; + $generator->modelClass = 'yiiunit\extensions\gii\Profile'; + $generator->controllerClass = 'app\TestController'; + + if ($generator->validate()) { + $generator->generate(); + } else { + print_r($generator->getErrors()); + } + } +} \ No newline at end of file diff --git a/tests/unit/extensions/gii/GiiTestCase.php b/tests/unit/extensions/gii/GiiTestCase.php new file mode 100644 index 0000000..291065b --- /dev/null +++ b/tests/unit/extensions/gii/GiiTestCase.php @@ -0,0 +1,49 @@ +driverName]; + $pdo_database = 'pdo_'.$this->driverName; + + if (!extension_loaded('pdo') || !extension_loaded($pdo_database)) { + $this->markTestSkipped('pdo and '.$pdo_database.' extension are required.'); + } + + $this->mockApplication([ + 'components' => [ + 'db' => [ + 'class' => isset($config['class']) ? $config['class'] : 'yii\db\Connection', + 'dsn' => $config['dsn'], + 'username' => isset($config['username']) ? $config['username'] : null, + 'password' => isset($config['password']) ? $config['password'] : null, + ], + ], + ]); + + if(isset($config['fixture'])) { + Yii::$app->db->open(); + $lines = explode(';', file_get_contents($config['fixture'])); + foreach ($lines as $line) { + if (trim($line) !== '') { + Yii::$app->db->pdo->exec($line); + } + } + } + } +} \ No newline at end of file diff --git a/tests/unit/extensions/gii/Profile.php b/tests/unit/extensions/gii/Profile.php new file mode 100644 index 0000000..1635b16 --- /dev/null +++ b/tests/unit/extensions/gii/Profile.php @@ -0,0 +1,40 @@ + 128] + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'id' => 'ID', + 'description' => 'Description', + ]; + } +} \ No newline at end of file diff --git a/tests/unit/extensions/mongodb/console/controllers/MigrateControllerTest.php b/tests/unit/extensions/mongodb/console/controllers/MigrateControllerTest.php index 62d4a9b..288f7ee 100644 --- a/tests/unit/extensions/mongodb/console/controllers/MigrateControllerTest.php +++ b/tests/unit/extensions/mongodb/console/controllers/MigrateControllerTest.php @@ -35,10 +35,12 @@ class MigrateControllerTest extends MongoDbTestCase public function tearDown() { parent::tearDown(); - try { - $this->getConnection()->getCollection('migration')->drop(); - } catch (Exception $e) { - // shutdown exception + if (extension_loaded('mongo')) { + try { + $this->getConnection()->getCollection('migration')->drop(); + } catch (Exception $e) { + // shutdown exception + } } $this->tearDownMigrationPath(); } diff --git a/tests/unit/extensions/twig/ViewRendererTest.php b/tests/unit/extensions/twig/ViewRendererTest.php index 14efa32..f8f5298 100644 --- a/tests/unit/extensions/twig/ViewRendererTest.php +++ b/tests/unit/extensions/twig/ViewRendererTest.php @@ -69,6 +69,30 @@ class ViewRendererTest extends TestCase $this->assertTrue(strpos($content, 'variable') !== false, 'variable should be there:' . $content); } + public function testInheritance() + { + $view = $this->mockView(); + $content = $view->renderFile('@yiiunit/extensions/twig/views/extends2.twig'); + $this->assertTrue(strpos($content, 'Hello, I\'m inheritance test!') !== false, 'Hello, I\'m inheritance test! should be there:' . $content); + $this->assertTrue(strpos($content, 'extends2 block') !== false, 'extends2 block should be there:' . $content); + $this->assertFalse(strpos($content, 'extends1 block') !== false, 'extends1 block should not be there:' . $content); + + $content = $view->renderFile('@yiiunit/extensions/twig/views/extends3.twig'); + $this->assertTrue(strpos($content, 'Hello, I\'m inheritance test!') !== false, 'Hello, I\'m inheritance test! should be there:' . $content); + $this->assertTrue(strpos($content, 'extends3 block') !== false, 'extends3 block should be there:' . $content); + $this->assertFalse(strpos($content, 'extends1 block') !== false, 'extends1 block should not be there:' . $content); + } + + public function testChangeTitle() + { + $view = $this->mockView(); + $view->title = 'Original title'; + + $content = $view->renderFile('@yiiunit/extensions/twig/views/changeTitle.twig'); + $this->assertTrue(strpos($content, 'New title') !== false, 'New title should be there:' . $content); + $this->assertFalse(strpos($content, 'Original title') !== false, 'Original title should not be there:' . $content); + } + /** * Mocks view instance * @return View diff --git a/tests/unit/extensions/twig/views/changeTitle.twig b/tests/unit/extensions/twig/views/changeTitle.twig new file mode 100644 index 0000000..bfa62a4 --- /dev/null +++ b/tests/unit/extensions/twig/views/changeTitle.twig @@ -0,0 +1,3 @@ +{{ set(this, 'title', 'New title') }} + +{{ this.title }} \ No newline at end of file diff --git a/tests/unit/extensions/twig/views/extends1.twig b/tests/unit/extensions/twig/views/extends1.twig new file mode 100644 index 0000000..78b7e79 --- /dev/null +++ b/tests/unit/extensions/twig/views/extends1.twig @@ -0,0 +1,5 @@ +Hello, I'm inheritance test! + +{% block test %} +extends1 block +{% endblock %} \ No newline at end of file diff --git a/tests/unit/extensions/twig/views/extends2.twig b/tests/unit/extensions/twig/views/extends2.twig new file mode 100644 index 0000000..b230a1d --- /dev/null +++ b/tests/unit/extensions/twig/views/extends2.twig @@ -0,0 +1,5 @@ +{% extends "extends1.twig" %} + +{% block test %} +extends2 block +{% endblock %} \ No newline at end of file diff --git a/tests/unit/extensions/twig/views/extends3.twig b/tests/unit/extensions/twig/views/extends3.twig new file mode 100644 index 0000000..1870ddf --- /dev/null +++ b/tests/unit/extensions/twig/views/extends3.twig @@ -0,0 +1,5 @@ +{% extends "@yiiunit/extensions/twig/views/extends1.twig" %} + +{% block test %} +extends3 block +{% endblock %} \ No newline at end of file diff --git a/tests/unit/framework/base/ExposedSecurity.php b/tests/unit/framework/base/ExposedSecurity.php new file mode 100644 index 0000000..7909057 --- /dev/null +++ b/tests/unit/framework/base/ExposedSecurity.php @@ -0,0 +1,27 @@ +assertSame($this->formatter->nullDisplay, $this->formatter->asNumber(null)); } + public function testAsSize() { + // tests for base 1000 + $this->formatter->sizeFormat['base'] = 1000; + $this->assertSame("999 B", $this->formatter->asSize(999)); + $this->assertSame("1.05 MB", $this->formatter->asSize(1024 * 1024)); + $this->assertSame("1.05 MB", $this->formatter->asSize(1024 * 1024, false, false)); + $this->assertSame("1 KB", $this->formatter->asSize(1000)); + $this->assertSame("1.02 KB", $this->formatter->asSize(1023)); + $this->assertSame("3 gigabytes", $this->formatter->asSize(3 * 1000 * 1000 * 1000, true)); + $this->assertNotSame("3 PB", $this->formatter->asSize(3 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000)); // this is 3 EB not 3 PB + + // tests for base 1024 + $this->formatter->sizeFormat['base'] = 1024; + $this->assertSame("1 KiB", $this->formatter->asSize(1024)); + $this->assertSame("1 MB", $this->formatter->asSize(1024 * 1024, false, false)); + $this->assertSame("1023 B", $this->formatter->asSize(1023)); + $this->assertSame("5 GiB", $this->formatter->asSize(5 * 1024 * 1024 * 1024)); + $this->assertSame("5 gibibytes", $this->formatter->asSize(5 * 1024 * 1024 * 1024,true)); + $this->assertNotSame("5 PiB", $this->formatter->asSize(5 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024)); // this is 5 EiB not 5 PiB + //$this->assertSame("1 YiB", $this->formatter->asSize(pow(2, 80))); + $this->assertSame("2 GiB", $this->formatter->asSize(2147483647)); // round 1.999 up to 2 + $this->formatter->sizeFormat['decimalSeparator'] = ','; + $this->formatter->sizeFormat['decimals'] = 3; + $this->assertSame("1,001 KiB", $this->formatter->asSize(1025)); + } + public function testFormat() { $value = time(); diff --git a/tests/unit/framework/base/SecurityTest.php b/tests/unit/framework/base/SecurityTest.php index ff23083..a807c8e 100644 --- a/tests/unit/framework/base/SecurityTest.php +++ b/tests/unit/framework/base/SecurityTest.php @@ -8,7 +8,6 @@ namespace yiiunit\framework\base; use yiiunit\TestCase; -use yii\base\Security; /** * @group base @@ -16,15 +15,15 @@ use yii\base\Security; class SecurityTest extends TestCase { /** - * @var Security + * @var ExposedSecurity */ protected $security; protected function setUp() { parent::setUp(); - $this->security = new Security(); - $this->security->derivationIterations = 100; // speed up test running + $this->security = new ExposedSecurity(); + $this->security->derivationIterations = 1000; // speed up test running } // Tests : @@ -78,72 +77,276 @@ class SecurityTest extends TestCase $this->assertFalse($this->security->validatePassword('test', $hash)); } + public function testEncryptByPassword() + { + $data = 'known data'; + $key = 'secret'; + + $encryptedData = $this->security->encryptByPassword($data, $key); + $this->assertFalse($data === $encryptedData); + $decryptedData = $this->security->decryptByPassword($encryptedData, $key); + $this->assertEquals($data, $decryptedData); + + $tampered = $encryptedData; + $tampered[20] = ~$tampered[20]; + $decryptedData = $this->security->decryptByPassword($tampered, $key); + $this->assertTrue(false === $decryptedData); + } + + public function testEncryptByKey() + { + $data = 'known data'; + $key = $this->security->generateRandomKey(80); + + $encryptedData = $this->security->encryptByKey($data, $key); + $this->assertFalse($data === $encryptedData); + $decryptedData = $this->security->decryptByKey($encryptedData, $key); + $this->assertEquals($data, $decryptedData); + + $encryptedData = $this->security->encryptByKey($data, $key, $key); + $decryptedData = $this->security->decryptByKey($encryptedData, $key, $key); + $this->assertEquals($data, $decryptedData); + + $tampered = $encryptedData; + $tampered[20] = ~$tampered[20]; + $decryptedData = $this->security->decryptByKey($tampered, $key); + $this->assertTrue(false === $decryptedData); + + $decryptedData = $this->security->decryptByKey($encryptedData, $key, $key . "\0"); + $this->assertTrue(false === $decryptedData); + } + + public function testGenerateRandomKey() + { + $length = 21; + $key = $this->security->generateRandomKey($length); + $this->assertEquals($length, strlen($key)); + } + + public function testGenerateRandomString() + { + $length = 21; + $key = $this->security->generateRandomString($length); + $this->assertEquals($length, strlen($key)); + $this->assertEquals(1, preg_match('/[A-Za-z0-9_-]+/', $key)); + } + + public function dataProviderPbkdf2() + { + return [ + [ + 'sha1', + 'password', + 'salt', + 1, + 20, + '0c60c80f961f0e71f3a9b524af6012062fe037a6' + ], + [ + 'sha1', + 'password', + 'salt', + 2, + 20, + 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957' + ], + [ + 'sha1', + 'password', + 'salt', + 4096, + 20, + '4b007901b765489abead49d926f721d065a429c1' + ], + [ + 'sha1', + 'password', + 'salt', + 16777216, + 20, + 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984' + ], + [ + 'sha1', + 'passwordPASSWORDpassword', + 'saltSALTsaltSALTsaltSALTsaltSALTsalt', + 4096, + 25, + '3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038' + ], + [ + 'sha1', + "pass\0word", + "sa\0lt", + 4096, + 16, + '56fa6aa75548099dcc37d7f03425e0c3' + ], + [ + 'sha256', + 'password', + 'salt', + 1, + 20, + '120fb6cffcf8b32c43e7225256c4f837a86548c9' + ], + [ + 'sha256', + "pass\0word", + "sa\0lt", + 4096, + 32, + '89b69d0516f829893c696226650a86878c029ac13ee276509d5ae58b6466a724' + ], + [ + 'sha256', + 'passwordPASSWORDpassword', + 'saltSALTsaltSALTsaltSALTsaltSALTsalt', + 4096, + 40, + '348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c4e2a1fb8dd53e1c635518c7dac47e9' + ], + ]; + } + /** - * Data provider for [[testEncrypt()]] - * @return array test data + * @dataProvider dataProviderPbkdf2 + * + * @param string $hash + * @param string $password + * @param string $salt + * @param int $iterations + * @param int $length + * @param string $okm */ - public function dataProviderEncrypt() + public function testPbkdf2($hash, $password, $salt, $iterations, $length, $okm) + { + $this->security->derivationIterations = $iterations; + $DK = $this->security->pbkdf2($hash, $password, $salt, $iterations, $length); + $this->assertEquals($okm, bin2hex($DK)); + } + + public function dataProviderDeriveKey() { + // See Appendix A in https://tools.ietf.org/html/rfc5869 return [ [ - 'hmac', - true, - false, + 'Hash' => 'sha256', + 'IKM' => '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', + 'salt' => '000102030405060708090a0b0c', + 'info' => 'f0f1f2f3f4f5f6f7f8f9', + 'L' => 42, + 'PRK' => '077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5', + 'OKM' => '3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865', + ], + [ + 'Hash' => 'sha256', + 'IKM' => '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f', + 'salt' => '606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf', + 'info' => 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff', + 'L' => 82, + 'PRK' => '06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15fc244', + 'OKM' => 'b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87', + ], + [ + 'Hash' => 'sha256', + 'IKM' => '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', + 'salt' => '', + 'info' => '', + 'L' => 42, + 'PRK' => '19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293ccb04', + 'OKM' => '8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8', ], [ - 'hmac', - false, - false, + 'Hash' => 'sha1', + 'IKM' => '0b0b0b0b0b0b0b0b0b0b0b', + 'salt' => '000102030405060708090a0b0c', + 'info' => 'f0f1f2f3f4f5f6f7f8f9', + 'L' => 42, + 'PRK' => '9b6c18c432a7bf8f0e71c8eb88f4b30baa2ba243', + 'OKM' => '085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e422478d305f3f896', ], [ - 'pbkdf2', - true, - !function_exists('hash_pbkdf2') + 'Hash' => 'sha1', + 'IKM' => '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f', + 'salt' => '606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf', + 'info' => 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff', + 'L' => 82, + 'PRK' => '8adae09a2a307059478d309b26c4115a224cfaf6', + 'OKM' => '0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e927336d0441f4c4300e2cff0d0900b52d3b4', ], [ - 'pbkdf2', - false, - !function_exists('hash_pbkdf2') + 'Hash' => 'sha1', + 'IKM' => '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', + 'salt' => '', + 'info' => '', + 'L' => 42, + 'PRK' => 'da8c8a73c7fa77288ec6f5e7c297786aa0d32d01', + 'OKM' => '0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0ea00033de03984d34918', ], + [ + 'Hash' => 'sha1', + 'IKM' => '0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', + 'salt' => null, + 'info' => '', + 'L' => 42, + 'PRK' => '2adccada18779e7c2077ad2eb19d3f3e731385dd', + 'OKM' => '2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5673a081d70cce7acfc48', + ] ]; } /** - * @dataProvider dataProviderEncrypt + * @dataProvider dataProviderDeriveKey * - * @param string $deriveKeyStrategy - * @param boolean $useDeriveKeyUniqueSalt - * @param boolean $isSkipped + * @param string $hash + * @param string $ikm + * @param string $salt + * @param string $info + * @param int $l + * @param string $prk + * @param string $okm */ - public function testEncrypt($deriveKeyStrategy, $useDeriveKeyUniqueSalt, $isSkipped) + public function testHkdf($hash, $ikm, $salt, $info, $l, $prk, $okm) { - if ($isSkipped) { - $this->markTestSkipped("Unable to test '{$deriveKeyStrategy}' derive key strategy"); - return; - } - $this->security->deriveKeyStrategy = $deriveKeyStrategy; - $this->security->useDeriveKeyUniqueSalt = $useDeriveKeyUniqueSalt; - - $data = 'known data'; - $key = 'secret'; - $encryptedData = $this->security->encrypt($data, $key); - $this->assertFalse($data === $encryptedData); - $decryptedData = $this->security->decrypt($encryptedData, $key); - $this->assertEquals($data, $decryptedData); + $dk = $this->security->hkdf($hash, hex2bin($ikm), hex2bin($salt), hex2bin($info), $l); + $this->assertEquals($okm, bin2hex($dk)); } - public function testGenerateRandomBytes() + public function dataProviderCompareStrings() { - $length = 21; - $key = $this->security->generateRandomBytes($length); - $this->assertEquals($length, strlen($key)); + return [ + ["", ""], + [false, ""], + [null, ""], + [0, ""], + [0.00, ""], + ["", null], + ["", false], + ["", 0], + ["", "\0"], + ["\0", ""], + ["\0", "\0"], + ["0", "\0"], + [0, "\0"], + ["user", "User"], + ["password", "password"], + ["password", "passwordpassword"], + ["password1", "password"], + ["password", "password2"], + ["", "password"], + ["password", ""], + ]; } - public function testGenerateRandomKey() + /** + * @dataProvider dataProviderCompareStrings + * + * @param $expected + * @param $actual + */ + public function testCompareStrings($expected, $actual) { - $length = 21; - $key = $this->security->generateRandomKey($length); - $this->assertEquals($length, strlen($key)); - $this->assertEquals(1, preg_match('/[A-Za-z0-9_.-]+/', $key)); + $this->assertEquals(strcmp($expected, $actual) === 0, $this->security->compareString($expected, $actual)); } -} +} \ No newline at end of file diff --git a/tests/unit/framework/console/controllers/BaseMessageControllerTest.php b/tests/unit/framework/console/controllers/BaseMessageControllerTest.php new file mode 100644 index 0000000..3701704 --- /dev/null +++ b/tests/unit/framework/console/controllers/BaseMessageControllerTest.php @@ -0,0 +1,346 @@ +mockApplication(); + $this->sourcePath = Yii::getAlias('@yiiunit/runtime/test_source'); + FileHelper::createDirectory($this->sourcePath, 0777); + if (!file_exists($this->sourcePath)) { + $this->markTestIncomplete('Unit tests runtime directory should have writable permissions!'); + } + $this->configFileName = Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . 'message_controller_test_config.php'; + } + + public function tearDown() + { + FileHelper::removeDirectory($this->sourcePath); + if (file_exists($this->configFileName)) { + unlink($this->configFileName); + } + } + + /** + * Creates test message controller instance. + * @return MessageController message command instance. + */ + protected function createMessageController() + { + $module = $this->getMock('yii\\base\\Module', ['fake'], ['console']); + $messageController = new MessageController('message', $module); + $messageController->interactive = false; + + return $messageController; + } + + /** + * Emulates running of the message controller action. + * @param string $actionId id of action to be run. + * @param array $args action arguments. + * @return string command output. + */ + protected function runMessageControllerAction($actionId, array $args = []) + { + $controller = $this->createMessageController(); + ob_start(); + ob_implicit_flush(false); + $controller->run($actionId, $args); + + return ob_get_clean(); + } + + /** + * Creates message command config file named as [[configFileName]]. + * @param array $config message command config. + */ + protected function saveConfigFile(array $config) + { + if (file_exists($this->configFileName)) { + unlink($this->configFileName); + } + $fileContent = 'configFileName, $fileContent); + } + + /** + * Creates source file with given content + * @param string $content file content + * @return string path to source file + */ + protected function createSourceFile($content) + { + $fileName = $this->sourcePath . DIRECTORY_SEPARATOR . md5(uniqid()) . '.php'; + file_put_contents($fileName, $content); + return $fileName; + } + + /** + * Saves messages + * + * @param array $messages + * @param string $category + */ + abstract protected function saveMessages($messages, $category); + + /** + * Loads messages + * + * @param string $category + * @return array + */ + abstract protected function loadMessages($category); + + /** + * @return array default config + */ + abstract protected function getDefaultConfig(); + + /** + * Returns config + * + * @param array $additionalConfig + * @return array + */ + protected function getConfig($additionalConfig = []) + { + return array_merge($this->getDefaultConfig(), $additionalConfig); + } + + // Tests: + + public function testActionConfig() + { + $configFileName = $this->configFileName; + $out = $this->runMessageControllerAction('config', [$configFileName]); + $this->assertTrue(file_exists($configFileName), "Unable to create config file from template. Command output:\n\n" . $out); + } + + public function testConfigFileNotExist() + { + $this->setExpectedException('yii\\console\\Exception'); + $this->runMessageControllerAction('extract', ['not_existing_file.php']); + } + + public function testCreateTranslation() + { + $category = 'test_category1'; + $message = 'test message'; + $sourceFileContent = "Yii::t('{$category}', '{$message}');"; + $this->createSourceFile($sourceFileContent); + + $this->saveConfigFile($this->getConfig()); + $out = $this->runMessageControllerAction('extract', [$this->configFileName]); + + $messages = $this->loadMessages($category); + $this->assertArrayHasKey($message, $messages, "\"$message\" is missing in translation file. Command output:\n\n" . $out); + } + + /** + * @depends testCreateTranslation + */ + public function testNothingToSave() + { + $category = 'test_category2'; + $message = 'test message'; + $sourceFileContent = "Yii::t('{$category}', '{$message}')"; + $this->createSourceFile($sourceFileContent); + + $this->saveConfigFile($this->getConfig()); + $out = $this->runMessageControllerAction('extract', [$this->configFileName]); + $out .= $this->runMessageControllerAction('extract', [$this->configFileName]); + + $this->assertTrue(strpos($out, 'Nothing to save') !== false, "Controller should respond with \"Nothing to save\" if there's nothing to update. Command output:\n\n" . $out); + } + + /** + * @depends testCreateTranslation + */ + public function testMerge() + { + $category = 'test_category3'; + + $existingMessage = 'test existing message'; + $existingMessageTranslation = 'test existing message translation'; + $this->saveMessages( + [$existingMessage => $existingMessageTranslation], + $category + ); + + $newMessage = 'test new message'; + $sourceFileContent = "Yii::t('{$category}', '{$existingMessage}');"; + $sourceFileContent .= "Yii::t('{$category}', '{$newMessage}');"; + $this->createSourceFile($sourceFileContent); + + $this->saveConfigFile($this->getConfig()); + $out = $this->runMessageControllerAction('extract', [$this->configFileName]); + + $messages = $this->loadMessages($category); + $this->assertArrayHasKey($newMessage, $messages, "Unable to add new message: \"$newMessage\". Command output:\n\n" . $out); + $this->assertArrayHasKey($existingMessage, $messages, "Unable to keep existing message: \"$existingMessage\". Command output:\n\n" . $out); + $this->assertEquals('', $messages[$newMessage], "Wrong new message content. Command output:\n\n" . $out); + $this->assertEquals($existingMessageTranslation, $messages[$existingMessage], "Unable to keep existing message content. Command output:\n\n" . $out); + } + + /** + * @depends testMerge + */ + public function testMarkObosoleteMessages() + { + $category = 'category'; + + $obsoleteMessage = 'obsolete message'; + $obsoleteTranslation = 'obsolete translation'; + $this->saveMessages([$obsoleteMessage => $obsoleteTranslation], $category); + + $sourceFileContent = "Yii::t('{$category}', 'any new message');"; + $this->createSourceFile($sourceFileContent); + + $this->saveConfigFile($this->getConfig(['removeUnused' => false])); + $out = $this->runMessageControllerAction('extract', [$this->configFileName]); + + $messages = $this->loadMessages($category); + + $this->assertArrayHasKey($obsoleteMessage, $messages, "Obsolete message should not be removed. Command output:\n\n" . $out); + $this->assertEquals('@@' . $obsoleteTranslation . '@@', $messages[$obsoleteMessage], "Obsolete message was not marked properly. Command output:\n\n" . $out); + } + + /** + * @depends testMerge + */ + public function removeObosoleteMessages() + { + $category = 'category'; + + $obsoleteMessage = 'obsolete message'; + $obsoleteTranslation = 'obsolete translation'; + $this->saveMessages([$obsoleteMessage => $obsoleteTranslation], $category); + + $sourceFileContent = "Yii::t('{$category}', 'any new message');"; + $this->createSourceFile($sourceFileContent); + + $this->saveConfigFile($this->getConfig(['removeUnused' => true])); + $out = $this->runMessageControllerAction('extract', [$this->configFileName]); + + $messages = $this->loadMessages($category); + + $this->assertArrayHasKey($obsoleteMessage, $messages, "Obsolete message should be removed. Command output:\n\n" . $out); + } + + /** + * @depends testMerge + */ + public function testMergeWithContentZero() + { + $category = 'test_category5'; + + $zeroMessage = 'test zero message'; + $zeroMessageContent = '0'; + $falseMessage = 'test false message'; + $falseMessageContent = 'false'; + $this->saveMessages([ + $zeroMessage => $zeroMessageContent, + $falseMessage => $falseMessageContent, + ], $category); + + $newMessage = 'test new message'; + $sourceFileContent = "Yii::t('{$category}', '{$zeroMessage}')"; + $sourceFileContent .= "Yii::t('{$category}', '{$falseMessage}')"; + $sourceFileContent .= "Yii::t('{$category}', '{$newMessage}')"; + $this->createSourceFile($sourceFileContent); + + $this->saveConfigFile($this->getConfig()); + $out = $this->runMessageControllerAction('extract', [$this->configFileName]); + + $messages = $this->loadMessages($category); + $this->assertTrue($zeroMessageContent === $messages[$zeroMessage], "Message content \"0\" is lost. Command output:\n\n" . $out); + $this->assertTrue($falseMessageContent === $messages[$falseMessage], "Message content \"false\" is lost. Command output:\n\n" . $out); + } + + /** + * @depends testCreateTranslation + */ + public function testMultipleTranslators() + { + $category = 'test_category6'; + + $translators = [ + 'Yii::t', + 'Custom::translate', + ]; + + $sourceMessages = [ + 'first message', + 'second message', + ]; + $sourceFileContent = ''; + foreach ($sourceMessages as $key => $message) { + $sourceFileContent .= $translators[$key] . "('{$category}', '{$message}');\n"; + } + $this->createSourceFile($sourceFileContent); + + $this->saveConfigFile($this->getConfig(['translator' => $translators])); + $this->runMessageControllerAction('extract', [$this->configFileName]); + + $messages = $this->loadMessages($category); + + foreach ($sourceMessages as $sourceMessage) { + $this->assertArrayHasKey($sourceMessage, $messages); + } + } + + /** + * @depends testCreateTranslation + */ + public function testMultipleCategories() + { + $category1 = 'category1'; + $category2 = 'category2'; + + $message1 = 'message1'; + $message2 = 'message2'; + $message3 = 'message3'; + + $this->saveConfigFile($this->getConfig(['removeUnused' => true])); + + // Generate initial translation + $sourceFileContent = "Yii::t('{$category1}', '{$message1}'); Yii::t('{$category2}', '{$message2}');"; + $source = $this->createSourceFile($sourceFileContent); + $out = $this->runMessageControllerAction('extract', [$this->configFileName]); + unlink($source); + + $messages1 = $this->loadMessages($category1); + $messages2 = $this->loadMessages($category2); + + $this->assertArrayHasKey($message1, $messages1, "message1 not found in category1. Command output:\n\n" . $out); + $this->assertArrayHasKey($message2, $messages2, "message2 not found in category2. Command output:\n\n" . $out); + $this->assertArrayNotHasKey($message3, $messages2, "message3 found in category2. Command output:\n\n" . $out); + + // Change source code, run translation again + $sourceFileContent = "Yii::t('{$category1}', '{$message1}'); Yii::t('{$category2}', '{$message3}');"; + $source = $this->createSourceFile($sourceFileContent); + $out .= "\n" . $this->runMessageControllerAction('extract', [$this->configFileName]); + unlink($source); + + $messages1 = $this->loadMessages($category1); + $messages2 = $this->loadMessages($category2); + $this->assertArrayHasKey($message1, $messages1, "message1 not found in category1. Command output:\n\n" . $out); + $this->assertArrayHasKey($message3, $messages2, "message3 not found in category2. Command output:\n\n" . $out); + $this->assertArrayNotHasKey($message2, $messages2, "message2 found in category2. Command output:\n\n" . $out); + } +} diff --git a/tests/unit/framework/console/controllers/MessageControllerTest.php b/tests/unit/framework/console/controllers/MessageControllerTest.php deleted file mode 100644 index 38e3f69..0000000 --- a/tests/unit/framework/console/controllers/MessageControllerTest.php +++ /dev/null @@ -1,387 +0,0 @@ -mockApplication(); - $this->sourcePath = Yii::getAlias('@yiiunit/runtime/test_source'); - $this->createDir($this->sourcePath); - if (!file_exists($this->sourcePath)) { - $this->markTestIncomplete('Unit tests runtime directory should have writable permissions!'); - } - $this->messagePath = Yii::getAlias('@yiiunit/runtime/test_messages'); - $this->createDir($this->messagePath); - $this->configFileName = Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . 'message_controller_test_config.php'; - } - - public function tearDown() - { - $this->removeDir($this->sourcePath); - $this->removeDir($this->messagePath); - if (file_exists($this->configFileName)) { - unlink($this->configFileName); - } - } - - /** - * Creates directory. - * @param $dirName directory full name - */ - protected function createDir($dirName) - { - if (!file_exists($dirName)) { - mkdir($dirName, 0777, true); - } - } - - /** - * Removes directory. - * @param $dirName directory full name - */ - protected function removeDir($dirName) - { - if (!empty($dirName) && file_exists($dirName)) { - $this->removeFileSystemObject($dirName); - } - } - - /** - * Removes file system object: directory or file. - * @param string $fileSystemObjectFullName file system object full name. - */ - protected function removeFileSystemObject($fileSystemObjectFullName) - { - if (!is_dir($fileSystemObjectFullName)) { - unlink($fileSystemObjectFullName); - } else { - $dirHandle = opendir($fileSystemObjectFullName); - while (($fileSystemObjectName = readdir($dirHandle)) !== false) { - if ($fileSystemObjectName === '.' || $fileSystemObjectName === '..') { - continue; - } - $this->removeFileSystemObject($fileSystemObjectFullName . DIRECTORY_SEPARATOR . $fileSystemObjectName); - } - closedir($dirHandle); - rmdir($fileSystemObjectFullName); - } - } - - /** - * Creates test message controller instance. - * @return MessageController message command instance. - */ - protected function createMessageController() - { - $module = $this->getMock('yii\\base\\Module', ['fake'], ['console']); - $messageController = new MessageController('message', $module); - $messageController->interactive = false; - - return $messageController; - } - - /** - * Emulates running of the message controller action. - * @param string $actionId id of action to be run. - * @param array $args action arguments. - * @return string command output. - */ - protected function runMessageControllerAction($actionId, array $args = []) - { - $controller = $this->createMessageController(); - ob_start(); - ob_implicit_flush(false); - $controller->run($actionId, $args); - - return ob_get_clean(); - } - - /** - * Creates message command config file named as [[configFileName]]. - * @param array $config message command config. - */ - protected function composeConfigFile(array $config) - { - if (file_exists($this->configFileName)) { - unlink($this->configFileName); - } - $fileContent = 'configFileName, $fileContent); - } - - /** - * Creates source file with given content - * @param string $content file content - * @param string|null $name file self name - */ - protected function createSourceFile($content, $name = null) - { - if (empty($name)) { - $name = md5(uniqid()) . '.php'; - } - file_put_contents($this->sourcePath . DIRECTORY_SEPARATOR . $name, $content); - } - - /** - * Creates message file with given messages. - * @param string $name file name - * @param array $messages messages. - */ - protected function createMessageFile($name, array $messages = []) - { - $fileName = $this->messagePath . DIRECTORY_SEPARATOR . $name; - if (file_exists($fileName)) { - unlink($fileName); - } else { - $dirName = dirname($fileName); - if (!file_exists($dirName)) { - mkdir($dirName, 0777, true); - } - } - $fileContent = 'configFileName; - $this->runMessageControllerAction('config', [$configFileName]); - $this->assertTrue(file_exists($configFileName), 'Unable to create config file template!'); - } - - public function testConfigFileNotExist() - { - $this->setExpectedException('yii\\console\\Exception'); - $this->runMessageControllerAction('extract', ['not_existing_file.php']); - } - - public function testCreateTranslation() - { - $language = 'en'; - - $category = 'test_category1'; - $message = 'test message'; - $sourceFileContent = "Yii::t('{$category}', '{$message}')"; - $this->createSourceFile($sourceFileContent); - - $this->composeConfigFile([ - 'languages' => [$language], - 'sourcePath' => $this->sourcePath, - 'messagePath' => $this->messagePath, - ]); - $this->runMessageControllerAction('extract', [$this->configFileName]); - - $this->assertTrue(file_exists($this->messagePath . DIRECTORY_SEPARATOR . $language), 'No language dir created!'); - $messageFileName = $this->messagePath . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $category . '.php'; - $this->assertTrue(file_exists($messageFileName), 'No message file created!'); - $messages = require($messageFileName); - $this->assertTrue(is_array($messages), 'Unable to compose messages!'); - $this->assertTrue(array_key_exists($message, $messages), 'Source message is missing!'); - } - - /** - * @depends testCreateTranslation - */ - public function testNothingNew() - { - $language = 'en'; - - $category = 'test_category2'; - $message = 'test message'; - $sourceFileContent = "Yii::t('{$category}', '{$message}')"; - $this->createSourceFile($sourceFileContent); - - $this->composeConfigFile([ - 'languages' => [$language], - 'sourcePath' => $this->sourcePath, - 'messagePath' => $this->messagePath, - ]); - $this->runMessageControllerAction('extract', [$this->configFileName]); - - $messageFileName = $this->messagePath . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $category . '.php'; - - // check file not overwritten: - $messageFileContent = file_get_contents($messageFileName); - $messageFileContent .= '// some not generated by command content'; - file_put_contents($messageFileName, $messageFileContent); - - $this->runMessageControllerAction('extract', [$this->configFileName]); - - $this->assertEquals($messageFileContent, file_get_contents($messageFileName)); - } - - /** - * @depends testCreateTranslation - */ - public function testMerge() - { - if (defined('HHVM_VERSION')) { - $this->markTestSkipped('Can not test on HHVM because modified files can not be reloaded.'); - } - - $language = 'en'; - $category = 'test_category3'; - $messageFileName = $language . DIRECTORY_SEPARATOR . $category . '.php'; - - $existingMessage = 'test existing message'; - $existingMessageContent = 'test existing message content'; - $this->createMessageFile($messageFileName, [ - $existingMessage => $existingMessageContent - ]); - - $newMessage = 'test new message'; - $sourceFileContent = "Yii::t('{$category}', '{$existingMessage}')"; - $sourceFileContent .= "Yii::t('{$category}', '{$newMessage}')"; - $this->createSourceFile($sourceFileContent); - - $this->composeConfigFile([ - 'languages' => [$language], - 'sourcePath' => $this->sourcePath, - 'messagePath' => $this->messagePath, - 'overwrite' => true, - ]); - $commandOutput = $this->runMessageControllerAction('extract', [$this->configFileName]); - - $messages = require($this->messagePath . DIRECTORY_SEPARATOR . $messageFileName); - $this->assertTrue(array_key_exists($newMessage, $messages), 'Unable to add new message: "' . $newMessage . '". Command output was:' . "\n" . $commandOutput); - $this->assertTrue(array_key_exists($existingMessage, $messages), 'Unable to keep existing message: "' . $existingMessage . '". Command output was:' . "\n" . $commandOutput); - $this->assertEquals('', $messages[$newMessage], 'Wrong new message content!. Command output was:\n' . $commandOutput); - $this->assertEquals($existingMessageContent, $messages[$existingMessage], 'Unable to keep existing message content!. Command output was:' . "\n" . $commandOutput); - } - - /** - * @depends testMerge - */ - public function testNoLongerNeedTranslation() - { - if (defined('HHVM_VERSION')) { - $this->markTestSkipped('Can not test on HHVM because modified files can not be reloaded.'); - } - - $language = 'en'; - $category = 'test_category4'; - $messageFileName = $language . DIRECTORY_SEPARATOR . $category . '.php'; - - $oldMessage = 'test old message'; - $oldMessageContent = 'test old message content'; - $this->createMessageFile($messageFileName, [ - $oldMessage => $oldMessageContent - ]); - - $sourceFileContent = "Yii::t('{$category}', 'some new message')"; - $this->createSourceFile($sourceFileContent); - - $this->composeConfigFile([ - 'languages' => [$language], - 'sourcePath' => $this->sourcePath, - 'messagePath' => $this->messagePath, - 'overwrite' => true, - 'removeUnused' => false, - ]); - $this->runMessageControllerAction('extract', [$this->configFileName]); - - $messages = require($this->messagePath . DIRECTORY_SEPARATOR . $messageFileName); - - $this->assertTrue(array_key_exists($oldMessage, $messages), 'No longer needed message removed!'); - $this->assertEquals('@@' . $oldMessageContent . '@@', $messages[$oldMessage], 'No longer needed message content does not marked properly!'); - } - - /** - * @depends testMerge - */ - public function testMergeWithContentZero() - { - if (defined('HHVM_VERSION')) { - $this->markTestSkipped('Can not test on HHVM because modified files can not be reloaded.'); - } - - $language = 'en'; - $category = 'test_category5'; - $messageFileName = $language . DIRECTORY_SEPARATOR . $category . '.php'; - - $zeroMessage = 'test zero message'; - $zeroMessageContent = '0'; - $falseMessage = 'test false message'; - $falseMessageContent = 'false'; - $this->createMessageFile($messageFileName, [ - $zeroMessage => $zeroMessageContent, - $falseMessage => $falseMessageContent, - ]); - - $newMessage = 'test new message'; - $sourceFileContent = "Yii::t('{$category}', '{$zeroMessage}')"; - $sourceFileContent .= "Yii::t('{$category}', '{$falseMessage}')"; - $sourceFileContent .= "Yii::t('{$category}', '{$newMessage}')"; - $this->createSourceFile($sourceFileContent); - - $this->composeConfigFile([ - 'languages' => [$language], - 'sourcePath' => $this->sourcePath, - 'messagePath' => $this->messagePath, - 'overwrite' => true, - ]); - $this->runMessageControllerAction('extract', [$this->configFileName]); - - $messages = require($this->messagePath . DIRECTORY_SEPARATOR . $messageFileName); - $this->assertTrue($zeroMessageContent === $messages[$zeroMessage], 'Message content "0" is lost!'); - $this->assertTrue($falseMessageContent === $messages[$falseMessage], 'Message content "false" is lost!'); - } - - /** - * @depends testCreateTranslation - */ - public function testMultiplyTranslators() - { - if (defined('HHVM_VERSION')) { - $this->markTestSkipped('Can not test on HHVM because modified files can not be reloaded.'); - } - - $language = 'en'; - $category = 'test_category6'; - - $translators = [ - 'Yii::t', - 'Custom::translate', - ]; - - $sourceMessages = [ - 'first message', - 'second message', - ]; - $sourceFileContent = ''; - foreach ($sourceMessages as $key => $message) { - $sourceFileContent .= $translators[$key] . "('{$category}', '{$message}');\n"; - } - $this->createSourceFile($sourceFileContent); - - $this->composeConfigFile([ - 'languages' => [$language], - 'sourcePath' => $this->sourcePath, - 'messagePath' => $this->messagePath, - 'translator' => $translators, - ]); - $this->runMessageControllerAction('extract', [$this->configFileName]); - - $messageFileName = $this->messagePath . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $category . '.php'; - $messages = require($messageFileName); - - foreach ($sourceMessages as $sourceMessage) { - $this->assertTrue(array_key_exists($sourceMessage, $messages)); - } - } -} diff --git a/tests/unit/framework/console/controllers/PHPMessageControllerTest.php b/tests/unit/framework/console/controllers/PHPMessageControllerTest.php new file mode 100644 index 0000000..20ddf1d --- /dev/null +++ b/tests/unit/framework/console/controllers/PHPMessageControllerTest.php @@ -0,0 +1,84 @@ +messagePath = Yii::getAlias('@yiiunit/runtime/test_messages'); + FileHelper::createDirectory($this->messagePath, 0777); + } + + public function tearDown() + { + parent::tearDown(); + FileHelper::removeDirectory($this->messagePath); + } + + /** + * @inheritdoc + */ + protected function getDefaultConfig() + { + return [ + 'format' => 'php', + 'languages' => [$this->language], + 'sourcePath' => $this->sourcePath, + 'messagePath' => $this->messagePath, + 'overwrite' => true, + ]; + } + + /** + * @param string $category + * @return string message file path + */ + protected function getMessageFilePath($category) + { + return $this->messagePath . '/' . $this->language . '/' . $category . '.php'; + } + + /** + * @inheritdoc + */ + protected function saveMessages($messages, $category) + { + $fileName = $this->getMessageFilePath($category); + if (file_exists($fileName)) { + unlink($fileName); + } else { + $dirName = dirname($fileName); + if (!file_exists($dirName)) { + mkdir($dirName, 0777, true); + } + } + $fileContent = 'markTestSkipped('Can not test on HHVM because require is cached.'); + } + + $messageFilePath = $this->getMessageFilePath($category); + $this->assertTrue(file_exists($messageFilePath), "There's no message file $messageFilePath!"); + return require $messageFilePath; + } +} \ No newline at end of file diff --git a/tests/unit/framework/console/controllers/POMessageControllerTest.php b/tests/unit/framework/console/controllers/POMessageControllerTest.php new file mode 100644 index 0000000..95d4e2f --- /dev/null +++ b/tests/unit/framework/console/controllers/POMessageControllerTest.php @@ -0,0 +1,80 @@ +messagePath = Yii::getAlias('@yiiunit/runtime/test_messages'); + FileHelper::createDirectory($this->messagePath, 0777); + } + + public function tearDown() + { + parent::tearDown(); + FileHelper::removeDirectory($this->messagePath); + } + + /** + * @inheritdoc + */ + protected function getDefaultConfig() + { + return [ + 'format' => 'po', + 'languages' => [$this->language], + 'sourcePath' => $this->sourcePath, + 'messagePath' => $this->messagePath, + 'overwrite' => true, + ]; + } + + /** + * @return string message file path + */ + protected function getMessageFilePath() + { + return $this->messagePath . '/' . $this->language . '/' . $this->catalog . '.po'; + } + + /** + * @inheritdoc + */ + protected function saveMessages($messages, $category) + { + $messageFilePath = $this->getMessageFilePath(); + FileHelper::createDirectory(dirname($messageFilePath), 0777); + $gettext = new GettextPoFile(); + + $data = []; + foreach ($messages as $message => $translation) { + $data[$category . chr(4) . $message] = $translation; + } + + $gettext->save($messageFilePath, $data); + } + + /** + * @inheritdoc + */ + protected function loadMessages($category) + { + $messageFilePath = $this->getMessageFilePath(); + $this->assertTrue(file_exists($messageFilePath), "There's no message file $messageFilePath!"); + + $gettext = new GettextPoFile(); + return $gettext->load($messageFilePath, $category); + } +} \ No newline at end of file diff --git a/tests/unit/framework/helpers/FileHelperTest.php b/tests/unit/framework/helpers/FileHelperTest.php index 33382ca..af27781 100644 --- a/tests/unit/framework/helpers/FileHelperTest.php +++ b/tests/unit/framework/helpers/FileHelperTest.php @@ -143,7 +143,7 @@ class FileHelperTest extends TestCase */ public function testCopyDirectoryPermissions() { - if (substr(PHP_OS, 0, 3) == 'WIN') { + if (DIRECTORY_SEPARATOR === '\\') { $this->markTestSkipped("Can't reliably test it on Windows because fileperms() always return 0777."); } diff --git a/tests/unit/framework/helpers/StringHelperTest.php b/tests/unit/framework/helpers/StringHelperTest.php index 4f9a50c..33d075b 100644 --- a/tests/unit/framework/helpers/StringHelperTest.php +++ b/tests/unit/framework/helpers/StringHelperTest.php @@ -26,6 +26,34 @@ class StringHelperTest extends TestCase { $this->assertEquals('th', StringHelper::byteSubstr('this', 0, 2)); $this->assertEquals('э', StringHelper::byteSubstr('это', 0, 2)); + + $this->assertEquals('abcdef', StringHelper::byteSubstr('abcdef', 0)); + $this->assertEquals('abcdef', StringHelper::byteSubstr('abcdef', 0, null)); + + $this->assertEquals('de', StringHelper::byteSubstr('abcdef', 3, 2)); + $this->assertEquals('def', StringHelper::byteSubstr('abcdef', 3)); + $this->assertEquals('def', StringHelper::byteSubstr('abcdef', 3, null)); + + $this->assertEquals('cd', StringHelper::byteSubstr('abcdef', -4, 2)); + $this->assertEquals('cdef', StringHelper::byteSubstr('abcdef', -4)); + $this->assertEquals('cdef', StringHelper::byteSubstr('abcdef', -4, null)); + + $this->assertEquals('', StringHelper::byteSubstr('abcdef', 4, 0)); + $this->assertEquals('', StringHelper::byteSubstr('abcdef', -4, 0)); + + $this->assertEquals('это', StringHelper::byteSubstr('это', 0)); + $this->assertEquals('это', StringHelper::byteSubstr('это', 0, null)); + + $this->assertEquals('т', StringHelper::byteSubstr('это', 2, 2)); + $this->assertEquals('то', StringHelper::byteSubstr('это', 2)); + $this->assertEquals('то', StringHelper::byteSubstr('это', 2, null)); + + $this->assertEquals('т', StringHelper::byteSubstr('это', -4, 2)); + $this->assertEquals('то', StringHelper::byteSubstr('это', -4)); + $this->assertEquals('то', StringHelper::byteSubstr('это', -4, null)); + + $this->assertEquals('', StringHelper::byteSubstr('это', 4, 0)); + $this->assertEquals('', StringHelper::byteSubstr('это', -4, 0)); } public function testBasename() diff --git a/tests/unit/framework/rbac/DbManagerTestCase.php b/tests/unit/framework/rbac/DbManagerTestCase.php index f8795d7..043b86a 100644 --- a/tests/unit/framework/rbac/DbManagerTestCase.php +++ b/tests/unit/framework/rbac/DbManagerTestCase.php @@ -70,8 +70,7 @@ abstract class DbManagerTestCase extends ManagerTestCase protected function setUp() { parent::setUp(); - $this->auth = new DbManager(['db' => $this->getConnection()]); - + $this->auth = $this->createManager(); } protected function tearDown() @@ -105,4 +104,12 @@ abstract class DbManagerTestCase extends ManagerTestCase } return static::$db; } + + /** + * @return \yii\rbac\ManagerInterface + */ + protected function createManager() + { + return new DbManager(['db' => $this->getConnection()]); + } } diff --git a/tests/unit/framework/rbac/ManagerTestCase.php b/tests/unit/framework/rbac/ManagerTestCase.php index ec12f78..0acc791 100644 --- a/tests/unit/framework/rbac/ManagerTestCase.php +++ b/tests/unit/framework/rbac/ManagerTestCase.php @@ -4,6 +4,7 @@ namespace yiiunit\framework\rbac; use yii\rbac\Item; use yii\rbac\Permission; +use yii\rbac\PhpManager; use yii\rbac\Role; use yiiunit\TestCase; @@ -17,6 +18,11 @@ abstract class ManagerTestCase extends TestCase */ protected $auth; + /** + * @return \yii\rbac\ManagerInterface + */ + abstract protected function createManager(); + public function testCreateRole() { $role = $this->auth->createRole('admin'); @@ -241,6 +247,43 @@ abstract class ManagerTestCase extends TestCase $roles = $this->auth->getRolesByUser('reader A'); $this->assertTrue(reset($roles) instanceof Role); $this->assertEquals($roles['reader']->name, 'reader'); + } + + public function testAssignMultipleRoles() + { + $this->prepareData(); + + $reader = $this->auth->getRole('reader'); + $author = $this->auth->getRole('author'); + $this->auth->assign($reader, 'readingAuthor'); + $this->auth->assign($author, 'readingAuthor'); + + $this->auth = $this->createManager(); + + $roles = $this->auth->getRolesByUser('readingAuthor'); + $roleNames = []; + foreach ($roles as $role) { + $roleNames[] = $role->name; + } + + $this->assertContains('reader', $roleNames, 'Roles should contain reader. Currently it has: ' . implode(', ', $roleNames)); + $this->assertContains('author', $roleNames, 'Roles should contain author. Currently it has: ' . implode(', ', $roleNames)); + } + + public function testAssignmentsToIntegerId() + { + $this->prepareData(); + + $reader = $this->auth->getRole('reader'); + $author = $this->auth->getRole('author'); + $this->auth->assign($reader, 42); + $this->auth->assign($author, 1337); + $this->auth->assign($reader, 1337); + + $this->auth = $this->createManager(); + $this->assertEquals(0, count($this->auth->getAssignments(0))); + $this->assertEquals(1, count($this->auth->getAssignments(42))); + $this->assertEquals(2, count($this->auth->getAssignments(1337))); } } diff --git a/tests/unit/framework/rbac/MySQLManagerTest.php b/tests/unit/framework/rbac/MySQLManagerTest.php index 853faa5..d64c0cd 100644 --- a/tests/unit/framework/rbac/MySQLManagerTest.php +++ b/tests/unit/framework/rbac/MySQLManagerTest.php @@ -3,6 +3,8 @@ namespace yiiunit\framework\rbac; /** * MySQLManagerTest + * @group db + * @group rbac */ class MySQLManagerTest extends DbManagerTestCase { diff --git a/tests/unit/framework/rbac/PgSQLManagerTest.php b/tests/unit/framework/rbac/PgSQLManagerTest.php index 4186fe2..fb15aad 100644 --- a/tests/unit/framework/rbac/PgSQLManagerTest.php +++ b/tests/unit/framework/rbac/PgSQLManagerTest.php @@ -3,6 +3,8 @@ namespace yiiunit\framework\rbac; /** * PgSQLManagerTest + * @group db + * @group rbac */ class PgSQLManagerTest extends DbManagerTestCase { diff --git a/tests/unit/framework/rbac/PhpManagerTest.php b/tests/unit/framework/rbac/PhpManagerTest.php index d420b7a..d22c8c4 100644 --- a/tests/unit/framework/rbac/PhpManagerTest.php +++ b/tests/unit/framework/rbac/PhpManagerTest.php @@ -1,5 +1,16 @@ getRuntimePath() . '/rbac-items.php'; @@ -32,6 +45,9 @@ class PhpManagerTest extends ManagerTestCase @unlink($this->getRuleFile()); } + /** + * @inheritdoc + */ protected function createManager() { return new ExposedPhpManager([ @@ -43,6 +59,7 @@ class PhpManagerTest extends ManagerTestCase protected function setUp() { + static::$filemtime = null; parent::setUp(); $this->mockApplication(); $this->removeDataFiles(); @@ -52,11 +69,13 @@ class PhpManagerTest extends ManagerTestCase protected function tearDown() { $this->removeDataFiles(); + static::$filemtime = null; parent::tearDown(); } public function testSaveLoad() { + static::$filemtime = time(); $this->prepareData(); $items = $this->auth->items; @@ -73,4 +92,4 @@ class PhpManagerTest extends ManagerTestCase $this->assertEquals($assignments, $this->auth->assignments); $this->assertEquals($rules, $this->auth->rules); } -} \ No newline at end of file +} diff --git a/tests/unit/framework/rbac/SqliteManagerTest.php b/tests/unit/framework/rbac/SqliteManagerTest.php index 316c46a..2b77fd0 100644 --- a/tests/unit/framework/rbac/SqliteManagerTest.php +++ b/tests/unit/framework/rbac/SqliteManagerTest.php @@ -3,6 +3,8 @@ namespace yiiunit\framework\rbac; /** * SqliteManagerTest + * @group db + * @group rbac */ class SqliteManagerTest extends DbManagerTestCase {