├── .github └── workflows │ └── php.yml ├── .gitignore ├── Changelog.md ├── Hacking.md ├── LICENSE ├── Makefile ├── README.md ├── composer.json ├── composer.lock ├── docs ├── 404.html ├── class-LeanCloud.ACL.html ├── class-LeanCloud.AppRouter.html ├── class-LeanCloud.BatchRequestError.html ├── class-LeanCloud.Bytes.html ├── class-LeanCloud.Client.html ├── class-LeanCloud.CloudException.html ├── class-LeanCloud.Engine.Cloud.html ├── class-LeanCloud.Engine.FunctionError.html ├── class-LeanCloud.Engine.LaravelEngine.html ├── class-LeanCloud.Engine.LeanEngine.html ├── class-LeanCloud.Engine.SlimEngine.html ├── class-LeanCloud.File.html ├── class-LeanCloud.GeoPoint.html ├── class-LeanCloud.LeanObject.html ├── class-LeanCloud.MIMEType.html ├── class-LeanCloud.Operation.ArrayOperation.html ├── class-LeanCloud.Operation.DeleteOperation.html ├── class-LeanCloud.Operation.IOperation.html ├── class-LeanCloud.Operation.IncrementOperation.html ├── class-LeanCloud.Operation.RelationOperation.html ├── class-LeanCloud.Operation.SetOperation.html ├── class-LeanCloud.Push.html ├── class-LeanCloud.Query.html ├── class-LeanCloud.Region.html ├── class-LeanCloud.Relation.html ├── class-LeanCloud.Role.html ├── class-LeanCloud.RouteCache.html ├── class-LeanCloud.SMS.html ├── class-LeanCloud.SaveOption.html ├── class-LeanCloud.Storage.CookieStorage.html ├── class-LeanCloud.Storage.IStorage.html ├── class-LeanCloud.Storage.SessionStorage.html ├── class-LeanCloud.Uploader.QCloudUploader.html ├── class-LeanCloud.Uploader.QiniuUploader.html ├── class-LeanCloud.Uploader.S3Uploader.html ├── class-LeanCloud.Uploader.SimpleUploader.html ├── class-LeanCloud.User.html ├── elementlist.js ├── index.html ├── namespace-LeanCloud.Engine.html ├── namespace-LeanCloud.Operation.html ├── namespace-LeanCloud.Storage.html ├── namespace-LeanCloud.Uploader.html ├── namespace-LeanCloud.html ├── resources │ ├── collapsed.png │ ├── combined.js │ ├── footer.png │ ├── inherit.png │ ├── resize.png │ ├── sort.png │ ├── style.css │ ├── tree-cleaner.png │ ├── tree-hasnext.png │ ├── tree-last.png │ └── tree-vertical.png ├── source-class-LeanCloud.ACL.html ├── source-class-LeanCloud.AppRouter.html ├── source-class-LeanCloud.BatchRequestError.html ├── source-class-LeanCloud.Bytes.html ├── source-class-LeanCloud.Client.html ├── source-class-LeanCloud.CloudException.html ├── source-class-LeanCloud.Engine.Cloud.html ├── source-class-LeanCloud.Engine.FunctionError.html ├── source-class-LeanCloud.Engine.LaravelEngine.html ├── source-class-LeanCloud.Engine.LeanEngine.html ├── source-class-LeanCloud.Engine.SlimEngine.html ├── source-class-LeanCloud.File.html ├── source-class-LeanCloud.GeoPoint.html ├── source-class-LeanCloud.LeanObject.html ├── source-class-LeanCloud.MIMEType.html ├── source-class-LeanCloud.Operation.ArrayOperation.html ├── source-class-LeanCloud.Operation.DeleteOperation.html ├── source-class-LeanCloud.Operation.IOperation.html ├── source-class-LeanCloud.Operation.IncrementOperation.html ├── source-class-LeanCloud.Operation.RelationOperation.html ├── source-class-LeanCloud.Operation.SetOperation.html ├── source-class-LeanCloud.Push.html ├── source-class-LeanCloud.Query.html ├── source-class-LeanCloud.Region.html ├── source-class-LeanCloud.Relation.html ├── source-class-LeanCloud.Role.html ├── source-class-LeanCloud.RouteCache.html ├── source-class-LeanCloud.SMS.html ├── source-class-LeanCloud.SaveOption.html ├── source-class-LeanCloud.Storage.CookieStorage.html ├── source-class-LeanCloud.Storage.IStorage.html ├── source-class-LeanCloud.Storage.SessionStorage.html ├── source-class-LeanCloud.Uploader.QCloudUploader.html ├── source-class-LeanCloud.Uploader.QiniuUploader.html ├── source-class-LeanCloud.Uploader.S3Uploader.html ├── source-class-LeanCloud.Uploader.SimpleUploader.html └── source-class-LeanCloud.User.html ├── fabfile.py ├── phpunit.xml ├── release.sh ├── src ├── LeanCloud │ ├── ACL.php │ ├── AppRouter.php │ ├── BatchRequestError.php │ ├── Bytes.php │ ├── Client.php │ ├── CloudException.php │ ├── Engine │ │ ├── Cloud.php │ │ ├── FunctionError.php │ │ ├── LaravelEngine.php │ │ ├── LeanEngine.php │ │ └── SlimEngine.php │ ├── File.php │ ├── GeoPoint.php │ ├── LeanObject.php │ ├── MIMEType.php │ ├── Object.php │ ├── Operation │ │ ├── ArrayOperation.php │ │ ├── DeleteOperation.php │ │ ├── IOperation.php │ │ ├── IncrementOperation.php │ │ ├── RelationOperation.php │ │ └── SetOperation.php │ ├── Push.php │ ├── Query.php │ ├── Region.php │ ├── Relation.php │ ├── Role.php │ ├── SMS.php │ ├── SaveOption.php │ ├── Storage │ │ ├── CookieStorage.php │ │ ├── IStorage.php │ │ └── SessionStorage.php │ ├── Uploader │ │ ├── QCloudUploader.php │ │ ├── QiniuUploader.php │ │ ├── S3Uploader.php │ │ └── SimpleUploader.php │ └── User.php └── autoload.php └── test ├── ACLTest.php ├── APITest.php ├── AppRouterTest.php ├── ArrayOperationTest.php ├── BytesTest.php ├── ClientTest.php ├── CloudTest.php ├── DeleteOperationTest.php ├── FileTest.php ├── GeoPointTest.php ├── IncrementOperationTest.php ├── LeanObjectTest.php ├── MasterTest.php ├── Php72ObjectDeprecated.php ├── PushTest.php ├── QueryTest.php ├── RelationOperationTest.php ├── RelationTest.php ├── RoleTest.php ├── SetOperationTest.php ├── StorageTest.php ├── UserTest.php └── engine ├── LeanEngineTest.php └── index.php /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: PHP Composer 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | paths-ignore: 7 | - '**.md' 8 | - 'doc/**' 9 | pull_request: 10 | branches: [ master ] 11 | paths-ignore: 12 | - '**.md' 13 | - 'doc/**' 14 | 15 | jobs: 16 | build: 17 | 18 | runs-on: ubuntu-latest 19 | strategy: 20 | max-parallel: 1 21 | matrix: 22 | php-versions: [7.2, 7.4] 23 | name: PHP ${{ matrix.php-versions }} Test 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v2 27 | 28 | - name: Setup PHP Action 29 | uses: shivammathur/setup-php@2.9.0 30 | with: 31 | php-version: ${{ matrix.php-versions }} 32 | coverage: xdebug 33 | 34 | - name: Validate composer.json and composer.lock 35 | run: composer validate --no-check-lock 36 | - name: Get composer cache directory 37 | id: composer-cache 38 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 39 | - name: Cache dependencies 40 | uses: actions/cache@v2 41 | with: 42 | path: ${{ steps.composer-cache.outputs.dir }} 43 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 44 | restore-keys: ${{ runner.os }}-composer- 45 | - name: Install dependencies 46 | run: composer install --prefer-dist 47 | 48 | - name: Run test suite with phpunit 49 | env: 50 | LEANCLOUD_API_SERVER: https://wndg0lpt.api.lncldglobal.com 51 | LEANCLOUD_APP_ID: wnDg0lPt0wcYGJSiHRwHBhD4 52 | LEANCLOUD_APP_KEY: u9ekx9HFSFFBErWwyWHFmPDy 53 | LEANCLOUD_APP_MASTER_KEY: ${{ secrets.MASTER_KEY }} 54 | LEANCLOUD_REGION: US 55 | LEANCLOUD_APP_HOST: 127.0.0.1 56 | LEANCLOUD_APP_PORT: 8081 57 | LEANCLOUD_WILDCARD_DOMAIN: lncldglobal.com 58 | LEANCLOUD_APP_ENV: production 59 | run: | 60 | make test_engine & 61 | php -r 'exit(PHP_VERSION_ID >= 70200 ? 0 : 1);' || vendor/bin/phpunit test/Php72ObjectDeprecated.php 62 | vendor/bin/phpunit --coverage-text 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | 2 | 0.14.2 Released on 2024-05-11 3 | ---- 4 | 5 | - Require phone number when verify sms code 6 | 7 | 0.14.1 Released on 2021-07-08 8 | ---- 9 | 10 | - Fix a bug for uploading files at Chinese regions. 11 | 12 | 0.14.0 Released on 2020-12-15 13 | ---- 14 | 15 | - Add `runRemote()` (invoking cloud function remotely). 16 | - Allow specifying the file key/path with `masterKey`. 17 | - Add license (Apache-2.0). 18 | 19 | 0.13.0 Released on 2020-07-15 20 | ---- 21 | 22 | - Add `requestChangePhoneNumber` & `changePhoneNumber` to verify mobile number *before* updating it. 23 | - Add `_messageUpdate` hook. 24 | - Add `setApiTimeout` to specify request timeout. 25 | 26 | 0.12.0 发布日期:2020-04-21 27 | ---- 28 | 29 | * 支持平滑推送(`flow_control`) 30 | * 支持即时通讯服务的 `_rtmClientSign`、`_conversationAdded`、`_conversationRemoved` hook 31 | * 支持 PHP 7.4 32 | 33 | 0.11.0 发布日期:2019-10-25 34 | ---- 35 | 36 | * 支持即时通讯服务新增的 `_clientOnline`、`_clientOffline` 这两个 hook。 37 | * 支持 PHP 7.3。 38 | 39 | 0.10.3 发布日期:2019-08-30 40 | ---- 41 | 42 | * 优化云引擎错误处理 43 | * 处理中间件中的异常,避免返回 500 内部错误。比如 sessionToken 不合法的异常。 44 | * 在云引擎错误栈中,打印请求到 API 的 method, url 45 | 46 | 0.10.2 发布日期:2019-06-24 47 | ---- 48 | 49 | * 修复 signUpOrLoginByMobilePhone 登录问题 50 | 51 | 0.10.1 发布日期:2019-06-24 52 | ---- 53 | 54 | * 修复 signUpOrLoginByMobilePhone 登录问题 55 | 56 | 0.10.0 发布日期:2019-06-24 57 | ---- 58 | 59 | - 添加 `User::signUpOrLoginByMobilePhone` 支持手机注册或登录 60 | 61 | 0.9.0 发布日期:2019-05-23 62 | ---- 63 | 64 | - 添加 `Cloud::start` 函数,更便捷地初始化云函数服务 65 | - 添加 `User::logInWithEmail` 函数,支持邮箱密码登录功能 66 | 67 | 0.8.1 发布日期:2018-09-25 68 | ---- 69 | 70 | - 修复 `Client::useRegion` 71 | 72 | 0.8.0 发布日期:2018-06-21 73 | ---- 74 | 75 | - `Object` 类更名为了 `LeanObject`,兼容 PHP 7.2 76 | 77 | 当 SDK 运行在 PHP 7.2 以下版本时,会为 `LeanObject` 创建一个别名,继续支持之前使用 `Object` 类的代码,这两个名字实际上指向同一个类,两个类名也可以混用。我们会在 PHP 7.2 以下继续支持 `Object` 一段时间,希望开发者尽快将代码中的 `Object` 改为 `LeanObject`。 78 | 79 | 0.7.0 发布日期:2018-03-19 80 | ---- 81 | 82 | * 添加 `Client::setServerUrl` 接口 83 | * 非云引擎环境下支持 app-router 84 | 85 | 0.6.0 发布日期:2017-09-06 86 | ---- 87 | 88 | * 更新 `LEANCLOUD_*` 环境变量 89 | * 根据环境变量设置是否以生产环境请求 90 | 91 | 0.5.6 发布日期:2017-04-13 92 | ---- 93 | 94 | * 修复: 用户定义的 class hook 不需要返回对象, 95 | 而是由中间件来返回被修改的对象 96 | 97 | 0.5.5 发布日期:2017-04-12 98 | ---- 99 | 100 | * 云引擎中输出错误栈 101 | 102 | 0.5.4 发布日期:2017-02-27 103 | ---- 104 | 105 | * 修复推送请求中的格式不兼容问题 106 | 107 | 0.5.3 发布日期:2017-02-13 108 | ---- 109 | 110 | * 支持小写的 region 111 | 112 | 0.5.2 发布日期:2017-01-22 113 | ---- 114 | 115 | * 修复 pointer 对象序列化为 object 116 | * 创建本地文件时支持传入文件名 117 | 118 | 0.5.1 发布日期:2016-11-25 119 | ---- 120 | 121 | * 修复 PHP 5.6 一下版本不能定义 array 常量的 bug 122 | 123 | 0.5.0 发布日期:2016-11-18 124 | ---- 125 | 126 | * 添加 User#getRoles 方法获取角色 127 | * 添加 User#isAuthenticated 方法检测用户是否登录 128 | * 添加 User#refreshSessionToken 方法重置 token 129 | 130 | 0.4.2 发布日期:2016-11-03 131 | ---- 132 | 133 | * 修复毫秒丢失的问题 #114 134 | * 修复 Relation 不能编码的异常 #110 135 | * Push 设置默认的 prod 参数 #111 136 | * 增加 `Client::setDebug(true)` 支持调试模式 #108 137 | * 添加 OptionSave 类支持 fetchWhenSave 以及 where #49 #83 138 | 139 | 0.4.1 发布日期:2016-09-13 140 | ---- 141 | 142 | * 支持实时通信的相关 hook 及校验 143 | * 支持通用短信发送接口 144 | 145 | 0.4.0 发布日期:2016-08-10 146 | ---- 147 | 148 | **不兼容改动** 149 | 150 | 为了与其它语言 SDK 类型名保持一致,将主要类型名称的 Lean 前缀去掉。如 151 | 果升级,请注意同步修改代码。 152 | 153 | 以下是去掉 `Lean` 前缀的类型列表: 154 | 155 | ``` 156 | LeanACL LeanBytes LeanClient LeanFile LeanObject LeanPush 157 | LeanQuery LeanRelation LeanRole LeanUser 158 | ``` 159 | 160 | 0.3.0 发布日期:2016-06-30 161 | ---- 162 | 163 | * 支持云引擎,及 Slim 框架的中间件 164 | 165 | 0.2.6 发布日期:2016-05-16 166 | ---- 167 | 168 | * LeanPush 支持同时向多平台发送推送 169 | * LeanObject::save, fetch, destroy 不再返回批量查询错误 170 | * 修复 LeanACL 为空时被编码为 array 的问题 171 | - LeanACL::encode 将返回 object (不兼容) 172 | * 修复 LeanRole 查询不能正常初识化 173 | - LeanRole 构造函数接收两个可选参数 className, objectId (不兼容) 174 | 175 | 0.2.5 发布日期:2016-02-01 176 | ---- 177 | * 支持手机号码和密码登录 178 | * 修复查询 `_User` 未传递 sessionToken 导致查询失败 179 | 180 | 0.2.4 发布日期:2016-01-26 181 | ---- 182 | 183 | * 修复短信验证码登录后 current user 为空的问题 184 | 185 | 0.2.3 发布日期:2016-01-12 186 | ---- 187 | 188 | * 修复 getCurrentUser 循环调用问题 close #48 189 | 190 | 0.2.2 发布日期:2016-01-06 191 | ---- 192 | 193 | * 修复保存关联文件的对象时的语法错误 close #46 194 | 195 | 0.2.1 发布日期:2015-12-31 196 | ---- 197 | 198 | * 修复类型不安全的字符串比较 close #43 199 | 200 | 0.2.0 发布日期:2015-11-13 201 | ---- 202 | * 支持 CQL 查询:LeanQuery::doCloudQuery 203 | * 支持发送 Push 推送消息 (#23) 204 | * 支持 GeoPoint 类型及地理位置查询 (#25) 205 | * 支持 Role 和 ACL 权限管理 (#19) 206 | * 修复: `LeanClient::useMasterKey()` 没有生效的问题 #21 207 | * `LeanClient::decode()` 添加第二个参数以识别 ACL 208 | (**与上一版本不兼容**) 209 | 210 | 0.1.0 发布日期: 2015-10-30 211 | ---- 212 | -------------------------------------------------------------------------------- /Hacking.md: -------------------------------------------------------------------------------- 1 | # Hacking 2 | 3 | ## Pull Request 4 | 5 | * Get and install [composer](https://getcomposer.org) 6 | * Fork the SDK from leancloud/php-sdk 7 | * Run `composer install` to get dependencies 8 | * Setup app credential in env variables: 9 | 10 | ```sh 11 | export LC_APP_ID=... 12 | export LC_APP_KEY=... 13 | export LC_APP_MASTER_KEY=... 14 | export LC_API_REGION=US 15 | export LEANCLOUD_APP_HOST="127.0.0.1" 16 | export LEANCLOUD_APP_PORT=8081 17 | export LEANCLOUD_WILDCARD_DOMAIN="lncldglobal.com" 18 | ``` 19 | 20 | * Run tests: 21 | 22 | ```sh 23 | make test_engine & 24 | make test 25 | ``` 26 | 27 | Run one single test: 28 | 29 | ```sh 30 | vendor/bin/phpunit --filter testInitializeWithString test/QueryTest.php 31 | ``` 32 | 33 | * `make doc` to build documentation. 34 | The make task uses PHP 5.6, to install it on recent versions of macOS, 35 | see https://github.com/eXolnet/homebrew-deprecated/pull/25 36 | 37 | * Send a pull request at leancloud/php-sdk 38 | 39 | Thanks for your contribution! 40 | 41 | ## Prepare a Release 42 | 43 | Make sure all tests are passed. 44 | 45 | Run `make release V=MAJOR.MINOR.PATCH` (e.g. `make release V=0.11.0`), 46 | and edit `Changelog.md` (git log subjects are for reference only, do not leave them unchanged). 47 | 48 | Commit changes and send a pull request at leancloud/php-sdk. 49 | 50 | If everything is O.K., the maintainer will merge the pull request, create a new tag, and publish a new release at GitHub. 51 | Then a new version will be published at Packagist automatically. 52 | It is recommended for the maintainer to create the tag when drafting a new release on GitHub web interface. 53 | If you prefer creating a tag locally, do not forget to pull from origin before creating a new tag. 54 | Then a new version will be published at Packagist automatically. 55 | 56 | ## Run Tests with Coverage 57 | 58 | To run tests with coverage, you need to have xdebug enabled. 59 | In other words, make sure "with Xdebug" is in the output of `php -v`. 60 | 61 | ```sh 62 | php -v 63 | PHP 7.2.34 (cli) (built: Nov 30 2020 14:07:08) ( NTS ) 64 | Copyright (c) 1997-2018 The PHP Group 65 | Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies 66 | with Xdebug v3.0.1, Copyright (c) 2002-2020, by Derick Rethans 67 | with Zend OPcache v7.2.34, Copyright (c) 1999-2018, by Zend Technologies 68 | ``` 69 | 70 | By default, PHP installed via Homebrew does not enable xdebug. 71 | You can install xdebug with pecl to enable it. 72 | For example, install xdebug for PHP 7.2 on macOS: 73 | 74 | ```sh 75 | brew install php@7.2 76 | $(brew --prefix php@7.2)/bin/pecl install --force xdebug 77 | ``` 78 | 79 | Once xdebug is enabled, run tests with coverage with the following commands: 80 | 81 | ```sh 82 | # export environment variables as usual 83 | make test_engine & 84 | XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-text 85 | ``` 86 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | vendor/bin/phpunit test 3 | php -r 'exit(PHP_VERSION_ID >= 70200 ? 0 : 1);' || vendor/bin/phpunit test/Php72ObjectDeprecated.php 4 | 5 | release: 6 | ./release.sh $V 7 | make doc 8 | 9 | doc: 10 | @rm -rf docs 11 | @php5.6 vendor/bin/apigen generate --source src --destination docs 12 | 13 | test_engine: 14 | php -S ${LEANCLOUD_APP_HOST}:${LEANCLOUD_APP_PORT} test/engine/index.php 15 | 16 | .PHONY: test doc test_engine 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LeanCloud PHP SDK 2 | ==== 3 | 4 | [![Build Status](https://img.shields.io/travis/leancloud/php-sdk.svg) 5 | ](https://travis-ci.org/leancloud/php-sdk) 6 | [![Latest Version](https://img.shields.io/packagist/v/leancloud/leancloud-sdk.svg) 7 | ](https://packagist.org/packages/leancloud/leancloud-sdk) 8 | [![Coverage Status](https://img.shields.io/codecov/c/github/leancloud/php-sdk/master.svg)](https://codecov.io/github/leancloud/php-sdk) 9 | 10 | LeanCloud 为应用提供了从数据存储,消息推送,实时通信到离线分析等全方位 11 | 的一站式云端服务,帮助应用开发者降低后端开发及维护成本,为应用开发加速。 12 | PHP SDK 提供了对数据存储,用户管理等模块的 PHP 实现及接口,以方便 PHP 13 | 应用的开发。 14 | 15 | 安装 16 | ---- 17 | 18 | 运行环境要求 PHP 5.6 及以上版本,以及 19 | [cURL](http://php.net/manual/zh/book.curl.php)。 20 | 21 | #### composer 安装 22 | 23 | 如果使用标准的包管理器 composer,你可以很容易的在项目中添加依赖并下载: 24 | 25 | ```bash 26 | composer require leancloud/leancloud-sdk 27 | ``` 28 | 29 | #### 手动下载安装 30 | 31 | 你也可以前往[发布页面](https://github.com/leancloud/php-sdk/releases) 32 | 手动下载安装包。假设你的应用位于 `$APP_ROOT` 目录下: 33 | 34 | ```bash 35 | cd $APP_ROOT 36 | wget https://github.com/leancloud/php-sdk/archive/vX.X.X.zip 37 | 38 | # 解压并置于 vendor 目录 39 | unzip vX.X.X.zip 40 | mv php-sdk-X.X.X vendor/leancloud 41 | ``` 42 | 43 | 初始化 44 | ---- 45 | 46 | 完成上述安装后,需要对 SDK 初始化。如果已经创建应用,可以在 LeanCloud 47 | [**控制台** > **应用设置**]里找到应用的 ID 和 key。然后在项目中加载 SDK, 48 | 并初始化: 49 | 50 | ```php 51 | // 如果是 composer 安装 52 | // require_once("vendor/autoload.php"); 53 | 54 | // 如果是手动安装 55 | require_once("vendor/leancloud/src/autoload.php"); 56 | 57 | // 参数依次为 app-id, app-key, master-key 58 | LeanCloud\Client::initialize("app_id", "app_key", "master_key"); 59 | ``` 60 | 61 | 使用示例 62 | ---- 63 | 64 | #### 用户注册及管理 65 | 66 | 注册一个用户: 67 | 68 | ```php 69 | use LeanCloud\User; 70 | use LeanCloud\CloudException; 71 | 72 | $user = new User(); 73 | $user->setUsername("alice"); 74 | $user->setEmail("alice@example.net"); 75 | $user->setPassword("passpass"); 76 | try { 77 | $user->signUp(); 78 | } catch (CloudException $ex) { 79 | // 如果 LeanCloud 返回错误,这里会抛出异常 CloudException 80 | // 如用户名已经被注册:202 Username has been taken 81 | } 82 | 83 | // 注册成功后,用户被自动登录。可以通过以下方法拿到当前登录用户和 84 | // 授权码。 85 | User::getCurrentUser(); 86 | User::getCurrentSessionToken(); 87 | ``` 88 | 89 | 登录一个用户: 90 | 91 | ```php 92 | User::logIn("alice", "passpass"); 93 | $user = User::getCurrentUser(); 94 | $token = User::getCurrentSessionToken(); 95 | 96 | // 给定一个 token 可以很容易的拿到用户 97 | User::become($token); 98 | 99 | // 我们还支持短信验证码,及第三方授权码登录 100 | User::logInWithSmsCode("phone number", "sms code"); 101 | User::logInWith("weibo", array("openid" => "...")); 102 | ``` 103 | 104 | #### 对象存储 105 | 106 | ```php 107 | use LeanCloud\LeanObject; 108 | use LeanCloud\CloudException; 109 | 110 | $obj = new LeanObject("TestObject"); 111 | $obj->set("name", "alice"); 112 | $obj->set("height", 60.0); 113 | $obj->set("weight", 4.5); 114 | $obj->set("birthdate", new \DateTime()); 115 | try { 116 | $obj->save(); 117 | } catch (CloudException $ex) { 118 | // CloudException 会被抛出,如果保存失败 119 | } 120 | 121 | // 获取字段值 122 | $obj->get("name"); 123 | $obj->get("height"); 124 | $obj->get("birthdate"); 125 | 126 | // 原子增加一个数 127 | $obj->increment("age", 1); 128 | 129 | // 在数组字段中添加,添加唯一,删除 130 | // 注意: 由于API限制,不同数组操作之间必须保存,否则会报错 131 | $obj->addIn("colors", "blue"); 132 | $obj->save(); 133 | $obj->addUniqueIn("colors", "orange"); 134 | $obj->save(); 135 | $obj->removeIn("colors", "blue"); 136 | $obj->save(); 137 | 138 | // 在云存储上删除数据 139 | $obj->destroy(); 140 | ``` 141 | 142 | 我们同样支持子类继承,子类中需要定义静态变量 `$className` ,并注册到存储类: 143 | 144 | ```php 145 | class TestObject extends LeanObject { 146 | protected static $className = "TestObject"; 147 | public setName($name) { 148 | $this->set("name", $name); 149 | return $this; 150 | } 151 | } 152 | // register it as storage class 153 | TestObject::registerClass(); 154 | 155 | $obj = new TestObject(); 156 | $obj->setName(); 157 | $obj->set("eyeColor", "blue"); 158 | ... 159 | ``` 160 | 161 | #### 对象查询 162 | 163 | 给定一个 objectId,可以如下获取对象。 164 | 165 | ```php 166 | use LeanCloud\Query; 167 | 168 | $query = new Query("TestObject"); 169 | $obj = $query->get($objectId); 170 | ``` 171 | 172 | 更为复杂的条件查询: 173 | 174 | ```php 175 | $query = new Query("TestObject"); 176 | $query->lessThan("height", 100.0); // 小于 177 | $query->greaterThanOrEqualTo("weight", 5.0); // 大于等于 178 | $query->addAscend("birthdate"); // 递增排序 179 | $query->addDescend("name"); // 递减排序 180 | $query->count(); 181 | $query->first(); // 返回第一个对象 182 | 183 | $query->skip(100); 184 | $query->limit(20); 185 | $objects = $query->find(); // 返回查询到的对象 186 | ``` 187 | 188 | #### 文件存储 189 | 190 | 直接创建文件: 191 | 192 | ```php 193 | use LeanCloud\File; 194 | $file = File::createWithData("hello.txt", "Hello LeanCloud!"); 195 | try { 196 | $file->save(); 197 | } catch (CloudException $ex) { 198 | // 云存储返回错误,保存失败 199 | } 200 | 201 | $file->getSize(); 202 | $file->getName(); 203 | $file->getUrl(); 204 | ``` 205 | 206 | 由本地文件创建: 207 | 208 | ```php 209 | $file = File::createWithLocalFile("/tmp/myfile.png"); 210 | try { 211 | $file->save(); 212 | } catch (CloudException $ex) { 213 | // 云存储返回错误,保存失败 214 | } 215 | 216 | // 获取文件缩略图的链接 217 | $url = $file->getThumbUrl(); 218 | ``` 219 | 220 | 由已知的 URL 创建文件: 221 | 222 | ```php 223 | $file = File::createWithUrl("image.png", "http://example.net/image.png"); 224 | try { 225 | $file->save(); 226 | } catch (CloudException $ex) { 227 | // 云存储返回错误,保存失败 228 | } 229 | ``` 230 | 231 | 更多文档请参考 232 | [PHP 数据存储开发指南](https://leancloud.cn/docs/leanstorage_guide-php.html) 233 | 234 | 贡献 235 | ---- 236 | 237 | See Hacking.md if you'd like to contribute. 238 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leancloud/leancloud-sdk", 3 | "description": "LeanCloud PHP SDK", 4 | "type": "library", 5 | "keywords": ["leancloud"], 6 | "homepage": "https://github.com/leancloud/php-sdk", 7 | "authors": [ 8 | { 9 | "name": "Juvenn Woo", 10 | "email": "yun.wu@leancloud.rocks" 11 | } 12 | ], 13 | "license": "Apache-2.0", 14 | "require": { 15 | "php": ">=5.6", 16 | "ext-curl": "*" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "^5.7", 20 | "apigen/apigen": "^4.1", 21 | "doctrine/instantiator": "<1.1.0" 22 | }, 23 | "autoload": { 24 | "psr-4": {"LeanCloud\\": "src/LeanCloud"} 25 | }, 26 | "minimum-stability": "stable" 27 | } 28 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Page not found 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 109 |
110 | 111 |
112 | 113 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /docs/class-LeanCloud.Engine.FunctionError.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Class LeanCloud\Engine\FunctionError 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 73 |
74 | 75 |
76 | 77 | 289 | 290 | 291 | 292 | 293 | -------------------------------------------------------------------------------- /docs/class-LeanCloud.RouteCache.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Class LeanCloud\RouteCache 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 87 |
88 | 89 |
90 | 91 | 266 | 267 | 268 | 269 | 270 | -------------------------------------------------------------------------------- /docs/class-LeanCloud.SMS.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Class LeanCloud\SMS 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 87 |
88 | 89 |
90 | 91 | 264 | 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /docs/class-LeanCloud.SaveOption.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Class LeanCloud\SaveOption 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 87 |
88 | 89 |
90 | 91 | 259 | 260 | 261 | 262 | 263 | -------------------------------------------------------------------------------- /docs/class-LeanCloud.Uploader.QCloudUploader.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Class LeanCloud\Uploader\QCloudUploader 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 69 |
70 | 71 |
72 | 73 | 257 | 258 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /docs/class-LeanCloud.Uploader.S3Uploader.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Class LeanCloud\Uploader\S3Uploader 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 69 |
70 | 71 |
72 | 73 | 222 | 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /docs/elementlist.js: -------------------------------------------------------------------------------- 1 | 2 | var ApiGen = ApiGen || {}; 3 | ApiGen.elements = [["c","LeanCloud\\ACL"],["c","LeanCloud\\AppRouter"],["c","LeanCloud\\BatchRequestError"],["c","LeanCloud\\Bytes"],["c","LeanCloud\\Client"],["c","LeanCloud\\CloudException"],["c","LeanCloud\\Engine\\Cloud"],["c","LeanCloud\\Engine\\FunctionError"],["c","LeanCloud\\Engine\\LaravelEngine"],["c","LeanCloud\\Engine\\LeanEngine"],["c","LeanCloud\\Engine\\SlimEngine"],["c","LeanCloud\\File"],["c","LeanCloud\\GeoPoint"],["c","LeanCloud\\LeanObject"],["c","LeanCloud\\MIMEType"],["c","LeanCloud\\Operation\\ArrayOperation"],["c","LeanCloud\\Operation\\DeleteOperation"],["c","LeanCloud\\Operation\\IncrementOperation"],["c","LeanCloud\\Operation\\IOperation"],["c","LeanCloud\\Operation\\RelationOperation"],["c","LeanCloud\\Operation\\SetOperation"],["c","LeanCloud\\Push"],["c","LeanCloud\\Query"],["c","LeanCloud\\Region"],["c","LeanCloud\\Relation"],["c","LeanCloud\\Role"],["c","LeanCloud\\RouteCache"],["c","LeanCloud\\SaveOption"],["c","LeanCloud\\SMS"],["c","LeanCloud\\Storage\\CookieStorage"],["c","LeanCloud\\Storage\\IStorage"],["c","LeanCloud\\Storage\\SessionStorage"],["c","LeanCloud\\Uploader\\QCloudUploader"],["c","LeanCloud\\Uploader\\QiniuUploader"],["c","LeanCloud\\Uploader\\S3Uploader"],["c","LeanCloud\\Uploader\\SimpleUploader"],["c","LeanCloud\\User"]]; 4 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Overview 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 107 |
108 | 109 |
110 | 111 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /docs/namespace-LeanCloud.Engine.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Namespace LeanCloud\Engine 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 73 |
74 | 75 |
76 | 77 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /docs/namespace-LeanCloud.Operation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Namespace LeanCloud\Operation 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 74 |
75 | 76 |
77 | 78 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /docs/namespace-LeanCloud.Storage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Namespace LeanCloud\Storage 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 71 |
72 | 73 |
74 | 75 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /docs/namespace-LeanCloud.Uploader.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Namespace LeanCloud\Uploader 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 69 |
70 | 71 |
72 | 73 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /docs/namespace-LeanCloud.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Namespace LeanCloud 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 87 |
88 | 89 |
90 | 91 | 229 | 230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /docs/resources/collapsed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/php-sdk/66f57f0132c83bd91bd13d8ebdabb7d977cf5095/docs/resources/collapsed.png -------------------------------------------------------------------------------- /docs/resources/footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/php-sdk/66f57f0132c83bd91bd13d8ebdabb7d977cf5095/docs/resources/footer.png -------------------------------------------------------------------------------- /docs/resources/inherit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/php-sdk/66f57f0132c83bd91bd13d8ebdabb7d977cf5095/docs/resources/inherit.png -------------------------------------------------------------------------------- /docs/resources/resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/php-sdk/66f57f0132c83bd91bd13d8ebdabb7d977cf5095/docs/resources/resize.png -------------------------------------------------------------------------------- /docs/resources/sort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/php-sdk/66f57f0132c83bd91bd13d8ebdabb7d977cf5095/docs/resources/sort.png -------------------------------------------------------------------------------- /docs/resources/tree-cleaner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/php-sdk/66f57f0132c83bd91bd13d8ebdabb7d977cf5095/docs/resources/tree-cleaner.png -------------------------------------------------------------------------------- /docs/resources/tree-hasnext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/php-sdk/66f57f0132c83bd91bd13d8ebdabb7d977cf5095/docs/resources/tree-hasnext.png -------------------------------------------------------------------------------- /docs/resources/tree-last.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/php-sdk/66f57f0132c83bd91bd13d8ebdabb7d977cf5095/docs/resources/tree-last.png -------------------------------------------------------------------------------- /docs/resources/tree-vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/php-sdk/66f57f0132c83bd91bd13d8ebdabb7d977cf5095/docs/resources/tree-vertical.png -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # Usage: 4 | # fab -H username@hostname deploy_docs:local_dir='folder',platform='php' 5 | # 6 | # 7 | 8 | from fabric.api import run, sudo, env, cd, local, prefix, put, lcd, settings 9 | from fabric.contrib.files import exists, sed 10 | from fabric.contrib.project import rsync_project 11 | 12 | env.use_ssh_config = True 13 | 14 | user = 'deploy' 15 | doc_dir = '/var/www/avoscloud-api-docs' 16 | 17 | project_dir = "." 18 | dist = 'debian' 19 | host_count = len(env.hosts) 20 | 21 | def _set_user_dir(): 22 | global dist,user,doc_dir 23 | with settings(warn_only=True): 24 | issue = run('id ubuntu').lower() 25 | if 'id: ubuntu' in issue: 26 | dist = 'debian' 27 | elif 'uid=' in issue: 28 | dist = 'ubuntu' 29 | user = 'ubuntu' 30 | doc_dir = '/mnt/avos/avoscloud-api-docs' 31 | 32 | def prepare_remote_dirs(remote_dir): 33 | _set_user_dir() 34 | if not exists(remote_dir): 35 | sudo('mkdir -p %s' % remote_dir) 36 | sudo('chown %s %s' % (user, remote_dir)) 37 | 38 | def deploy_docs(local_dir='', platform='unknown'): 39 | global host_count 40 | _set_user_dir() 41 | remote_dir = '%s/%s/' % (doc_dir, platform) 42 | 43 | prepare_remote_dirs(remote_dir) 44 | rsync_project(local_dir=local_dir + '/', 45 | remote_dir=remote_dir, 46 | delete=True) 47 | host_count -= 1 48 | if (host_count == 0): 49 | print("Finished to public api docs!") 50 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | test 8 | tests/engine 9 | 10 | 11 | 12 | 13 | 14 | src 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #### it requires new version number 4 | if [ -z "$1" ]; then 5 | echo "Error: please provide a version string" 6 | exit 1 7 | fi 8 | version="$1" 9 | 10 | #### Build new changelog 11 | echo "" >> Changelog.md.0 12 | echo "$version Released on `date +%Y-%m-%d`" >> Changelog.md.0 13 | echo "----" >> Changelog.md.0 14 | git log `git describe --tags --abbrev=0`..HEAD --pretty=%s >> Changelog.md.0 15 | 16 | #### prepend changelog 17 | cat Changelog.md >> Changelog.md.0 18 | mv Changelog.md.0 Changelog.md 19 | 20 | #### update version string client 21 | perl -pi -e "s/const VERSION = .*\;/const VERSION = \'$version\'\;/" \ 22 | src/LeanCloud/Client.php 23 | 24 | echo "Done! Ready to commit and release $version!" 25 | 26 | -------------------------------------------------------------------------------- /src/LeanCloud/AppRouter.php: -------------------------------------------------------------------------------- 1 | "us-api.leancloud.cn", 21 | Region::CN_E1 => "e1-api.leancloud.cn", 22 | Region::CN_N1 => "api.leancloud.cn" 23 | ); 24 | 25 | private static $DEFAULT_REGION_RTM_ROUTE = array( 26 | Region::US => "router-a0-push.leancloud.cn", 27 | Region::CN_E1 => "router-q0-push.leancloud.cn", 28 | Region::CN_N1 => "router-g0-push.leancloud.cn" 29 | ); 30 | 31 | private function __construct($appId) { 32 | $this->appId = $appId; 33 | $region = getenv("LEANCLOUD_REGION"); 34 | if (!$region) { 35 | $region = Region::CN; 36 | } 37 | $this->setRegion($region); 38 | $this->routeCache = RouteCache::create($appId); 39 | } 40 | 41 | /** 42 | * Get instance of AppRouter. 43 | */ 44 | public static function getInstance($appId) { 45 | if (isset(self::$INSTANCES[$appId])) { 46 | return self::$INSTANCES[$appId]; 47 | } else { 48 | $router = new AppRouter($appId); 49 | self::$INSTANCES[$appId] = $router; 50 | return $router; 51 | } 52 | } 53 | 54 | /** 55 | * Get app region default route host 56 | */ 57 | public function getRegionDefaultRoute($server_key) { 58 | $this->validate_server_key($server_key); 59 | return $this->getDefaultRoutes()[$server_key]; 60 | } 61 | 62 | /** 63 | * Set region 64 | * 65 | * See `LeanCloud\Region` for available regions. 66 | * 67 | * @param mixed $region 68 | */ 69 | public function setRegion($region) { 70 | if (is_numeric($region)) { 71 | $this->region = $region; 72 | } else { 73 | $this->region = Region::fromName($region); 74 | } 75 | } 76 | 77 | /** 78 | * Get and return route host by server type, or null if not found. 79 | */ 80 | public function getRoute($server_key) { 81 | $this->validate_server_key($server_key); 82 | $routes = $this->routeCache->read(); 83 | if (isset($routes[$server_key])) { 84 | return $routes[$server_key]; 85 | } 86 | $routes = $this->getRoutes(); 87 | if (!$routes) { 88 | $routes = $this->getDefaultRoutes(); 89 | } 90 | $this->routeCache->write($routes); 91 | return isset($routes[$server_key]) ? $routes[$server_key] : null; 92 | } 93 | 94 | private function getRouterUrl() { 95 | $url = getenv("LEANCLOUD_APP_ROUTER"); 96 | if (!$url) { 97 | $url = "https://app-router.leancloud.cn/2/route?appId="; 98 | } 99 | return "{$url}{$this->appId}"; 100 | } 101 | 102 | private function validate_server_key($server_key) { 103 | $routes = $this->getDefaultRoutes(); 104 | if (!isset($routes[$server_key])) { 105 | throw new IllegalArgumentException("Invalid server key."); 106 | } 107 | } 108 | 109 | /** 110 | * Detect region by app-id 111 | */ 112 | private function detectRegion() { 113 | if (!$this->appId) { 114 | return Region::CN_N1; 115 | } 116 | $parts = explode("-", $this->appId); 117 | if (count($parts) <= 1) { 118 | return Region::CN_N1; 119 | } else if ($parts[1] === "MdYXbMMI") { 120 | return Region::US; 121 | } else if ($parts[1] === "9Nh9j0Va") { 122 | return Region::CN_E1; 123 | } else { 124 | $this->region = Region::CN_N1; 125 | } 126 | } 127 | 128 | /** 129 | * Get routes remotely from app router, return array. 130 | */ 131 | private function getRoutes() { 132 | $routes = @json_decode(file_get_contents($this->getRouterUrl()), true); 133 | if (isset($routes[self::TTL_KEY])) { 134 | return $routes; 135 | } 136 | return null; 137 | } 138 | 139 | /** 140 | * Fallback default routes, if app router not available. 141 | */ 142 | private function getDefaultRoutes() { 143 | $host = self::$DEFAULT_REGION_ROUTE[$this->region]; 144 | 145 | return array( 146 | self::API_SERVER_KEY => $host, 147 | self::PUSH_SERVER_KEY => $host, 148 | self::STATS_SERVER_KEY => $host, 149 | self::ENGINE_SERVER_KEY => $host, 150 | self::RTM_ROUTER_SERVER_KEY => self::$DEFAULT_REGION_RTM_ROUTE[$this->region], 151 | self::TTL_KEY => 3600 152 | ); 153 | } 154 | 155 | 156 | } 157 | 158 | 159 | /** 160 | * Route cache 161 | * 162 | * Ideally we should use ACPu for caching, but it can be inconvenient to 163 | * install, esp. on Windows[1], thus we implement a naive file based 164 | * cache. 165 | * 166 | * [1]: https://stackoverflow.com/a/28124144/108112 167 | */ 168 | class RouteCache { 169 | private $filename; 170 | private $_cache; 171 | 172 | private function __construct($id) { 173 | $this->filename = sys_get_temp_dir() . "/route_{$id}.json"; 174 | } 175 | 176 | public static function create($id) { 177 | return new RouteCache($id); 178 | } 179 | 180 | /** 181 | * Serialize array and store in file, array must be json_encode safe. 182 | */ 183 | public function write($array) { 184 | $body = json_encode($array); 185 | if (file_put_contents($this->filename, $body, LOCK_EX) === false) { 186 | error_log("WARNING: failed to write route cache ({$this->filename}), performance may be degraded."); 187 | } else { 188 | $this->_cache = $array; 189 | } 190 | } 191 | 192 | /** 193 | * Read routes either from cache or file, return json_decoded array. 194 | */ 195 | public function read() { 196 | if ($this->_cache) { 197 | return $this->_cache; 198 | } 199 | $data = $this->readFile(); 200 | if (!empty($data)) { 201 | $this->_cache = $data; 202 | return $data; 203 | } 204 | return null; 205 | } 206 | 207 | private function readFile() { 208 | if (file_exists($this->filename)) { 209 | $fp = fopen($this->filename, "rb"); 210 | $body = null; 211 | if (flock($fp, LOCK_SH)) { 212 | $body = fread($fp, filesize($this->filename)); 213 | flock($fp, LOCK_UN); 214 | } 215 | fclose($fp); 216 | if (!empty($body)) { 217 | $data = @json_decode($body, true); 218 | if (!empty($data)) { 219 | return $data; 220 | } 221 | } 222 | } 223 | return null; 224 | } 225 | } -------------------------------------------------------------------------------- /src/LeanCloud/BatchRequestError.php: -------------------------------------------------------------------------------- 1 | errors[] = $error; 39 | return $this; 40 | } 41 | 42 | /** 43 | * Get all error response 44 | * 45 | * @return array 46 | */ 47 | public function getAll() { 48 | return $this->errors; 49 | } 50 | 51 | /** 52 | * Get first error response as map 53 | * 54 | * Returns associative array of following format: 55 | * 56 | * `{"code": 101, "error": "error message", "request": {...}}` 57 | * 58 | * @return array|null 59 | */ 60 | public function getFirst() { 61 | return isset($this->errors[0]) ? $this->errors[0] : null; 62 | } 63 | 64 | /** 65 | * Contains error response or not 66 | * 67 | * @return bool 68 | */ 69 | public function isEmpty() { 70 | return count($this->errors) == 0; 71 | } 72 | 73 | public function __toString() { 74 | $message = $this->message; 75 | if (!$this->isEmpty()) { 76 | $message .= json_encode($this->errors); 77 | } 78 | return __CLASS__ . ": [{$this->code}]: {$message}\n"; 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /src/LeanCloud/Bytes.php: -------------------------------------------------------------------------------- 1 | byteArray = $byteArray; 25 | return $bytes; 26 | } 27 | 28 | /** 29 | * Create Bytes from base64 encoded string 30 | * 31 | * @param string $data Base64 encoded string 32 | * @return Bytes 33 | */ 34 | public static function createFromBase64Data($data) { 35 | $bytes = new Bytes(); 36 | 37 | // convert unpacked associative array to sequence array 38 | $byteMap = unpack('C*', base64_decode($data)); 39 | forEach($byteMap as $byte) { 40 | $bytes->byteArray[] .= $byte; 41 | } 42 | return $bytes; 43 | } 44 | 45 | /** 46 | * Get byte array 47 | * 48 | * @return array 49 | */ 50 | public function getByteArray() { 51 | return $this->byteArray; 52 | } 53 | 54 | /** 55 | * Get string representation of byte array 56 | * 57 | * @return string 58 | */ 59 | public function asString() { 60 | $str = ""; 61 | forEach($this->byteArray as $byte) { 62 | $str .= chr($byte); 63 | } 64 | return $str; 65 | } 66 | 67 | /** 68 | * Encode to LeanCloud bytes type 69 | * 70 | * @return array 71 | */ 72 | public function encode() { 73 | return array( 74 | "__type" => "Bytes", 75 | "base64" => base64_encode($this->asString())); 76 | } 77 | 78 | } 79 | 80 | -------------------------------------------------------------------------------- /src/LeanCloud/CloudException.php: -------------------------------------------------------------------------------- 1 | status = $status; 34 | $this->method = $method; 35 | $this->url = $url; 36 | } 37 | 38 | public function __toString() { 39 | $req = $this->method ? ": {$this->method} {$this->url}": ""; 40 | return __CLASS__ . ": [{$this->code}] {$this->message}{$req}\n"; 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /src/LeanCloud/Engine/FunctionError.php: -------------------------------------------------------------------------------- 1 | status = $status; 17 | } 18 | 19 | public function __toString() { 20 | return __CLASS__ . ": [{$this->code}] {$this->message}\n"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/LeanCloud/Engine/LaravelEngine.php: -------------------------------------------------------------------------------- 1 | request->header($key); 29 | } 30 | 31 | /** 32 | * Get request body string 33 | * 34 | * @return string 35 | */ 36 | protected function getBody() { 37 | return $this->request->getContent(); 38 | } 39 | 40 | /** 41 | * Laravel middleware entry point 42 | * 43 | * @param \Illuminate\Http\Reuqest $request Laravel request 44 | * @param \Closure $next Laravel closure 45 | * @return mixed 46 | */ 47 | public function handle($request, $next) { 48 | $this->request = $request; 49 | $this->dispatch($request->method(), 50 | $request->url()); 51 | return $next($this->request); 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/LeanCloud/Engine/SlimEngine.php: -------------------------------------------------------------------------------- 1 | add(new SlimEngine()); 13 | * ``` 14 | * 15 | * @link http://www.slimframework.com/docs/concepts/middleware.html 16 | */ 17 | class SlimEngine extends LeanEngine { 18 | 19 | /** 20 | * Get request header value 21 | * 22 | * @param string $key Header key 23 | * @return string 24 | */ 25 | protected function getHeaderLine($key) { 26 | return $this->request->getHeaderLine($key); 27 | } 28 | 29 | /** 30 | * Get request body string 31 | * 32 | * @return string 33 | */ 34 | protected function getBody() { 35 | return $this->request->getBody()->getContents(); 36 | } 37 | 38 | /* 39 | * Ideally we would like to write to Slim response and send 40 | * the response to client. But we did not yet find a good way 41 | * to end the request as Slime middleware. As a work around, 42 | * we fallback to PHP native functions to do that. Pull request 43 | * is welcome. 44 | * 45 | * @see LeanEngine::withHeader LeanEngine::send 46 | */ 47 | // protected function withHeader($key, $val) {} 48 | // protected function send($key, $val) {} 49 | 50 | /** 51 | * Slim middleware entry point 52 | * 53 | * @param \Psr\Http\Message\ServerRequestInterface $request PSR7 request 54 | * @param \Psr\Http\Message\ResponseInterface $response PSR7 response 55 | * @param callable $next Next middleware 56 | * @return \Psr\Http\Message\ResponseInterface 57 | */ 58 | public function __invoke($request, $response, $next) { 59 | $this->request = $request; 60 | $this->response = $response; 61 | $this->dispatch($request->getMethod(), 62 | $request->getUri()); 63 | return $next($this->request, $this->response); 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/LeanCloud/GeoPoint.php: -------------------------------------------------------------------------------- 1 | = -90.0 && 34 | $longitude <= 180.0 && $longitude >= -180.0) { 35 | $this->latitude = $latitude; 36 | $this->longitude = $longitude; 37 | } else { 38 | throw new \InvalidArgumentException("Invalid latitude or " . 39 | "longitude for geo point"); 40 | } 41 | } 42 | 43 | /** 44 | * @return number 45 | */ 46 | public function getLatitude() { 47 | return $this->latitude; 48 | } 49 | 50 | /** 51 | * @return number 52 | */ 53 | public function getLongitude() { 54 | return $this->longitude; 55 | } 56 | 57 | /** 58 | * Compute distance (in radians) to a geo point 59 | * 60 | * @param GeoPoint $point Other geo point 61 | * @return number 62 | */ 63 | public function radiansTo(GeoPoint $point) { 64 | $d2r = M_PI / 180.0; 65 | $lat1rad = $this->getLatitude() * $d2r; 66 | $lon1rad = $this->getLongitude() * $d2r; 67 | $lat2rad = $point->getLatitude() * $d2r; 68 | $lon2rad = $point->getLongitude() * $d2r; 69 | $deltaLat = $lat1rad - $lat2rad; 70 | $deltaLon = $lon1rad - $lon2rad; 71 | $sinLat = sin($deltaLat / 2); 72 | $sinLon = sin($deltaLon / 2); 73 | $a = $sinLat * $sinLat + 74 | cos($lat1rad) * cos($lat2rad) * $sinLon * $sinLon; 75 | $a = min(1.0, $a); 76 | return 2 * asin(sqrt($a)); 77 | } 78 | 79 | /** 80 | * Compute distance (in kilometers) to other geo point 81 | * 82 | * @param GeoPoint $point Other geo point 83 | * @return number 84 | */ 85 | public function kilometersTo(GeoPoint $point) { 86 | return $this->radiansTo($point) * 6371.0; 87 | } 88 | 89 | /** 90 | * Compute distance (in miles) to other geo point 91 | * 92 | * @param GeoPoint $point Other geo point 93 | * @return number 94 | */ 95 | public function milesTo(GeoPoint $point) { 96 | return $this->radiansTo($point) * 3958.8; 97 | } 98 | 99 | public function encode() { 100 | return array( 101 | '__type' => 'GeoPoint', 102 | 'latitude' => $this->latitude, 103 | 'longitude' => $this->longitude 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/LeanCloud/Object.php: -------------------------------------------------------------------------------- 1 | = 70200) { 5 | throw new \RuntimeException("'Object` was reserved by PHP 7.2, use 'LeanObject' instead, see https://url.leanapp.cn/php72-object-deprecated"); 6 | } else { 7 | $filename = sys_get_temp_dir() . "/php72-object-deprecated"; 8 | 9 | if (!file_exists($filename)) { 10 | touch($filename); 11 | error_log("Warning: 'Object' was deprecated, use 'LeanObject' instead, see https://url.leanapp.cn/php72-object-deprecated"); 12 | } 13 | 14 | class_alias('\LeanCloud\LeanObject', '\LeanCloud\Object'); 15 | } 16 | -------------------------------------------------------------------------------- /src/LeanCloud/Operation/ArrayOperation.php: -------------------------------------------------------------------------------- 1 | key = $key; 51 | $this->value = $val; 52 | $this->opType = $opType; 53 | } 54 | 55 | /** 56 | * Get key of field the operation applies to. 57 | * 58 | * @return string 59 | */ 60 | public function getKey() { 61 | return $this->key; 62 | } 63 | 64 | /** 65 | * Get type of operation 66 | * 67 | * @return string 68 | */ 69 | public function getOpType() { 70 | return $this->opType; 71 | } 72 | 73 | /** 74 | * Get value of operation 75 | * 76 | * @return mixed 77 | */ 78 | public function getValue() { 79 | return $this->value; 80 | } 81 | 82 | /** 83 | * Encode to JSON represented operation 84 | * 85 | * @return array 86 | */ 87 | public function encode() { 88 | return array( 89 | "__op" => $this->getOpType(), 90 | "objects" => Client::encode($this->value), 91 | ); 92 | } 93 | 94 | /** 95 | * Add objects of this operation to old array 96 | * 97 | * @param array $oldval Old array of objects 98 | * @return array Merged new array 99 | */ 100 | private function add($oldval) { 101 | return array_merge($oldval, $this->getValue()); 102 | } 103 | 104 | /** 105 | * Add objects of this operation, uniquely, to old array. 106 | * 107 | * Note duplicated items in old array will remain duplicate. 108 | * 109 | * @param array $oldval Old array of objects 110 | * @return array 111 | */ 112 | private function addUnique($oldval) { 113 | $newval = $oldval; // New result array 114 | $found = array(); // Hash map of objects with objectId as key 115 | forEach($oldval as $obj) { 116 | if (($obj instanceof LeanObject) && ($obj->getObjectId())) { 117 | $found[$obj->getObjectId()] = true; 118 | } 119 | } 120 | forEach($this->getValue() as $obj) { 121 | if (($obj instanceof LeanObject) && ($obj->getObjectId())) { 122 | if (isset($found[$obj->getObjectId()])) { 123 | // skip duplicate object 124 | } else { 125 | $found[$obj->getObjectId()] = true; 126 | $newval[] = $obj; 127 | } 128 | } else if (!in_array($obj, $newval)) { 129 | $newval[] = $obj; 130 | } 131 | } 132 | return $newval; 133 | } 134 | 135 | /** 136 | * Remove objects of this operation from old array. 137 | * 138 | * @param array $oldval Old array of objects 139 | * @return array 140 | */ 141 | private function remove($oldval) { 142 | $newval = array(); 143 | $remove = $this->getValue(); // items to remove 144 | forEach($oldval as $item) { 145 | if (!in_array($item, $remove)) { 146 | $newval[] = $item; 147 | } 148 | } 149 | return $newval; 150 | } 151 | 152 | /** 153 | * Apply this operation based on old array. 154 | * 155 | * @param array $oldval Old array 156 | * @return array 157 | * @throws RuntimeException 158 | */ 159 | public function applyOn($oldval) { 160 | if (!$oldval) { $oldval = array();} 161 | 162 | if (!is_array($oldval)) { 163 | throw new \RuntimeException("Operation incompatible" . 164 | " with previous value."); 165 | } 166 | 167 | // TODO: Ensure behaviours of adding and removing associative array 168 | if ($this->getOpType() === "Add") { 169 | return $this->add($oldval); 170 | } 171 | if ($this->getOpType() === "AddUnique") { 172 | return $this->addUnique($oldval); 173 | } 174 | if ($this->getOpType() === "Remove") { 175 | return $this->remove($oldval); 176 | } 177 | throw new \RuntimeException("Operation type {$this->getOptype()}" . 178 | " not supported."); 179 | } 180 | 181 | /** 182 | * Merge this operation into a (previous) operation. 183 | * 184 | * @param IOperation $prevOp 185 | * @return IOperation 186 | */ 187 | public function mergeWith($prevOp) { 188 | if (!$prevOp) { 189 | return $this; 190 | } else if ($prevOp instanceof SetOperation) { 191 | if (!is_array($prevOp->getValue())) { 192 | throw new \RuntimeException("Operation incompatible " . 193 | "with previous value."); 194 | } 195 | return new SetOperation($this->key, 196 | $this->applyOn($prevOp->getValue())); 197 | } else if (($prevOp instanceof ArrayOperation) && 198 | ($this->getOpType() === $prevOp->getOpType())) { 199 | if ($this->getOpType() === "Remove") { 200 | $objects = array_merge($prevOp->getValue(), $this->getValue()); 201 | } else { 202 | $objects = $this->applyOn($prevOp->getValue()); 203 | } 204 | return new ArrayOperation($this->key, 205 | $objects, 206 | $this->getOpType()); 207 | } else if ($prevOp instanceof DeleteOperation) { 208 | if ($this->getOpType() === "Remove") { 209 | return $prevOp; 210 | } else { 211 | return new SetOperation($this->getKey(), $this->applyOn(null)); 212 | } 213 | } else { 214 | throw new \RuntimeException("Operation incompatible with" . 215 | " previous one."); 216 | } 217 | } 218 | } 219 | 220 | -------------------------------------------------------------------------------- /src/LeanCloud/Operation/DeleteOperation.php: -------------------------------------------------------------------------------- 1 | key = $key; 19 | } 20 | 21 | /** 22 | * Get key of field the operation applies to. 23 | * 24 | * @return string 25 | */ 26 | public function getKey() { 27 | return $this->key; 28 | } 29 | 30 | /** 31 | * Encode to JSON represented operation 32 | * 33 | * @return array 34 | */ 35 | public function encode() { 36 | return array("__op" => "Delete"); 37 | } 38 | 39 | /** 40 | * Apply this operation on an old value. 41 | * 42 | * @param mixed $oldval 43 | * @return null 44 | */ 45 | public function applyOn($oldval=null) { 46 | return null; 47 | } 48 | 49 | /** 50 | * Merge this operation with (previous) operation. 51 | * 52 | * @param IOperation $prevOp 53 | * @return IOperation 54 | */ 55 | public function mergeWith($prevOp) { 56 | return $this; 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/LeanCloud/Operation/IOperation.php: -------------------------------------------------------------------------------- 1 | key = $key; 34 | $this->value = $val; 35 | } 36 | 37 | /** 38 | * Get key of field the operation applies to 39 | * 40 | * @return string 41 | */ 42 | public function getKey() { 43 | return $this->key; 44 | } 45 | 46 | /** 47 | * Get value of operation 48 | * 49 | * @return number 50 | */ 51 | public function getValue() { 52 | return $this->value; 53 | } 54 | 55 | /** 56 | * Encode to JSON represented operation 57 | * 58 | * @return string json represented string 59 | */ 60 | public function encode() { 61 | return array("__op" => "Increment", 62 | "amount" => $this->value); 63 | } 64 | 65 | /** 66 | * Apply operation on old value and returns new one 67 | * 68 | * @param mixed $oldval 69 | * @return mixed 70 | */ 71 | public function applyOn($oldval) { 72 | $oldval = is_null($oldval) ? 0 : $oldval; 73 | if (is_numeric($oldval)) { 74 | return $this->value + $oldval; 75 | } 76 | throw new \RuntimeException("Operation incompatible with previous value."); 77 | } 78 | 79 | /** 80 | * Merge this operation into a (previous) operation. 81 | * 82 | * @param IOperation $prevOp 83 | * @return IOperation 84 | */ 85 | public function mergeWith($prevOp) { 86 | if (!$prevOp) { 87 | return $this; 88 | } else if ($prevOp instanceof SetOperation) { 89 | return new SetOperation($this->getKey(), 90 | $this->applyOn($prevOp->getValue())); 91 | } else if ($prevOp instanceof IncrementOperation) { 92 | return new IncrementOperation($this->getKey(), 93 | $this->applyOn($prevOp->getValue())); 94 | } else if ($prevOp instanceof DeleteOperation){ 95 | return new SetOperation($this->getKey(), $this->getValue()); 96 | } else { 97 | throw new \RuntimeException("Operation incompatible with previous one."); 98 | } 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /src/LeanCloud/Operation/RelationOperation.php: -------------------------------------------------------------------------------- 1 | key = $key; 53 | // The op order here ensures add wins over remove 54 | $this->remove($removes); 55 | $this->add($adds); 56 | } 57 | 58 | /** 59 | * Get key of field the operation applies to. 60 | * 61 | * @return string 62 | */ 63 | public function getKey() { 64 | return $this->key; 65 | } 66 | 67 | /** 68 | * Get target className of relation 69 | * 70 | * @return string 71 | */ 72 | public function getTargetClassName() { 73 | return $this->targetClassName; 74 | } 75 | 76 | /** 77 | * Encode to JSON represented operation 78 | * 79 | * @return array 80 | */ 81 | public function encode() { 82 | $adds = array("__op" => "AddRelation", 83 | "objects" => array()); 84 | $removes = array("__op" => "RemoveRelation", 85 | "objects" => array()); 86 | forEach($this->objects_to_add as $obj) { 87 | $adds["objects"][] = $obj->getPointer(); 88 | } 89 | forEach($this->objects_to_remove as $obj) { 90 | $removes["objects"][] = $obj->getPointer(); 91 | } 92 | 93 | if (empty($this->objects_to_remove)) { 94 | return $adds; 95 | } 96 | if (empty($this->objects_to_add)) { 97 | return $removes; 98 | } 99 | return array("__op" => "Batch", 100 | "ops" => array($adds, $removes)); 101 | } 102 | 103 | /** 104 | * Add object(s) to relation 105 | * 106 | * @param array $objects LeanObject(s) to add 107 | */ 108 | private function add($objects) { 109 | if (empty($objects)) { return; } 110 | if (!$this->targetClassName) { 111 | $this->targetClassName = current($objects)->getClassName(); 112 | } 113 | forEach($objects as $obj) { 114 | if (!$obj->getObjectId()) { 115 | throw new \RuntimeException("Cannot add unsaved object" . 116 | " to relation."); 117 | } 118 | if ($obj->getClassName() !== $this->targetClassName) { 119 | throw new \RuntimeException("LeanObject type incompatible" . 120 | " with relation."); 121 | } 122 | if (isset($this->objects_to_remove[$obj->getObjectID()])) { 123 | unset($this->objects_to_remove[$obj->getObjectID()]); 124 | } 125 | $this->objects_to_add[$obj->getObjectId()] = $obj; 126 | } 127 | } 128 | 129 | /** 130 | * Remove object(s) from relation 131 | * 132 | * @param array $objects LeanObject(s) to remove 133 | */ 134 | private function remove($objects) { 135 | if (empty($objects)) { return; } 136 | if (!$this->targetClassName) { 137 | $this->targetClassName = current($objects)->getClassName(); 138 | } 139 | forEach($objects as $obj) { 140 | if (!$obj->getObjectId()) { 141 | throw new \RuntimeException("Cannot remove unsaved object" . 142 | " from relation."); 143 | } 144 | if ($obj->getClassName() !== $this->targetClassName) { 145 | throw new \RuntimeException("LeanObject type incompatible" . 146 | " with relation."); 147 | } 148 | if (isset($this->objects_to_add[$obj->getObjectID()])) { 149 | unset($this->objects_to_add[$obj->getObjectID()]); 150 | } 151 | $this->objects_to_remove[$obj->getObjectId()] = $obj; 152 | } 153 | } 154 | 155 | /** 156 | * Apply the operation on previous relation 157 | * 158 | * @param Relation $relation Previous relation 159 | * @param LeanObject $object Parent of relation 160 | * @return Relation 161 | * @throws RuntimeException 162 | */ 163 | public function applyOn($relation, $object=null) { 164 | if (!$relation) { 165 | return new Relation($object, $this->getKey(), 166 | $this->getTargetClassName()); 167 | } 168 | if (!($relation instanceof Relation)) { 169 | throw new \RuntimeException("Operation incompatible with " . 170 | "previous value."); 171 | } 172 | // TODO: check target class 173 | return $relation; 174 | } 175 | 176 | /** 177 | * Merge with (previous) operation 178 | * 179 | * @param IOperation $prevOp Previous operation 180 | * @return IOperation 181 | */ 182 | public function mergeWith($prevOp) { 183 | if (!$prevOp) { 184 | return $this; 185 | } 186 | if ($prevOp instanceof RelationOperation) { 187 | $adds = array_merge($this->objects_to_add, 188 | $prevOp->objects_to_add); 189 | $removes = array_merge($this->objects_to_remove, 190 | $prevOp->objects_to_remove); 191 | return new RelationOperation($this->getKey(), $adds, $removes); 192 | } else { 193 | throw new \RuntimeException("Operation incompatible with " . 194 | "previous one."); 195 | } 196 | } 197 | } 198 | 199 | -------------------------------------------------------------------------------- /src/LeanCloud/Operation/SetOperation.php: -------------------------------------------------------------------------------- 1 | key = $key; 35 | $this->value = $val; 36 | } 37 | 38 | /** 39 | * Get key of field the operation applies to 40 | * 41 | * @return string 42 | */ 43 | public function getKey() { 44 | return $this->key; 45 | } 46 | 47 | /** 48 | * Get value of operation 49 | * 50 | * @return mixed 51 | */ 52 | public function getValue() { 53 | return $this->value; 54 | } 55 | 56 | /** 57 | * Encode to JSON represented operation 58 | * 59 | * @return array 60 | */ 61 | public function encode() { 62 | return Client::encode($this->value); 63 | } 64 | 65 | /** 66 | * Apply operation on old value and returns new one 67 | * 68 | * @param mixed $oldval 69 | * @return mixed 70 | */ 71 | public function applyOn($oldval) { 72 | return $this->value; 73 | } 74 | 75 | /** 76 | * Merge this operation with (previous) operation 77 | * 78 | * @param IOperation $prevOp 79 | * @return IOperation 80 | */ 81 | public function mergeWith($prevOp) { 82 | return $this; 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /src/LeanCloud/Push.php: -------------------------------------------------------------------------------- 1 | data = $data; 32 | $this->options = $options; 33 | $this->options["prod"] = Client::$isProduction ? "prod": "dev"; 34 | } 35 | 36 | /** 37 | * Set notification data attributes 38 | * 39 | * Available attributes please see 40 | * https://leancloud.cn/docs/push_guide.html#%E6%8E%A8%E9%80%81%E6%B6%88%E6%81%AF 41 | * 42 | * @param string $key Attribute key 43 | * @param mixed $val Attribute value 44 | * @return self 45 | */ 46 | public function setData($key, $val) { 47 | $this->data[$key] = $val; 48 | } 49 | 50 | /** 51 | * Set general option for notificiation 52 | * 53 | * Available options please see 54 | * https://leancloud.cn/docs/push_guide.html#%E6%8E%A8%E9%80%81%E6%B6%88%E6%81%AF 55 | * 56 | * There are helper methods for setting most of options, use those 57 | * if possible. Use this when no helper present, e.g. to enable 58 | * "dev" environment in iOS: 59 | * 60 | * ```php 61 | * $push->setOption("prod", "dev"); 62 | * ``` 63 | * 64 | * @param string $key Option key 65 | * @param mixed $val Option value 66 | * @return self 67 | * @see self::setWhere, self::setChannels, self::setPushTime ... 68 | */ 69 | public function setOption($key, $val) { 70 | $this->options[$key] = $val; 71 | return $this; 72 | } 73 | 74 | /** 75 | * Set target channels 76 | * 77 | * @param array $channels List of channel names 78 | * @return self 79 | * @see self::setOption() 80 | */ 81 | public function setChannels($channels) { 82 | return $this->setOption("channels", $channels); 83 | } 84 | 85 | /** 86 | * Filter target devices by query 87 | * 88 | * The query must be over _Installation table. 89 | * 90 | * @param Query $query A query over _Installation 91 | * @return self 92 | * @see self::setOption() 93 | */ 94 | public function setWhere(Query $query) { 95 | if ($query->getClassName() != "_Installation") { 96 | throw new \RuntimeException("Query must be over " . 97 | "_Installation table."); 98 | } 99 | return $this->setOption("where", $query); 100 | } 101 | 102 | /** 103 | * Schedule a time to send message 104 | * 105 | * @param DateTime $time Time to send message to clients 106 | * @return self 107 | * @see self::setOption() 108 | */ 109 | public function setPushTime(\DateTime $time) { 110 | return $this->setOption("push_time", $time); 111 | } 112 | 113 | /** 114 | * Set expiration interval for message 115 | * 116 | * When client received message after the interval, it will not be 117 | * displayed to user. 118 | * 119 | * @param int $interval Number of seconds (from now) to expire message 120 | * @return self 121 | * @see self::setOption() 122 | */ 123 | public function setExpirationInterval($interval) { 124 | return $this->setOption("expiration_interval", $interval); 125 | } 126 | 127 | /** 128 | * Set expiration time for message 129 | * 130 | * When client received message after the specified time, it will 131 | * not be displayed to user. 132 | * 133 | * @param DateTime $time Time to expire message 134 | * @return self 135 | * @see self::setOption() 136 | */ 137 | public function setExpirationTime(\DateTime $time) { 138 | return $this->setOption("expiration_time", $time); 139 | } 140 | 141 | /** 142 | * Enable smooth push for message 143 | * 144 | * @param int $flowControl clients to push per second, 145 | * a value <1000 is equivalent to 1000. 146 | * @return self 147 | * @see self::setOption() 148 | */ 149 | public function setFlowControl($flowControl) { 150 | return $this->setOption("flow_control", $flowControl); 151 | } 152 | 153 | /** 154 | * Encode to JSON representation 155 | * 156 | * @return array 157 | */ 158 | public function encode() { 159 | $out = $this->options; 160 | $out["data"] = $this->data; 161 | $expire = isset($this->options["expiration_time"]) ? $this->options["expiration_time"] : null; 162 | if (($expire instanceof \DateTime) || 163 | ($expire instanceof \DateTimeImmutable)) { 164 | $out["expiration_time"] = Client::formatDate($expire); 165 | } 166 | $pushTime = isset($this->options["push_time"]) ? $this->options["push_time"] : null; 167 | if (($pushTime instanceof \DateTime) || 168 | ($pushTime instanceof \DateTimeImmutable)){ 169 | $out["push_time"] = Client::formatDate($pushTime); 170 | } 171 | if (isset($this->options["where"])) { 172 | $query = $this->options["where"]->encode(); 173 | $out["where"] = json_decode($query["where"], true); 174 | } 175 | return $out; 176 | } 177 | 178 | /** 179 | * Send notification to LeanCloud 180 | * 181 | * @return array 182 | */ 183 | public function send() { 184 | $out = $this->encode(); 185 | $resp = Client::post("/push", $out); 186 | return $resp; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/LeanCloud/Region.php: -------------------------------------------------------------------------------- 1 | getRelation($key)` instead. 41 | * 42 | * @param LeanObject $parent Parent object 43 | * @param string $key Field key on parent object 44 | * @param string $className ClassName the object relatedTo 45 | */ 46 | public function __construct($parent, $key, $className=null) { 47 | $this->parent = $parent; 48 | $this->key = $key; 49 | $this->targetClassName = $className; 50 | } 51 | 52 | /** 53 | * Encode to JSON representation of relation. 54 | * 55 | * @return array 56 | */ 57 | public function encode() { 58 | return array("__type" => "Relation", 59 | "className" => $this->targetClassName); 60 | } 61 | 62 | /** 63 | * Attempt to set and validate parent of relation 64 | * 65 | * @param LeanObject $parent Parent object of relation 66 | * @param string $key Field key 67 | * @throws RuntimeException 68 | */ 69 | public function setParentAndKey($parent, $key) { 70 | if ($this->parent && $this->parent != $parent) { 71 | throw new \RuntimeException("Relation does not belong to the object"); 72 | } 73 | if ($this->key && $this->key != $key) { 74 | throw new \RuntimeException("Relation does not belong to the field"); 75 | } 76 | $this->parent = $parent; 77 | $this->key = $key; 78 | } 79 | 80 | /** 81 | * Get target className of relation 82 | * 83 | * @return string 84 | */ 85 | public function getTargetClassName() { 86 | return $this->targetClassName; 87 | } 88 | 89 | /** 90 | * Add object(s) to the field as relation 91 | * 92 | * @param object|array $objects LeanObject(s) to add 93 | */ 94 | public function add($objects) { 95 | if (!is_array($objects)) { $objects = array($objects); } 96 | $op = new RelationOperation($this->key, $objects, null); 97 | $this->parent->set($this->key, $op); 98 | if (!$this->targetClassName) { 99 | $this->targetClassName = $op->getTargetClassName(); 100 | } 101 | } 102 | 103 | /** 104 | * Remove object(s) from the field 105 | * 106 | * @param object|array $objects LeanObject(s) to remove 107 | */ 108 | public function remove($objects) { 109 | if (!is_array($objects)) { $objects = array($objects); } 110 | $op = new RelationOperation($this->key, null, $objects); 111 | $this->parent->set($this->key, $op); 112 | if (!$this->targetClassName) { 113 | $this->targetClassName = $op->getTargetClassName(); 114 | } 115 | } 116 | 117 | /** 118 | * Query on the target class of relation 119 | * 120 | * @return Query 121 | */ 122 | public function getQuery() { 123 | if ($this->targetClassName) { 124 | $query = new Query($this->targetClassName); 125 | } else { 126 | $query = new Query($this->parent->getClassName()); 127 | $query->addOption("redirectClassNameForKey", $this->key); 128 | } 129 | $query->relatedTo($this->key, $this->parent); 130 | return $query; 131 | } 132 | 133 | /** 134 | * Query on the parent class where child is in the relation 135 | * 136 | * @param LeanObject $child Child object 137 | * @return Query 138 | */ 139 | public function getReverseQuery(LeanObject $child) { 140 | $query = new Query($this->parent->getClassName()); 141 | $query->equalTo($this->key, $child->getPointer()); 142 | return $query; 143 | } 144 | 145 | } 146 | 147 | -------------------------------------------------------------------------------- /src/LeanCloud/Role.php: -------------------------------------------------------------------------------- 1 | getUsers()`, which 13 | * is an instance of Relation, where users can be added or 14 | * removed. 15 | * 16 | * Roles can belong to role as well, which can be got by 17 | * `$role->getRoles()`, where roles can be added or removed. 18 | * 19 | * @see ACL, Relation 20 | */ 21 | class Role extends LeanObject { 22 | /** 23 | * Table name on LeanCloud 24 | * @var string 25 | */ 26 | protected static $className = "_Role"; 27 | 28 | /** 29 | * Set name of role 30 | * 31 | * The name can contain only alphanumeric characters, _, -, and 32 | * space. It cannot be changed after being saved. 33 | * 34 | * @return Role 35 | */ 36 | public function setName($name) { 37 | $this->set("name", $name); 38 | return $this; 39 | } 40 | 41 | /** 42 | * Get name of role 43 | * 44 | * @return string 45 | */ 46 | public function getName() { 47 | return $this->get("name"); 48 | } 49 | 50 | /** 51 | * Get a relation of users that belongs to this role 52 | * 53 | * @return Relation 54 | */ 55 | public function getUsers() { 56 | return $this->getRelation("users"); 57 | } 58 | 59 | /** 60 | * Get a relation of roles that belongs to this role 61 | * 62 | * @return Relation 63 | */ 64 | public function getRoles() { 65 | return $this->getRelation("roles"); 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /src/LeanCloud/SMS.php: -------------------------------------------------------------------------------- 1 | $v) { 34 | if (!isset($options[$k])) { 35 | unset($options[$k]); 36 | } 37 | } 38 | $options["mobilePhoneNumber"] = $phoneNumber; 39 | Client::post("/requestSmsCode", $options); 40 | } 41 | 42 | /** 43 | * Verify SMS code 44 | * 45 | * @param string $phoneNumber 46 | * @param string $smsCode 47 | */ 48 | public static function verifySmsCode($phoneNumber, $smsCode) { 49 | Client::post("/verifySmsCode/{$smsCode}?mobilePhoneNumber={$phoneNumber}", 50 | null); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/LeanCloud/SaveOption.php: -------------------------------------------------------------------------------- 1 | fetchWhenSave)) { 32 | $params["fetchWhenSave"] = $this->fetchWhenSave ? true : false; 33 | } 34 | if (!is_null($this->where)) { 35 | if ($this->where instanceof Query) { 36 | $out = $this->where->encode(); 37 | $params["where"] = $out["where"]; 38 | } else { 39 | throw new \RuntimeException("where of SaveOption must be Query object."); 40 | } 41 | } 42 | return $params; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/LeanCloud/Storage/CookieStorage.php: -------------------------------------------------------------------------------- 1 | expireIn = time() + $seconds; 57 | $this->path = $path; 58 | $this->domain = $domain; 59 | } 60 | 61 | /** 62 | * Set value by key 63 | * 64 | * @param string $key 65 | * @param mixed $val 66 | * @param int $seconds Number of seconds to live 67 | * @return $this 68 | */ 69 | public function set($key, $val, $seconds=null) { 70 | $expire = $seconds ? (time() + seconds) : $this->expireIn; 71 | setcookie($key, $val, $expire, $this->path, $this->domain); 72 | } 73 | 74 | /** 75 | * Get value by key 76 | * 77 | * @param string $key 78 | * @return mixed 79 | */ 80 | public function get($key) { 81 | if (isset($_COOKIE[$key])) { 82 | return $_COOKIE[$key]; 83 | } 84 | return null; 85 | } 86 | 87 | /** 88 | * Remove key from storage 89 | * 90 | * @param string $key 91 | */ 92 | public function remove($key) { 93 | setcookie($key, false, 1); 94 | } 95 | 96 | /** 97 | * Clear all data in storage 98 | * 99 | * @throws RuntimeException 100 | */ 101 | public function clear() { 102 | throw new \RuntimeException("Not implemented error."); 103 | } 104 | } 105 | 106 | -------------------------------------------------------------------------------- /src/LeanCloud/Storage/IStorage.php: -------------------------------------------------------------------------------- 1 | multipartEncode(array( 22 | "name" => $key, 23 | "mimeType" => $mimeType, 24 | "content" => $content, 25 | ), array( 26 | "op" => "upload", 27 | "sha" => hash("sha1", $content) 28 | ), $boundary); 29 | 30 | $headers[] = "User-Agent: " . Client::getVersionString(); 31 | $headers[] = "Content-Type: multipart/form-data;" . 32 | " boundary={$boundary}"; 33 | // $headers[] = "Content-Length: " . strlen($body); 34 | $headers[] = "Authorization: {$this->getAuthToken()}"; 35 | $url = $this->getUploadUrl(); 36 | $ch = curl_init($url); 37 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); 38 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 39 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 40 | curl_setopt($ch, CURLOPT_POST, 1); 41 | curl_setopt($ch, CURLOPT_POSTFIELDS, $body); 42 | $resp = curl_exec($ch); 43 | $respCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 44 | $respType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); 45 | $error = curl_error($ch); 46 | $errno = curl_errno($ch); 47 | curl_close($ch); 48 | 49 | /** type of error: 50 | * - curl error 51 | * - http status error 4xx, 5xx 52 | * - rest api error 53 | */ 54 | if ($errno > 0) { 55 | throw new \RuntimeException("CURL ($url) error: " . 56 | "{$errno} {$error}", 57 | $errno); 58 | } 59 | 60 | $data = json_decode($resp, true); 61 | if ($data["code"] != 0) { 62 | throw new \RuntimeException("Upload to Qcloud ({$url}) failed: ". 63 | "{$data['code']} {$data['message']}", 64 | $data["code"]); 65 | } 66 | return $data; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/LeanCloud/Uploader/QiniuUploader.php: -------------------------------------------------------------------------------- 1 | multipartEncode(array( 35 | "name" => $key, 36 | "mimeType" => $mimeType, 37 | "content" => $content, 38 | ), array( 39 | "token" => $this->getAuthToken(), 40 | "key" => $key, 41 | "crc32" => $this->crc32Data($content) 42 | ), $boundary); 43 | 44 | $headers[] = "User-Agent: " . Client::getVersionString(); 45 | $headers[] = "Content-Type: multipart/form-data;" . 46 | " boundary={$boundary}"; 47 | $headers[] = "Content-Length: " . strlen($body); 48 | 49 | $url = $this->getUploadUrl(); 50 | $ch = curl_init($url); 51 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); 52 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 53 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 54 | curl_setopt($ch, CURLOPT_POST, 1); 55 | curl_setopt($ch, CURLOPT_POSTFIELDS, $body); 56 | $resp = curl_exec($ch); 57 | $respCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 58 | $respType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); 59 | $error = curl_error($ch); 60 | $errno = curl_errno($ch); 61 | curl_close($ch); 62 | 63 | /** type of error: 64 | * - curl error 65 | * - http status error 4xx, 5xx 66 | * - rest api error 67 | */ 68 | if ($errno > 0) { 69 | throw new \RuntimeException("CURL ($url) error: " . 70 | "{$errno} {$error}", 71 | $errno); 72 | } 73 | 74 | $data = json_decode($resp, true); 75 | if (isset($data["error"])) { 76 | $code = isset($data["code"]) ? $data["code"] : 1; 77 | throw new \RuntimeException("Upload to Qiniu ({$url}) failed: ". 78 | "{$code} {$data['error']}", $code); 79 | } 80 | return $data; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/LeanCloud/Uploader/S3Uploader.php: -------------------------------------------------------------------------------- 1 | getUploadUrl()) { 16 | throw new \RuntimeException("Please initialize with pre-signed url."); 17 | } 18 | $headers[] = "User-Agent: " . Client::getVersionString(); 19 | $headers[] = "Content-Type: $mimeType"; 20 | $url = $this->getUploadUrl(); 21 | $ch = curl_init($url); 22 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); 23 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 24 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 25 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT"); 26 | curl_setopt($ch, CURLOPT_POSTFIELDS, $content); 27 | $resp = curl_exec($ch); 28 | $respCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 29 | $respType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); 30 | $error = curl_error($ch); 31 | $errno = curl_errno($ch); 32 | curl_close($ch); 33 | 34 | if ($errno > 0) { 35 | throw new \RuntimeException("CURL ({$url}) error: " . 36 | "{$errno} {$error}", 37 | $errno); 38 | } 39 | 40 | if ($respCode >= "300") { 41 | $S3Error = simplexml_load_string($resp); 42 | throw new \RuntimeException("Upload to S3 ({$url}) failed: " . 43 | "{$S3Error->Code} {$S3Error->Message}"); 44 | } 45 | return true; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/LeanCloud/Uploader/SimpleUploader.php: -------------------------------------------------------------------------------- 1 | $val) { 47 | $body .= "--{$boundary}\r\n"; 48 | $body .= "Content-Disposition: form-data; name=\"{$key}\"\r\n\r\n"; 49 | $body .= "{$val}\r\n"; 50 | } 51 | 52 | if (!empty($file)) { 53 | $mimeType = "application/octet-stream"; 54 | if (isset($file["mimeType"])) { 55 | $mimeType = $file["mimeType"]; 56 | } 57 | $fieldname = static::getFileFieldName(); 58 | // escape quotes in file name 59 | $filename = filter_var($file["name"], 60 | FILTER_SANITIZE_MAGIC_QUOTES); 61 | 62 | $body .= "--{$boundary}\r\n"; 63 | $body .= "Content-Disposition: form-data; name=\"{$fieldname}\"; filename=\"{$filename}\"\r\n"; 64 | $body .= "Content-Type: {$mimeType}\r\n\r\n"; 65 | $body .= "{$file['content']}\r\n"; 66 | } 67 | 68 | // append end frontier 69 | $body .= "--{$boundary}--\r\n"; 70 | 71 | return $body; 72 | } 73 | 74 | /** 75 | * Initialize uploader with url and auth token 76 | * 77 | * @param string $uploadUrl File provider url 78 | * @param string $authToken Auth token for file provider 79 | */ 80 | public function initialize($uploadUrl, $authToken) { 81 | $this->uploadUrl = $uploadUrl; 82 | $this->authToken = $authToken; 83 | } 84 | 85 | public function getUploadUrl() { 86 | return $this->uploadUrl; 87 | } 88 | 89 | public function getAuthToken() { 90 | return $this->authToken; 91 | } 92 | 93 | abstract public function upload($content, $mimeType, $key); 94 | } 95 | -------------------------------------------------------------------------------- /src/autoload.php: -------------------------------------------------------------------------------- 1 | encode(); 22 | $this->assertEquals(true, $out["id123"]["read"]); 23 | $this->assertEquals(true, $out["id123"]["write"]); 24 | } 25 | 26 | /** 27 | * Empty ACL should be encoded as object, instead of array. 28 | * 29 | * @link https://github.com/leancloud/php-sdk/issues/84 30 | */ 31 | public function testEmptyACL() { 32 | $acl = new ACL(); 33 | $out = $acl->encode(); 34 | $this->assertEquals("{}", json_encode($out)); 35 | } 36 | 37 | public function testSetPublicAccess() { 38 | $acl = new ACL(); 39 | $acl->setPublicReadAccess(true); 40 | $out = $acl->encode(); 41 | $this->assertEquals(true, $out[ACL::PUBLIC_KEY]["read"]); 42 | $this->assertEquals(true, $acl->getPublicReadAccess()); 43 | 44 | $acl->setPublicWriteAccess(false); 45 | $out = $acl->encode(); 46 | $this->assertEquals(false, $out[ACL::PUBLIC_KEY]["write"]); 47 | $this->assertEquals(false, $acl->getPublicWriteAccess()); 48 | } 49 | 50 | public function testSetUserAccess() { 51 | $user = new User(null, "id123"); 52 | $acl = new ACL(); 53 | $acl->setReadAccess($user, true); 54 | $out = $acl->encode(); 55 | $this->assertEquals(true, $out[$user->getObjectId()]["read"]); 56 | $this->assertEquals(true, $acl->getReadAccess($user)); 57 | 58 | $acl->setWriteAccess($user, false); 59 | $out = $acl->encode(); 60 | $this->assertEquals(false, $out[$user->getObjectId()]["write"]); 61 | $this->assertEquals(false, $acl->getWriteAccess($user)); 62 | } 63 | 64 | public function testSetRoleAccess() { 65 | $role = new Role(); 66 | $role->setName("admin"); 67 | $role->setACL(new ACL()); 68 | $acl = new ACL(); 69 | $acl->setRoleReadAccess($role, true); 70 | $out = $acl->encode(); 71 | $this->assertEquals(true, $out["role:admin"]["read"]); 72 | $this->assertEquals(true, $acl->getRoleReadAccess($role)); 73 | 74 | $acl->setRoleWriteAccess($role, false); 75 | $out = $acl->encode(); 76 | $this->assertEquals(false, $out["role:admin"]["write"]); 77 | $this->assertEquals(false, $acl->getRoleWriteAccess($role)); 78 | } 79 | 80 | public function testSetRoleAccessWithRoleName() { 81 | $acl = new ACL(); 82 | $acl->setRoleReadAccess("admin", true); 83 | $out = $acl->encode(); 84 | $this->assertEquals(true, $out["role:admin"]["read"]); 85 | 86 | $acl->setRoleWriteAccess("admin", false); 87 | $out = $acl->encode(); 88 | $this->assertEquals(false, $out["role:admin"]["write"]); 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /test/AppRouterTest.php: -------------------------------------------------------------------------------- 1 | genId(12)); 20 | $router->setRegion("CN_E1"); 21 | $router->setRegion("US"); 22 | $router->setRegion(Region::CN_E1); 23 | $router->setRegion(Region::US); 24 | } 25 | 26 | public function testGetRoute() { 27 | $this->markTestSkipped("app-router no longer available"); 28 | $appid = getenv("LEANCLOUD_APP_ID"); 29 | $router = AppRouter::getInstance($appid); 30 | $host = $router->getRoute(AppRouter::API_SERVER_KEY); 31 | $domain = getenv("LEANCLOUD_WILDCARD_DOMAIN"); 32 | $this->assertEquals("{$this->getShortAppId($appid)}.api.{$domain}", $host); 33 | 34 | $host = $router->getRoute(AppRouter::ENGINE_SERVER_KEY); 35 | $domain = getenv("LEANCLOUD_WILDCARD_DOMAIN"); 36 | $this->assertEquals("{$this->getShortAppId($appid)}.engine.{$domain}", $host); 37 | } 38 | 39 | public function testGetRouteWhenAppRouterNotAvailable() { 40 | $appid = $this->genId(18); 41 | $router = AppRouter::getInstance($appid); 42 | $router_url = getenv("LEANCLOUD_APP_ROUTER"); 43 | putenv("LEANCLOUD_APP_ROUTER=http://localhost:4000/route?appId="); 44 | $this->assertEquals($router->getRegionDefaultRoute(AppRouter::ENGINE_SERVER_KEY), 45 | $router->getRoute(AppRouter::ENGINE_SERVER_KEY)); 46 | 47 | putenv("LEANCLOUD_APP_ROUTER={$router_url}"); 48 | $host = $router->getRoute(AppRouter::API_SERVER_KEY); 49 | $this->assertEquals($router->getRegionDefaultRoute(AppRouter::API_SERVER_KEY), 50 | $host); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /test/BytesTest.php: -------------------------------------------------------------------------------- 1 | encode(); 10 | $this->assertEquals("Bytes", $out["__type"]); 11 | $this->assertEquals("", $out["base64"]); 12 | } 13 | 14 | public function testEncodeArray() { 15 | $bytes = Bytes::createFromByteArray(array(72, 101, 108, 108, 111)); 16 | $out = $bytes->encode(); 17 | $this->assertEquals("Bytes", $out["__type"]); 18 | $this->assertEquals(base64_encode("Hello"), $out["base64"]); 19 | } 20 | 21 | public function testCreateFromEmptyString() { 22 | $bytes = Bytes::createFromBase64Data(base64_encode("")); 23 | $this->assertEmpty($bytes->getByteArray()); 24 | $this->assertEquals("", $bytes->asString()); 25 | } 26 | 27 | public function testCreateFromBase64() { 28 | $bytes = Bytes::createFromByteArray(array(72, 101, 108, 108, 111)); 29 | $bytes1 = Bytes::createFromBase64Data(base64_encode("Hello")); 30 | $this->assertEquals($bytes->getByteArray(), $bytes1->getByteArray()); 31 | $this->assertEquals("Hello", $bytes->asString()); 32 | $this->assertEquals("Hello", $bytes1->asString()); 33 | } 34 | 35 | public function testEncodeCreateFromBase64() { 36 | $bytes = Bytes::createFromBase64Data(base64_encode("Hello")); 37 | $out = $bytes->encode(); 38 | $this->assertEquals(base64_encode("Hello"), $out["base64"]); 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /test/CloudTest.php: -------------------------------------------------------------------------------- 1 | setUsername("alice"); 17 | $user->setPassword("blabla"); 18 | $user->setEmail("alice@example.com"); 19 | try { 20 | $user->signUp(); 21 | } catch (CloudException $ex) { 22 | // skip 23 | } 24 | } 25 | 26 | public static function tearDownAfterClass() { 27 | // destroy default user if present 28 | try { 29 | $user = User::logIn("alice", "blabla"); 30 | $user->destroy(); 31 | } catch (CloudException $ex) { 32 | // skip 33 | } 34 | } 35 | 36 | 37 | 38 | public function testGetKeys() { 39 | $name = uniqid(); 40 | Cloud::define($name, function($params, $user) { 41 | return "hello"; 42 | }); 43 | $this->assertContains($name, Cloud::getKeys()); 44 | } 45 | 46 | public function testDefineFunctionWithoutArg() { 47 | // user function are free to accept positional arguments, 48 | // this one should not error out. 49 | Cloud::define("hello", function() { 50 | return "hello"; 51 | }); 52 | $result = Cloud::run("hello", array("name" => "alice"), null); 53 | $this->assertEquals("hello", $result); 54 | } 55 | 56 | public function testFunctionWithoutArg() { 57 | Cloud::define("hello", function($params, $user) { 58 | return "hello"; 59 | }); 60 | 61 | $result = Cloud::run("hello", array(), null); 62 | $this->assertEquals("hello", $result); 63 | } 64 | 65 | public function testFunctionWithArg() { 66 | Cloud::define("sayHello", function($params, $user) { 67 | return "hello {$params['name']}"; 68 | }); 69 | 70 | $result = Cloud::run("sayHello", array("name" => "alice"), null); 71 | $this->assertEquals("hello alice", $result); 72 | } 73 | 74 | public function testFunctionAcceptMeta() { 75 | Cloud::define("getMeta", function($params, $user, $meta) { 76 | return $meta['remoteAddress']; 77 | }); 78 | 79 | $result = Cloud::run("getMeta", 80 | array("name" => "alice"), 81 | null, 82 | array("remoteAddress" => "10.0.0.1") 83 | ); 84 | $this->assertEquals("10.0.0.1", $result); 85 | } 86 | 87 | public function testRemoteFunction() { 88 | // Assumes [LeanFunction] is deployed at this application's LeanEngine. 89 | // [LeanFunction]: https://github.com/leancloud/LeanFunction 90 | $response = Cloud::runRemote("hello", []); 91 | $result = $response["result"]; 92 | $this->assertEquals("Hello world!", $result); 93 | } 94 | 95 | public function testRemoteFunctionWithSession() { 96 | // See testRemoteFunction for dependencies. 97 | try { 98 | User::logIn("alice", "blabla"); 99 | } catch (\LeanCloud\CloudException $e) { 100 | // skip 101 | } 102 | $token = User::getCurrentSessionToken(); 103 | $response = Cloud::runRemote("echo-session-token", [], $token); 104 | $result = $response["result"]; 105 | $this->assertEquals($token, $result); 106 | } 107 | 108 | public function testClassHook() { 109 | forEach(array("beforeSave", "afterSave", 110 | "beforeUpdate", "afterUpdate", 111 | "beforeDelete", "afterDelete") as $hookName) { 112 | $count = 42; 113 | call_user_func( 114 | array("LeanCloud\Engine\Cloud", $hookName), 115 | "TestObject", 116 | function($obj, $user) use (&$count) { 117 | $count += 1; 118 | } 119 | ); 120 | Cloud::runHook("TestObject", $hookName, null, null); 121 | $this->assertEquals(43, $count); 122 | } 123 | } 124 | 125 | public function testOnVerifiedHook() { 126 | // use a closure to ensure hook being executed 127 | $count = 42; 128 | Cloud::onVerified("sms", function($user) use (&$count) { 129 | $count += 1; 130 | }); 131 | Cloud::runOnVerified("sms", null); 132 | $this->assertEquals(43, $count); 133 | } 134 | 135 | public function testOnLogin() { 136 | $count = 42; 137 | Cloud::onLogin(function($user) use (&$count) { 138 | $count += 1; 139 | }); 140 | Cloud::runOnLogin(null); 141 | $this->assertEquals(43, $count); 142 | } 143 | 144 | public function testOnInsight() { 145 | $count = 42; 146 | Cloud::onInsight(function($job) use (&$count) { 147 | $count += 1; 148 | }); 149 | Cloud::runOnInsight(null); 150 | $this->assertEquals(43, $count); 151 | } 152 | 153 | } 154 | 155 | -------------------------------------------------------------------------------- /test/DeleteOperationTest.php: -------------------------------------------------------------------------------- 1 | encode(); 13 | $this->assertEquals("Delete", $out["__op"]); 14 | } 15 | 16 | public function testApplyOperation() { 17 | $op = new DeleteOperation("tags"); 18 | $this->assertNull($op->applyOn()); 19 | } 20 | 21 | public function testMergeWithAnyOp() { 22 | $op = new DeleteOperation("tags"); 23 | 24 | $op2 = $op->mergeWith(null); 25 | $this->assertTrue($op2 instanceof DeleteOperation); 26 | 27 | $op2 = $op->mergeWith(new SetOperation("tags", "foo")); 28 | $this->assertTrue($op2 instanceof DeleteOperation); 29 | 30 | $op2 = $op->mergeWith(new DeleteOperation("tags")); 31 | $this->assertTrue($op2 instanceof DeleteOperation); 32 | 33 | $op2 = $op->mergeWith(new IncrementOperation("tags", 1)); 34 | $this->assertTrue($op2 instanceof DeleteOperation); 35 | 36 | $op2 = $op->mergeWith(new ArrayOperation("tags", 37 | array("frontend"), 38 | "Add")); 39 | $this->assertTrue($op2 instanceof DeleteOperation); 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /test/FileTest.php: -------------------------------------------------------------------------------- 1 | assertEquals("", $file->getName()); 19 | } 20 | 21 | public function testInitializeMimeType() { 22 | $file = new File("test.txt"); 23 | $this->assertEquals("text/plain", $file->getMimeType()); 24 | 25 | $file = new File("test.txt", null, "image/png"); 26 | $this->assertEquals("image/png", $file->getMimeType()); 27 | } 28 | 29 | public function testCreateWithURL() { 30 | $file = File::createWithUrl("blabla.png", "https://leancloud.cn/favicon.png"); 31 | $this->assertEquals("blabla.png", $file->getName()); 32 | $this->assertEquals("https://leancloud.cn/favicon.png", $file->getUrl()); 33 | $this->assertEquals("image/png", $file->getMimeType()); 34 | } 35 | 36 | public function testCreateWithLocalFile() { 37 | $file = File::createWithLocalFile(__FILE__); 38 | $this->assertEquals("FileTest.php", $file->getName()); 39 | } 40 | 41 | public function testSaveTextFile() { 42 | $file = File::createWithData("test.txt", "Hello World!"); 43 | $this->assertNull($file->getKey()); 44 | $file->save(); 45 | $this->assertNotEmpty($file->getObjectId()); 46 | $this->assertNotEmpty($file->getKey()); 47 | $this->assertNotEmpty($file->getUrl()); 48 | $this->assertNotEmpty($file->getName()); 49 | $this->assertEquals("text/plain", $file->getMimeType()); 50 | $content = file_get_contents($file->getUrl()); 51 | $this->assertEquals("Hello World!", $content); 52 | 53 | $file->destroy(); 54 | } 55 | 56 | public function testSaveUTF8TextFile() { 57 | $file = File::createWithData("testChinese.txt", "你好,中国!"); 58 | $file->save(); 59 | $this->assertNotEmpty($file->getUrl()); 60 | $this->assertEquals("text/plain", $file->getMimeType()); 61 | $content = file_get_contents($file->getUrl()); 62 | $this->assertEquals("你好,中国!", $content); 63 | 64 | $file->destroy(); 65 | } 66 | 67 | public function testSaveLocalFileWithMimeTypeAndName() { 68 | $file = File::createWithLocalFile(__FILE__, "application/x-php", "FileTest.php"); 69 | $file->save(); 70 | $this->assertNotEmpty($file->getUrl()); 71 | $this->assertEquals("application/x-php", $file->getMimeType()); 72 | 73 | $file->destroy(); 74 | } 75 | 76 | public function testSaveWithSpecifiedKeyWithoutMasterKey() { 77 | $file = File::createWithData("test.txt", "Hello World!"); 78 | $file->setKey("abc"); 79 | $this->assertEquals("abc", $file->getKey()); 80 | $unsupportedKeyError = "Unsupported file key. Please use masterKey to set file key."; 81 | $this->setExpectedException("LeanCloud\CloudException", $unsupportedKeyError, 1); 82 | $file->save(); 83 | $this->assertEmpty($file->getObjectId()); 84 | } 85 | 86 | public function testSaveExternalFile() { 87 | $file = File::createWithUrl("blabla.png", "https://leancloud.cn/favicon.png"); 88 | $file->save(); 89 | $this->assertNotEmpty($file->getObjectId()); 90 | $this->assertEquals("blabla.png", $file->getName()); 91 | $this->assertEquals("https://leancloud.cn/favicon.png", $file->getUrl()); 92 | $this->assertEquals("image/png", $file->getMimeType()); 93 | 94 | $file->destroy(); 95 | } 96 | 97 | public function testFetchFile() { 98 | $file = File::createWithData("testFetch.txt", "你好,中国!"); 99 | $file->save(); 100 | $file2 = File::fetch($file->getObjectId()); 101 | // `uploadResult.getUrl() != fetchResult.getUrl()` is a feature 102 | // $this->assertEquals($file->getUrl(), $file2->getUrl()); 103 | $this->assertEquals($file->getName(), $file2->getName()); 104 | $this->assertEquals($file->getSize(), $file2->getSize()); 105 | 106 | $file->destroy(); 107 | } 108 | 109 | public function testGetCreatedAtAndUpdatedAt() { 110 | $file = File::createWithData("testTimestamp.txt", "你好,中国!"); 111 | $file->save(); 112 | $this->assertNotEmpty($file->getUrl()); 113 | $this->assertNotEmpty($file->getCreatedAt()); 114 | $this->assertTrue($file->getCreatedAt() instanceof \DateTime); 115 | 116 | $file->destroy(); 117 | } 118 | 119 | public function testMetaData() { 120 | $file = File::createWithData("testMetadata.txt", "你好,中国!"); 121 | $file->setMeta("language", "zh-CN"); 122 | $file->setMeta("bool", false); 123 | $file->setMeta("downloads", 100); 124 | $file->save(); 125 | $file2 = File::fetch($file->getObjectId()); 126 | $this->assertEquals("zh-CN", $file2->getMeta("language")); 127 | $this->assertEquals(false, $file2->getMeta("bool")); 128 | $this->assertEquals(100, $file2->getMeta("downloads")); 129 | 130 | $file->destroy(); 131 | } 132 | 133 | /* 134 | * leancloud/php-sdk#46 135 | */ 136 | public function testSaveObjectWithFile() { 137 | $obj = new LeanObject("TestObject"); 138 | $obj->set("name", "alice"); 139 | 140 | $file = File::createWithData("test.txt", "你好,中国!"); 141 | $obj->addIn("files", $file); 142 | $obj->save(); 143 | 144 | $this->assertNotEmpty($obj->getObjectId()); 145 | $this->assertNotEmpty($file->getObjectId()); 146 | $this->assertNotEmpty($file->getUrl()); 147 | 148 | $file->destroy(); 149 | $obj->destroy(); 150 | } 151 | 152 | } 153 | 154 | -------------------------------------------------------------------------------- /test/GeoPointTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(0.0, $point->getLatitude()); 11 | $this->assertEquals(0.0, $point->getLongitude()); 12 | } 13 | 14 | public function testInitializeGeoPoint() { 15 | $point = new GeoPoint(90, 180); 16 | $this->assertEquals(90, $point->getLatitude()); 17 | $this->assertEquals(180, $point->getLongitude()); 18 | 19 | $point = new GeoPoint(90, -180); 20 | $point = new GeoPoint(-90, 180); 21 | $point = new GeoPoint(-90, -180); 22 | } 23 | 24 | public function testEncodeGeoPoint() { 25 | $point = new GeoPoint(39.9, 116.4); 26 | 27 | $out = $point->encode(); 28 | $this->assertEquals("GeoPoint", $out["__type"]); 29 | $this->assertEquals(39.9, $out["latitude"]); 30 | $this->assertEquals(116.4, $out["longitude"]); 31 | } 32 | 33 | public function testInvalidPoints() { 34 | $this->setExpectedException("InvalidArgumentException", 35 | "Invalid latitude or longitude " . 36 | "for geo point"); 37 | new GeoPoint(180, 90); 38 | } 39 | 40 | public function testRadiansDistance() { 41 | $point = new GeoPoint(39.9, 116.4); 42 | $this->assertEquals(0, $point->radiansTo($point)); 43 | 44 | // it should be equal to to M_PI 45 | $rad = $point->radiansTo(new GeoPoint(-39.9, -63.6)); 46 | $this->assertEquals(M_PI, $rad, '', 0.0000001); 47 | 48 | $rad = $point->radiansTo(new GeoPoint(0, 116.4)); 49 | $this->assertEquals(39.9 * M_PI / 180.0, $rad, '', 0.0000001); 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/IncrementOperationTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($op->getKey(), "score"); 11 | } 12 | 13 | public function testApplyOperation() { 14 | $op = new IncrementOperation("score", 20); 15 | $val = $op->applyOn(2); 16 | $this->assertEquals($val, 22); 17 | 18 | $val = $op->applyOn(22); 19 | $this->assertEquals($val, 42); 20 | 21 | $val = $op->applyOn("7"); 22 | $this->assertEquals($val, 27); 23 | } 24 | 25 | public function testIncrementOnString() { 26 | $op = new IncrementOperation("score", 1); 27 | $this->setExpectedException("RuntimeException", 28 | "Operation incompatible with previous value."); 29 | $op->applyOn("alice"); 30 | } 31 | 32 | public function testIncrementNonNumericAmount() { 33 | $this->setExpectedException("InvalidArgumentException", 34 | "Operand must be number."); 35 | $op = new IncrementOperation("score", "a"); 36 | } 37 | 38 | public function testOperationEncode() { 39 | $op = new IncrementOperation("score", 2); 40 | $out = $op->encode(); 41 | $this->assertEquals($out["__op"], "Increment"); 42 | $this->assertEquals($out["amount"], 2); 43 | 44 | $op = new IncrementOperation("score", -2); 45 | $out = $op->encode(); 46 | $this->assertEquals($out["__op"], "Increment"); 47 | $this->assertEquals($out["amount"], -2); 48 | } 49 | 50 | public function testMergeWithNull() { 51 | $op = new IncrementOperation("score", 2); 52 | $op2 = $op->mergeWith(null); 53 | $out = $op2->encode(); 54 | $this->assertEquals($out["__op"], "Increment"); 55 | $this->assertEquals($out["amount"], 2); 56 | } 57 | 58 | public function testMergeWithSetOperation() { 59 | $op = new IncrementOperation("score", 2); 60 | $op2 = $op->mergeWith(new SetOperation("score", 40)); 61 | $this->assertTrue($op2 instanceof SetOperation); 62 | $this->assertEquals($op2->getValue(), 42); 63 | } 64 | 65 | public function testMergeWithIncrementOperation() { 66 | $op = new IncrementOperation("score", 2); 67 | $op2 = $op->mergeWith(new IncrementOperation("score", 3)); 68 | $this->assertTrue($op2 instanceof IncrementOperation); 69 | $this->assertEquals($op2->getValue(), 5); 70 | } 71 | 72 | public function testMergeWithDelete() { 73 | $op = new IncrementOperation("score", 2); 74 | $op2 = $op->mergeWith(new DeleteOperation("score")); 75 | $this->assertTrue($op2 instanceof SetOperation); 76 | $this->assertEquals($op2->getValue(), 2); 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /test/MasterTest.php: -------------------------------------------------------------------------------- 1 | setKey("abc"); 20 | $this->assertEquals("abc", $file->getKey()); 21 | $file->save(); 22 | $this->assertNotEmpty($file->getObjectId()); 23 | $this->assertNotEmpty($file->getName()); 24 | 25 | $this->assertStringEndsWith("abc", $file->getKey()); 26 | $url = $file->getUrl(); 27 | $parsedUrl = parse_url($url); 28 | $path = $parsedUrl["path"]; 29 | $this->assertStringEndsWith("abc", $path); 30 | 31 | $this->assertEquals("text/plain", $file->getMimeType()); 32 | $content = file_get_contents($url); 33 | $this->assertEquals("Hello World!", $content); 34 | 35 | $file->destroy(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/Php72ObjectDeprecated.php: -------------------------------------------------------------------------------- 1 | set("name", "Alice in wonderland"); 25 | $obj->set("score", 81); 26 | $obj->save(); 27 | 28 | $this->assertTrue($obj instanceof Object); 29 | $this->assertTrue($obj instanceof LeanObject); 30 | $this->assertNotEmpty($obj->getObjectId()); 31 | 32 | LeanObject::destroyAll([$obj]); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/PushTest.php: -------------------------------------------------------------------------------- 1 | "Hello world!", 22 | "badge" => 20, 23 | "sound" => "APP/media/sound.mp3" 24 | ); 25 | $push = new Push($data); 26 | $out = $push->encode(); 27 | $this->assertEquals($data, $out["data"]); 28 | } 29 | 30 | public function testSetData() { 31 | $push = new Push(array( 32 | "alert" => "Hello world!" 33 | )); 34 | $push->setData("badge", 20); 35 | $push->setData("sound", "APP/media/sound.mp3"); 36 | $out = $push->encode(); 37 | $this->assertEquals(array( 38 | "alert" => "Hello world!", 39 | "badge" => 20, 40 | "sound" => "APP/media/sound.mp3" 41 | ), $out["data"]); 42 | } 43 | 44 | public function testSetPushForMultiplatform() { 45 | $data = array( 46 | "ios" => array( 47 | "alert" => "Hello world!", 48 | "badge" => 20, 49 | "sound" => "APP/media/sound.mp3" 50 | ), 51 | "android" => array( 52 | "alert" => "Hello world!", 53 | "title" => "Hello from app", 54 | "action" => "app.action" 55 | ), 56 | "wp" => array( 57 | "alert" => "Hello world!", 58 | "title" => "Hello from app", 59 | "wp-param" => "/chat.xaml?NavigatedFrom=Toast Notification" 60 | ) 61 | ); 62 | $push = new Push($data); 63 | $out = $push->encode(); 64 | $this->assertEquals($data, $out["data"]); 65 | } 66 | 67 | public function testDefaultProd() { 68 | $push = new Push(array( 69 | "alert" => "Hello world!" 70 | )); 71 | $out = $push->encode(); 72 | $this->assertEquals(Client::$isProduction, $out["prod"] == "prod"); 73 | } 74 | 75 | public function testSetProd() { 76 | $push = new Push(array( 77 | "alert" => "Hello world!" 78 | )); 79 | $push->setOption("prod", "dev"); 80 | $out = $push->encode(); 81 | $this->assertEquals("dev", $out["prod"]); 82 | } 83 | 84 | public function testSetChannels() { 85 | $push = new Push(array( 86 | "alert" => "Hello world!" 87 | )); 88 | $channels = array("vip", "premium"); 89 | $push->setChannels($channels); 90 | $out = $push->encode(); 91 | $this->assertEquals($channels, $out["channels"]); 92 | } 93 | 94 | public function testSetPushTime() { 95 | $push = new Push(array( 96 | "alert" => "Hello world!" 97 | )); 98 | $time = new DateTime(); 99 | $push->setPushTime($time); 100 | $out = $push->encode(); 101 | $time2 = new DateTime($out["push_time"]); 102 | $this->assertEquals($time->getTimestamp(), $time2->getTimestamp()); 103 | } 104 | 105 | public function testSetExpirationInterval() { 106 | $push = new Push(array( 107 | "alert" => "Hello world!" 108 | )); 109 | $push->setExpirationInterval(86400); 110 | $out = $push->encode(); 111 | $this->assertEquals(86400, $out["expiration_interval"]); 112 | } 113 | 114 | public function testSetExpirationTime() { 115 | $push = new Push(array( 116 | "alert" => "Hello world!" 117 | )); 118 | $date = new DateTime(); 119 | $push->setExpirationTime($date); 120 | $out = $push->encode(); 121 | $date2 = new DateTime($out["expiration_time"]); 122 | $this->assertEquals($date->getTimestamp(), $date2->getTimestamp()); 123 | } 124 | 125 | public function testSetWhere() { 126 | $push = new Push(array( 127 | "alert" => "Hello world!" 128 | )); 129 | $query = new Query("_Installation"); 130 | $date = new DateTime(); 131 | $query->lessThan("updatedAt", $date); 132 | $push->setWhere($query); 133 | $out = $push->encode(); 134 | $this->assertEquals(array( 135 | "updatedAt" => array( 136 | '$lt' => Client::encode($date) 137 | ) 138 | ), $out["where"]); 139 | } 140 | 141 | public function testSetFlowControl() { 142 | $push = new Push(array( 143 | "alert" => "Hello world!" 144 | )); 145 | $push->setFlowControl(3000); 146 | $out = $push->encode(); 147 | $this->assertEquals(3000, $out["flow_control"]); 148 | } 149 | 150 | public function testSendPush() { 151 | $push = new Push(array( 152 | "alert" => "Hello world!" 153 | )); 154 | $query = new Query("_Installation"); 155 | $query->equalTo("deviceType", "Android"); 156 | $push->setWhere($query); 157 | 158 | $at = new DateTime(); 159 | $at->add(new DateInterval("P1D")); 160 | $push->setPushTime($at); 161 | 162 | // $push->send(); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /test/RelationOperationTest.php: -------------------------------------------------------------------------------- 1 | setExpectedException("InvalidArgumentException", 20 | "Operands are empty."); 21 | $op = new RelationOperation("foo", array(), null); 22 | } 23 | 24 | public function testAddOpEncode() { 25 | $child1 = new LeanObject("TestObject", "ab123"); 26 | $op = new RelationOperation("foo", array($child1), null); 27 | $out = $op->encode(); 28 | $this->assertEquals("AddRelation", $out["__op"]); 29 | $this->assertEquals($child1->getPointer(), $out["objects"][0]); 30 | } 31 | 32 | public function testAddUnsavedObjects() { 33 | $child1 = new LeanObject("TestObject"); 34 | $this->setExpectedException("RuntimeException", 35 | "Cannot add unsaved object to relation."); 36 | $op = new RelationOperation("foo", array($child1), null); 37 | } 38 | 39 | public function testAddDuplicateObjects() { 40 | $child1 = new LeanObject("TestObject", "ab123"); 41 | $op = new RelationOperation("foo", array($child1, $child1), null); 42 | $out = $op->encode(); 43 | $this->assertEquals("AddRelation", $out["__op"]); 44 | $this->assertEquals(1, count($out["objects"])); 45 | $this->assertEquals($child1->getPointer(), $out["objects"][0]); 46 | } 47 | 48 | public function testRemoveOpEncode() { 49 | $child1 = new LeanObject("TestObject", "ab123"); 50 | $op = new RelationOperation("foo", null, array($child1)); 51 | $out = $op->encode(); 52 | $this->assertEquals("RemoveRelation", $out["__op"]); 53 | $this->assertEquals($child1->getPointer(), $out["objects"][0]); 54 | } 55 | 56 | public function testRemoveDuplicateObjects() { 57 | $child1 = new LeanObject("TestObject", "ab123"); 58 | $op = new RelationOperation("foo", null, array($child1, $child1)); 59 | $out = $op->encode(); 60 | $this->assertEquals("RemoveRelation", $out["__op"]); 61 | $this->assertEquals(1, count($out["objects"])); 62 | $this->assertEquals($child1->getPointer(), $out["objects"][0]); 63 | } 64 | 65 | public function testAddWinsOverRemove() { 66 | $child1 = new LeanObject("TestObject", "ab101"); 67 | $op = new RelationOperation("foo", 68 | array($child1), 69 | array($child1)); 70 | $out = $op->encode(); 71 | $this->assertEquals("AddRelation", $out["__op"]); 72 | $this->assertEquals(1, count($out["objects"])); 73 | $this->assertEquals($child1->getPointer(), $out["objects"][0]); 74 | } 75 | 76 | public function testAddAndRemove() { 77 | $child1 = new LeanObject("TestObject", "ab101"); 78 | $child2 = new LeanObject("TestObject", "ab102"); 79 | $child3 = new LeanObject("TestObject", "ab103"); 80 | $op = new RelationOperation("foo", 81 | array($child1, $child2), 82 | array($child2, $child3)); 83 | $out = $op->encode(); 84 | $this->assertEquals("Batch", $out["__op"]); 85 | 86 | $adds = $out["ops"][0]; 87 | $this->assertEquals("AddRelation", $adds["__op"]); 88 | $this->assertEquals(array($child1->getPointer(), $child2->getPointer()), 89 | $adds["objects"]); 90 | $removes = $out["ops"][1]; 91 | $this->assertEquals("RemoveRelation", $removes["__op"]); 92 | $this->assertEquals(array($child3->getPointer()), 93 | $removes["objects"]); 94 | } 95 | 96 | public function testMultipleClassesNotAllowed() { 97 | $child1 = new LeanObject("TestObject", "abc101"); 98 | $child2 = new LeanObject("Test2Object", "bac102"); 99 | $this->setExpectedException("RuntimeException", 100 | "LeanObject type incompatible with " . 101 | "relation."); 102 | $op = new RelationOperation("foo", 103 | array($child1), 104 | array($child2)); 105 | } 106 | 107 | public function testApplyOperation() { 108 | $child1 = new LeanObject("TestObject", "abc101"); 109 | $op = new RelationOperation("foo", array($child1), null); 110 | $parent = new LeanObject("Test2Object"); 111 | $val = $op->applyOn(null, $parent); 112 | $this->assertTrue($val instanceof Relation); 113 | $out = $val->encode(); 114 | $this->assertEquals("TestObject", $out["className"]); 115 | } 116 | 117 | public function testMergeWithNull() { 118 | $child1 = new LeanObject("TestObject", "abc101"); 119 | $op = new RelationOperation("foo", array($child1), null); 120 | $op2 = $op->mergeWith(null); 121 | $this->assertTrue($op2 instanceof RelationOperation); 122 | $this->assertEquals($op->encode(), $op2->encode()); 123 | } 124 | 125 | public function testMergeWithRelationOperation() { 126 | $child1 = new LeanObject("TestObject", "abc101"); 127 | $op = new RelationOperation("foo", array($child1), null); 128 | 129 | $child2 = new LeanObject("TestObject", "abc102"); 130 | $op2 = new RelationOperation("foo", null, array($child2)); 131 | 132 | $op3 = $op->mergeWith($op2); 133 | $this->assertTrue($op3 instanceof RelationOperation); 134 | $out = $op3->encode(); 135 | 136 | // it adds child1, removes child2 137 | $this->assertEquals("Batch", $out["__op"]); 138 | $this->assertEquals(array($child1->getPointer()), 139 | $out["ops"][0]["objects"]); 140 | $this->assertEquals(array($child2->getPointer()), 141 | $out["ops"][1]["objects"]); 142 | } 143 | 144 | } 145 | 146 | -------------------------------------------------------------------------------- /test/RelationTest.php: -------------------------------------------------------------------------------- 1 | getRelation("likes"); 18 | $out = $rel->encode(); 19 | $this->assertEquals("Relation", $out["__type"]); 20 | } 21 | 22 | public function testRelationClassEncode() { 23 | $obj = new LeanObject("TestObject"); 24 | $rel = $obj->getRelation("likes"); 25 | $out = $rel->encode(); 26 | $this->assertEquals("Relation", $out["__type"]); 27 | 28 | $child1 = new LeanObject("User", "abc101"); 29 | $rel->add($child1); 30 | $out = $rel->encode(); 31 | $this->assertEquals("User", $out["className"]); 32 | } 33 | 34 | public function testGetRelationOnTargetClass() { 35 | $obj = new LeanObject("TestObject", "id123"); 36 | $rel = new Relation($obj, "likes", "User"); 37 | $query = $rel->getQuery(); 38 | $this->assertEquals("User", $query->getClassName()); 39 | } 40 | 41 | public function testGetRelationQueryWithoutTargetClass() { 42 | $obj = new LeanObject("TestObject", "id123"); 43 | $rel = new Relation($obj, "likes"); 44 | $query = $rel->getQuery(); 45 | 46 | // the query should be made against the parent class, with 47 | // redirect key being set, so it will be redirected to target 48 | // class. 49 | $this->assertEquals("TestObject", $query->getClassName()); 50 | $out = $query->encode(); 51 | $this->assertEquals("likes", $out["redirectClassNameForKey"]); 52 | } 53 | 54 | public function getReverseQueryOnChildObject() { 55 | $obj = new LeanObject("TestObject", "id123"); 56 | $rel = new Relation($obj, "likes", "User"); 57 | $child = new LeanObject("User", "id124"); 58 | $query = $rel->getReverseQuery($child); 59 | $this->assertEquals("TestObject", $query->getClassName()); 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /test/RoleTest.php: -------------------------------------------------------------------------------- 1 | assertEquals("id123", $role->getObjectId()); 26 | } 27 | 28 | public function testGetChildrenAsRelation() { 29 | $role = new Role(); 30 | $this->assertTrue($role->getUsers() instanceof Relation); 31 | $this->assertTrue($role->getRoles() instanceof Relation); 32 | } 33 | 34 | public function testSaveRole() { 35 | $role = new Role(); 36 | $role->setName("admin"); 37 | 38 | $acl = new ACL(); 39 | $acl->setPublicWriteAccess(true); // so it can be destroyed 40 | $role->setACL($acl); 41 | 42 | $role->save(); 43 | $this->assertNotEmpty($role->getObjectId()); 44 | $this->assertTrue($role->getUsers() instanceof Relation); 45 | $this->assertTrue($role->getRoles() instanceof Relation); 46 | 47 | $role->destroy(); 48 | } 49 | 50 | } 51 | 52 | -------------------------------------------------------------------------------- /test/SetOperationTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($op->getKey(), "name"); 14 | 15 | $op = new SetOperation("story", "in wonderland"); 16 | $this->assertEquals($op->getKey(), "story"); 17 | } 18 | 19 | public function testApplyOperation() { 20 | $op = new SetOperation("name", "alice"); 21 | $val = $op->applyOn("alicia"); 22 | $this->assertEquals($val, "alice"); 23 | 24 | $val = $op->applyOn(42); 25 | $this->assertEquals($val, "alice"); 26 | } 27 | 28 | public function testOperationEncode() { 29 | $op = new SetOperation("name", "alice"); 30 | $this->assertEquals($op->encode(), "alice"); 31 | $op = new SetOperation("score", 70.0); 32 | $this->assertEquals($op->encode(), 70.0); 33 | 34 | $date = new DateTime(); 35 | $op = new SetOperation("released", $date); 36 | $out = $op->encode(); 37 | $this->assertEquals($out['__type'], "Date"); 38 | $this->assertEquals($out['iso'], 39 | Client::formatDate($date)); 40 | } 41 | 42 | public function testMergeWithAnyOp() { 43 | $op = new SetOperation("name", "alice"); 44 | 45 | $op2 = $op->mergeWith(null); 46 | $this->assertTrue($op2 instanceof SetOperation); 47 | $op2 = $op->mergeWith(new SetOperation("name", "jack")); 48 | $this->assertTrue($op2 instanceof SetOperation); 49 | $op2 = $op->mergeWith(new IncrementOperation("name", 1)); 50 | $this->assertTrue($op2 instanceof SetOperation); 51 | $op2 = $op->mergeWith(new DeleteOperation("name")); 52 | $this->assertTrue($op2 instanceof SetOperation); 53 | $op2 = $op->mergeWith(new ArrayOperation("name", array("jack"), "Add")); 54 | $this->assertTrue($op2 instanceof SetOperation); 55 | } 56 | 57 | } 58 | 59 | -------------------------------------------------------------------------------- /test/StorageTest.php: -------------------------------------------------------------------------------- 1 | set("null", null); 12 | $this->assertNull($storage->get("null")); 13 | 14 | $storage->set("bool", true); 15 | $this->assertTrue(true === $storage->get("bool")); 16 | 17 | $storage->set("int", 42); 18 | $this->assertEquals(42, $storage->get("int")); 19 | 20 | $storage->set("string", "bar"); 21 | $this->assertEquals("bar", $storage->get("string")); 22 | 23 | $date = new DateTime(); 24 | $storage->set("date", $date); 25 | $this->assertEquals($date, $storage->get("date")); 26 | 27 | $arr = array("a", "b"); 28 | $storage->set("array", $arr); 29 | $this->assertEquals($arr, $storage->get("array")); 30 | } 31 | } 32 | 33 | 34 | -------------------------------------------------------------------------------- /test/engine/index.php: -------------------------------------------------------------------------------- 1 | false); 36 | } else { 37 | return array("drop" => true); 38 | } 39 | }); 40 | 41 | Cloud::define("getMeta", function($params, $user, $meta) { 42 | return array("remoteAddress" => $meta["remoteAddress"]); 43 | }); 44 | 45 | Cloud::define("updateObject", function($params, $user) { 46 | $obj = $params["object"]; 47 | $obj->set("__testKey", 42); 48 | return $obj; 49 | }); 50 | 51 | Cloud::onLogin(function($user) { 52 | error_log("Logging a user"); 53 | return; 54 | }); 55 | 56 | Cloud::onInsight(function($job) { 57 | return; 58 | }); 59 | 60 | Cloud::onVerified("sms", function($user){ 61 | return; 62 | }); 63 | 64 | Cloud::beforeSave("TestObject", function($obj, $user) { 65 | $obj->set("__testKey", 42); 66 | }); 67 | 68 | Cloud::afterSave("TestObject", function($obj, $user) { 69 | return; 70 | }); 71 | 72 | Cloud::beforeDelete("TestObject", function($obj, $user) { 73 | return; 74 | }); 75 | 76 | $engine = new LeanEngine(); 77 | $engine->start(); 78 | 79 | --------------------------------------------------------------------------------