) will be sorted instead
+ * of the itself.
+ */
+jQuery.fn.sortElements = (function(){
+
+ var sort = [].sort;
+
+ return function(comparator, getSortable) {
+
+ getSortable = getSortable || function(){return this;};
+
+ var placements = this.map(function(){
+
+ var sortElement = getSortable.call(this),
+ parentNode = sortElement.parentNode,
+
+ // Since the element itself will change position, we have
+ // to have some way of storing it's original position in
+ // the DOM. The easiest way is to have a 'flag' node:
+ nextSibling = parentNode.insertBefore(
+ document.createTextNode(''),
+ sortElement.nextSibling
+ );
+
+ return function() {
+
+ if (parentNode === this) {
+ throw new Error(
+ "You can't sort elements if any one is a descendant of another."
+ );
+ }
+
+ // Insert before flag:
+ parentNode.insertBefore(this, nextSibling);
+ // Remove flag:
+ parentNode.removeChild(nextSibling);
+
+ };
+
+ });
+
+ return sort.call(this, comparator).each(function(i){
+ placements[i].call(getSortable.call(this));
+ });
+
+ };
+
+})();
+ $(window).load(function() {
+ var $document = $(document);
+ var $left = $('#left');
+ var $right = $('#right');
+ var $rightInner = $('#rightInner');
+ var $splitter = $('#splitter');
+ var $groups = $('#groups');
+ var $content = $('#content');
+
+ // Menu
+
+ // Hide deep packages and namespaces
+ $('ul span', $groups).click(function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ $(this)
+ .toggleClass('collapsed')
+ .parent()
+ .next('ul')
+ .toggleClass('collapsed');
+ }).click();
+
+ $active = $('ul li.active', $groups);
+ if ($active.length > 0) {
+ // Open active
+ $('> a > span', $active).click();
+ } else {
+ $main = $('> ul > li.main', $groups);
+ if ($main.length > 0) {
+ // Open first level of the main project
+ $('> a > span', $main).click();
+ } else {
+ // Open first level of all
+ $('> ul > li > a > span', $groups).click();
+ }
+ }
+
+ // Content
+
+ // Search autocompletion
+ var autocompleteFound = false;
+ var autocompleteFiles = {'c': 'class', 'co': 'constant', 'f': 'function', 'm': 'class', 'mm': 'class', 'p': 'class', 'mp': 'class', 'cc': 'class'};
+ var $search = $('#search input[name=q]');
+ $search
+ .autocomplete(ApiGen.elements, {
+ matchContains: true,
+ scrollHeight: 200,
+ max: 20,
+ noRecord: '',
+ highlight: function(value, term) {
+ var term = term.toUpperCase().replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1").replace(/[A-Z0-9]/g, function(m, offset) {
+ return offset === 0 ? '(?:' + m + '|^' + m.toLowerCase() + ')' : '(?:(?:[^<>]|<[^<>]*>)*' + m + '|' + m.toLowerCase() + ')';
+ });
+ return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)"), "$1 ");
+ },
+ formatItem: function(data) {
+ return data.length > 1 ? data[1].replace(/^(.+\\)(.+)$/, '$1 $2 ') : data[0];
+ },
+ formatMatch: function(data) {
+ return data[1];
+ },
+ formatResult: function(data) {
+ return data[1];
+ },
+ show: function($list) {
+ var $items = $('li span', $list);
+ var maxWidth = Math.max.apply(null, $items.map(function() {
+ return $(this).width();
+ }));
+ // 10px padding
+ $list
+ .width(Math.max(maxWidth + 10, $search.innerWidth()))
+ .css('left', $search.offset().left + $search.outerWidth() - $list.outerWidth());
+ }
+ }).result(function(event, data) {
+ autocompleteFound = true;
+ var location = window.location.href.split('/');
+ location.pop();
+ var parts = data[1].split(/::|$/);
+ var file = $.sprintf(ApiGen.config.templates[autocompleteFiles[data[0]]].filename, parts[0].replace(/\(\)/, '').replace(/[^\w]/g, '.'));
+ if (parts[1]) {
+ file += '#' + ('mm' === data[0] || 'mp' === data[0] ? 'm' : '') + parts[1].replace(/([\w]+)\(\)/, '_$1');
+ }
+ location.push(file);
+ window.location = location.join('/');
+
+ // Workaround for Opera bug
+ $(this).closest('form').attr('action', location.join('/'));
+ }).closest('form')
+ .submit(function() {
+ var query = $search.val();
+ if ('' === query) {
+ return false;
+ }
+ return !autocompleteFound && '' !== $('#search input[name=cx]').val();
+ });
+
+ // Save natural order
+ $('table.summary tr[data-order]', $content).each(function(index) {
+ do {
+ index = '0' + index;
+ } while (index.length < 3);
+ $(this).attr('data-order-natural', index);
+ });
+
+ // Switch between natural and alphabetical order
+ var $caption = $('table.summary', $content)
+ .filter(':has(tr[data-order])')
+ .find('caption');
+ $caption
+ .click(function() {
+ var $this = $(this);
+ var order = $this.data('order') || 'natural';
+ order = 'natural' === order ? 'alphabetical' : 'natural';
+ $this.data('order', order);
+ $.cookie('order', order, {expires: 365});
+ var attr = 'alphabetical' === order ? 'data-order' : 'data-order-natural';
+ $this
+ .closest('table')
+ .find('tr').sortElements(function(a, b) {
+ return $(a).attr(attr) > $(b).attr(attr) ? 1 : -1;
+ });
+ return false;
+ })
+ .addClass('switchable')
+ .attr('title', 'Switch between natural and alphabetical order');
+ if ((null === $.cookie('order') && 'alphabetical' === ApiGen.config.options.elementsOrder) || 'alphabetical' === $.cookie('order')) {
+ $caption.click();
+ }
+
+ // Open details
+ if (ApiGen.config.options.elementDetailsCollapsed) {
+ var trCollapsed = true;
+ $('tr', $content).filter(':has(.detailed)')
+ .click(function() {
+ var $this = $(this);
+ if (trCollapsed) {
+ $('.short', $this).hide();
+ $('.detailed', $this).show();
+ trCollapsed = false;
+ } else {
+ $('.short', $this).show();
+ $('.detailed', $this).hide();
+ trCollapsed = true;
+ }
+ });
+ }
+
+ // Splitter
+ var splitterWidth = $splitter.width();
+ var splitterPosition = $.cookie('splitter') ? parseInt($.cookie('splitter')) : null;
+ var splitterPositionBackup = $.cookie('splitterBackup') ? parseInt($.cookie('splitterBackup')) : null;
+ function setSplitterPosition(position)
+ {
+ splitterPosition = position;
+
+ $left.width(position);
+ $right.css('margin-left', position + splitterWidth);
+ $splitter.css('left', position);
+ }
+ function setContentWidth()
+ {
+ var width = $rightInner.width();
+ $rightInner
+ .toggleClass('medium', width <= 960)
+ .toggleClass('small', width <= 650);
+ }
+ $splitter.mousedown(function() {
+ $splitter.addClass('active');
+
+ $document.mousemove(function(event) {
+ if (event.pageX >= 230 && $document.width() - event.pageX >= 600 + splitterWidth) {
+ setSplitterPosition(event.pageX);
+ setContentWidth();
+ }
+ });
+
+ $()
+ .add($splitter)
+ .add($document)
+ .mouseup(function() {
+ $splitter
+ .removeClass('active')
+ .unbind('mouseup');
+ $document
+ .unbind('mousemove')
+ .unbind('mouseup');
+
+ $.cookie('splitter', splitterPosition, {expires: 365});
+ });
+
+ return false;
+ });
+ $splitter.dblclick(function() {
+ if (splitterPosition) {
+ splitterPositionBackup = $left.width();
+ setSplitterPosition(0);
+ } else {
+ setSplitterPosition(splitterPositionBackup);
+ splitterPositionBackup = null;
+ }
+
+ setContentWidth();
+
+ $.cookie('splitter', splitterPosition, {expires: 365});
+ $.cookie('splitterBackup', splitterPositionBackup, {expires: 365});
+ });
+ if (null !== splitterPosition) {
+ setSplitterPosition(splitterPosition);
+ }
+ setContentWidth();
+ $(window).resize(setContentWidth);
+
+ // Select selected lines
+ var matches = window.location.hash.substr(1).match(/^\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*$/);
+ if (null !== matches) {
+ var lists = matches[0].split(',');
+ for (var i = 0; i < lists.length; i++) {
+ var lines = lists[i].split('-');
+ lines[0] = parseInt(lines[0]);
+ lines[1] = parseInt(lines[1] || lines[0]);
+ for (var j = lines[0]; j <= lines[1]; j++) {
+ $('#' + j).addClass('selected');
+ }
+ }
+
+ var $firstLine = $('#' + parseInt(matches[0]));
+ if ($firstLine.length > 0) {
+ $document.scrollTop($firstLine.offset().top);
+ }
+ }
+
+ // Save selected lines
+ var lastLine;
+ $('.l a').click(function(event) {
+ event.preventDefault();
+
+ var selectedLine = $(this).parent().index() + 1;
+ var $selectedLine = $('pre.code .l').eq(selectedLine - 1);
+
+ if (event.shiftKey) {
+ if (lastLine) {
+ for (var i = Math.min(selectedLine, lastLine); i <= Math.max(selectedLine, lastLine); i++) {
+ $('#' + i).addClass('selected');
+ }
+ } else {
+ $selectedLine.addClass('selected');
+ }
+ } else if (event.ctrlKey) {
+ $selectedLine.toggleClass('selected');
+ } else {
+ var $selected = $('.l.selected')
+ .not($selectedLine)
+ .removeClass('selected');
+ if ($selected.length > 0) {
+ $selectedLine.addClass('selected');
+ } else {
+ $selectedLine.toggleClass('selected');
+ }
+ }
+
+ lastLine = $selectedLine.hasClass('selected') ? selectedLine : null;
+
+ // Update hash
+ var lines = $('.l.selected')
+ .map(function() {
+ return parseInt($(this).attr('id'));
+ })
+ .get()
+ .sort(function(a, b) {
+ return a - b;
+ });
+
+ var hash = [];
+ var list = [];
+ for (var j = 0; j < lines.length; j++) {
+ if (0 === j && j + 1 === lines.length) {
+ hash.push(lines[j]);
+ } else if (0 === j) {
+ list[0] = lines[j];
+ } else if (lines[j - 1] + 1 !== lines[j] && j + 1 === lines.length) {
+ hash.push(list.join('-'));
+ hash.push(lines[j]);
+ } else if (lines[j - 1] + 1 !== lines[j]) {
+ hash.push(list.join('-'));
+ list = [lines[j]];
+ } else if (j + 1 === lines.length) {
+ list[1] = lines[j];
+ hash.push(list.join('-'));
+ } else {
+ list[1] = lines[j];
+ }
+ }
+
+ hash = hash.join(',');
+ $backup = $('#' + hash).removeAttr('id');
+ window.location.hash = hash;
+ $backup.attr('id', hash);
+ });
+});
+
diff --git a/docs/resources/footer.png b/docs/resources/footer.png
new file mode 100644
index 0000000..d99890c
Binary files /dev/null and b/docs/resources/footer.png differ
diff --git a/docs/resources/inherit.png b/docs/resources/inherit.png
new file mode 100644
index 0000000..957079b
Binary files /dev/null and b/docs/resources/inherit.png differ
diff --git a/docs/resources/resize.png b/docs/resources/resize.png
new file mode 100644
index 0000000..fb98a7a
Binary files /dev/null and b/docs/resources/resize.png differ
diff --git a/docs/resources/sort.png b/docs/resources/sort.png
new file mode 100644
index 0000000..0d0fea1
Binary files /dev/null and b/docs/resources/sort.png differ
diff --git a/docs/resources/style.css b/docs/resources/style.css
new file mode 100644
index 0000000..8bf17b5
--- /dev/null
+++ b/docs/resources/style.css
@@ -0,0 +1,619 @@
+body {
+ font: 13px/1.5 Verdana, 'Geneva CE', lucida, sans-serif;
+ margin: 0;
+ padding: 0;
+ background: #ffffff;
+ color: #333333;
+}
+
+h1, h2, h3, h4, caption {
+ font-family: 'Trebuchet MS', 'Geneva CE', lucida, sans-serif;
+ color: #053368;
+}
+
+h1 {
+ color: #1e5eb6;
+ font-size: 230%;
+ font-weight: normal;
+ margin: .3em 0;
+}
+
+h2 {
+ color: #1e5eb6;
+ font-size: 150%;
+ font-weight: normal;
+ margin: -.3em 0 .3em 0;
+}
+
+h3 {
+ font-size: 1.6em;
+ font-weight: normal;
+ margin-bottom: 2px;
+}
+
+h4 {
+ font-size: 100%;
+ font-weight: bold;
+ padding: 0;
+ margin: 0;
+}
+
+caption {
+ border: 1px solid #cccccc;
+ background: #ecede5;
+ font-weight: bold;
+ font-size: 1.2em;
+ padding: 3px 5px;
+ text-align: left;
+ margin-bottom: 0;
+}
+
+p {
+ margin: .7em 0 1em;
+ padding: 0;
+}
+
+hr {
+ margin: 2em 0 1em;
+ border: none;
+ border-top: 1px solid #cccccc;
+ height: 0;
+}
+
+a {
+ color: #006aeb;
+ padding: 3px 1px;
+ text-decoration: none;
+}
+
+h1 a {
+ color: #1e5eb6;
+}
+
+a:hover, a:active, a:focus, a:hover b, a:hover var {
+ background-color: #006aeb;
+ color: #ffffff !important;
+}
+
+code, var, pre {
+ font-family: monospace;
+}
+
+var {
+ font-weight: bold;
+ font-style: normal;
+ color: #ca8a04;
+}
+
+pre {
+ margin: 0;
+}
+
+code a b {
+ color: #000000;
+}
+
+.deprecated {
+ text-decoration: line-through;
+ opacity: .5;
+}
+
+.invalid {
+ color: #e71818;
+}
+
+.hidden {
+ display: none;
+}
+
+/* Left side */
+#left {
+ overflow: auto;
+ width: 270px;
+ height: 100%;
+ position: fixed;
+}
+
+/* Menu */
+#menu {
+ padding: 10px;
+}
+
+#menu ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+#menu ul ul {
+ padding-left: 10px;
+}
+
+#menu li {
+ white-space: nowrap;
+ position: relative;
+}
+
+#menu a {
+ display: block;
+ padding: 0 2px;
+}
+
+#menu .active > a, #menu > span {
+ color: #333333;
+ background: none;
+ font-weight: bold;
+}
+
+#menu .active > a.invalid {
+ color: #e71818;
+}
+
+#menu .active > a:hover, #menu .active > a:active, #menu .active > a:focus {
+ background-color: #006aeb;
+}
+
+#menu #groups span {
+ position: absolute;
+ top: 4px;
+ right: 2px;
+ cursor: pointer;
+ display: block;
+ width: 12px;
+ height: 12px;
+ background: url('collapsed.png') transparent 0 0 no-repeat;
+}
+
+#menu #groups span:hover {
+ background-position: -12px 0;
+}
+
+#menu #groups span.collapsed {
+ background-position: 0 -12px;
+}
+
+#menu #groups span.collapsed:hover {
+ background-position: -12px -12px;
+}
+
+#menu #groups ul.collapsed {
+ display: none;
+}
+
+/* Right side */
+#right {
+ overflow: auto;
+ margin-left: 275px;
+ height: 100%;
+ position: relative;
+ left: 0;
+ right: 0;
+}
+
+#rightInner {
+ max-width: 1000px;
+ min-width: 350px;
+}
+
+/* Search */
+#search {
+ float: right;
+ margin: 3px 8px;
+}
+
+#search input.text {
+ padding: 3px 5px;
+ width: 250px;
+}
+
+/* Autocomplete */
+.ac_results {
+ padding: 0;
+ border: 1px solid #cccccc;
+ background-color: #ffffff;
+ overflow: hidden;
+ z-index: 99999;
+}
+
+.ac_results ul {
+ width: 100%;
+ list-style-position: outside;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.ac_results li {
+ margin: 0;
+ padding: 2px 5px;
+ cursor: default;
+ display: block;
+ font: 12px 'Trebuchet MS', 'Geneva CE', lucida, sans-serif;
+ line-height: 16px;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.ac_results li strong {
+ color: #000000;
+}
+
+.ac_odd {
+ background-color: #eeeeee;
+}
+
+.ac_over {
+ background-color: #006aeb;
+ color: #ffffff;
+}
+
+.ac_results li.ac_over strong {
+ color: #ffffff;
+}
+
+/* Navigation */
+#navigation {
+ padding: 3px 8px;
+ background-color: #f6f6f4;
+ height: 26px;
+}
+
+#navigation ul {
+ list-style: none;
+ margin: 0 8px 4px 0;
+ padding: 0;
+ overflow: hidden;
+ float: left;
+}
+
+#navigation ul + ul {
+ border-left: 1px solid #000000;
+ padding-left: 8px;
+}
+
+#navigation ul li {
+ float: left;
+ margin: 2px;
+ padding: 0 3px;
+ font-family: Verdana, 'Geneva CE', lucida, sans-serif;
+ color: #808080;
+}
+
+#navigation ul li.active {
+ background-color: #053368;
+ color: #ffffff;
+ font-weight: bold;
+}
+
+#navigation ul li a {
+ color: #000000;
+ font-weight: bold;
+ padding: 0;
+}
+
+#navigation ul li span {
+ float: left;
+ padding: 0 3px;
+}
+
+#navigation ul li a:hover span, #navigation ul li a:active span, #navigation ul li a:focus span {
+ background-color: #006aeb;
+}
+
+/* Content */
+#content {
+ clear: both;
+ padding: 5px 15px;
+}
+
+.description pre {
+ padding: .6em;
+ background: #fcfcf7;
+}
+
+#content > .description {
+ background: #ecede5;
+ padding: 1px 8px;
+ margin: 1.2em 0;
+}
+
+#content > .description pre {
+ margin: .5em 0;
+}
+
+dl.tree {
+ margin: 1.2em 0;
+}
+
+dl.tree dd {
+ margin: 0;
+ padding: 0;
+}
+
+.info {
+ margin: 1.2em 0;
+}
+
+.summary {
+ border: 1px solid #cccccc;
+ border-collapse: collapse;
+ font-size: 1em;
+ width: 100%;
+ margin: 1.2em 0 2.4em;
+}
+
+.summary caption {
+ border-width: 1px 1px 0;
+}
+
+.summary caption.switchable {
+ background: #ecede5 url('sort.png') no-repeat center right;
+ cursor: pointer;
+}
+
+.summary td {
+ border: 1px solid #cccccc;
+ margin: 0;
+ padding: 3px 10px;
+ font-size: 1em;
+ vertical-align: top;
+}
+
+.summary td:first-child {
+ text-align: right;
+}
+
+.summary td hr {
+ margin: 3px -10px;
+}
+
+#packages.summary td:first-child, #namespaces.summary td:first-child, .inherited.summary td:first-child, .used.summary td:first-child {
+ text-align: left;
+}
+
+.summary tr:hover td {
+ background: #f6f6f4;
+}
+
+.summary .description pre {
+ border: .5em solid #ecede5;
+}
+
+.summary .description p {
+ margin: 0;
+}
+
+.summary .description p + p, .summary .description ul {
+ margin: 3px 0 0 0;
+}
+
+.summary .description.detailed h4 {
+ margin-top: 3px;
+}
+
+.summary dl {
+ margin: 0;
+}
+
+.summary dd {
+ margin: 0 0 0 25px;
+}
+
+.name, .attributes {
+ white-space: nowrap;
+}
+
+.value code {
+ white-space: pre-wrap;
+}
+
+td.name, td.attributes {
+ width: 1%;
+}
+
+td.attributes {
+ width: 1%;
+}
+
+.class .methods .name, .class .properties .name, .class .constants .name {
+ width: auto;
+ white-space: normal;
+}
+
+.class .methods .name > div > code {
+ white-space: pre-wrap;
+}
+
+.class .methods .name > div > code span, .function .value > code {
+ white-space: nowrap;
+ display: inline-block;
+}
+
+.class .methods td.name > div, .class td.value > div {
+ position: relative;
+ padding-right: 1em;
+}
+
+.anchor {
+ position: absolute;
+ top: 0;
+ right: 0;
+ line-height: 1;
+ font-size: 85%;
+ margin: 0;
+ color: #006aeb !important;
+}
+
+.list {
+ margin: 0 0 5px 25px;
+}
+
+div.invalid {
+ background-color: #fae4e0;
+ padding: 10px;
+}
+
+/* Splitter */
+#splitter {
+ position: fixed;
+ height: 100%;
+ width: 5px;
+ left: 270px;
+ background: #1e5eb6 url('resize.png') left center no-repeat;
+ cursor: e-resize;
+}
+
+#splitter.active {
+ opacity: .5;
+}
+
+/* Footer */
+#footer {
+ border-top: 1px solid #e9eeef;
+ clear: both;
+ color: #a7a7a7;
+ font-size: 8pt;
+ text-align: center;
+ padding: 20px 0 0;
+ margin: 3em 0 0;
+ height: 90px;
+ background: #ffffff url('footer.png') no-repeat center top;
+}
+
+/* Tree */
+div.tree ul {
+ list-style: none;
+ background: url('tree-vertical.png') left repeat-y;
+ padding: 0;
+ margin-left: 20px;
+}
+
+div.tree li {
+ margin: 0;
+ padding: 0;
+}
+
+div.tree div {
+ padding-left: 30px;
+}
+
+div.tree div.notlast {
+ background: url('tree-hasnext.png') left 10px no-repeat;
+}
+
+div.tree div.last {
+ background: url('tree-last.png') left -240px no-repeat;
+}
+
+div.tree li.last {
+ background: url('tree-cleaner.png') left center repeat-y;
+}
+
+div.tree span.padding {
+ padding-left: 15px;
+}
+
+/* Source code */
+.php-keyword1 {
+ color: #e71818;
+ font-weight: bold;
+}
+
+.php-keyword2 {
+ font-weight: bold;
+}
+
+.php-var {
+ color: #d59401;
+ font-weight: bold;
+}
+
+.php-num {
+ color: #cd0673;
+}
+
+.php-quote {
+ color: #008000;
+}
+
+.php-comment {
+ color: #929292;
+}
+
+.xlang {
+ color: #ff0000;
+ font-weight: bold;
+}
+
+pre.numbers {
+ float: left;
+}
+
+span.l {
+ display: block;
+}
+
+span.l.selected {
+ background: #f6f6f4;
+}
+
+span.l a {
+ color: #333333;
+}
+
+span.l a:hover, div.l a:active, div.l a:focus {
+ background: transparent;
+ color: #333333 !important;
+}
+
+span.l .php-var a {
+ color: #d59401;
+}
+
+span.l .php-var a:hover, span.l .php-var a:active, span.l .php-var a:focus {
+ color: #d59401 !important;
+}
+
+span.l a.l {
+ padding-left: 2px;
+ color: #c0c0c0;
+}
+
+span.l a.l:hover, span.l a.l:active, span.l a.l:focus {
+ background: transparent;
+ color: #c0c0c0 !important;
+}
+
+#rightInner.medium #navigation {
+ height: 52px;
+}
+
+#rightInner.medium #navigation ul:first-child + ul {
+ clear: left;
+ border: none;
+ padding: 0;
+}
+
+#rightInner.medium .name, #rightInner.medium .attributes {
+ white-space: normal;
+}
+
+#rightInner.small #search {
+ float: left;
+}
+
+#rightInner.small #navigation {
+ height: 78px;
+}
+
+#rightInner.small #navigation ul:first-child {
+ clear: both;
+}
+
+/* global style */
+.left, .summary td.left {
+ text-align: left;
+}
+.right, .summary td.right {
+ text-align: right;
+}
diff --git a/docs/resources/tree-cleaner.png b/docs/resources/tree-cleaner.png
new file mode 100644
index 0000000..2eb9085
Binary files /dev/null and b/docs/resources/tree-cleaner.png differ
diff --git a/docs/resources/tree-hasnext.png b/docs/resources/tree-hasnext.png
new file mode 100644
index 0000000..91d6b79
Binary files /dev/null and b/docs/resources/tree-hasnext.png differ
diff --git a/docs/resources/tree-last.png b/docs/resources/tree-last.png
new file mode 100644
index 0000000..7f319f8
Binary files /dev/null and b/docs/resources/tree-last.png differ
diff --git a/docs/resources/tree-vertical.png b/docs/resources/tree-vertical.png
new file mode 100644
index 0000000..384908b
Binary files /dev/null and b/docs/resources/tree-vertical.png differ
diff --git a/docs/source-class-LeanCloud.ACL.html b/docs/source-class-LeanCloud.ACL.html
new file mode 100644
index 0000000..34e230c
--- /dev/null
+++ b/docs/source-class-LeanCloud.ACL.html
@@ -0,0 +1,459 @@
+
+
+
+
+
+
+ File LeanCloud/ACL.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312:
+
<?php
+namespace LeanCloud;
+
+
+ class ACL {
+
+ const PUBLIC_KEY = "*" ;
+
+
+ private $data ;
+
+
+ public function __construct($val =array ()) {
+ $this ->data = array ();
+
+ if ($val instanceof User) {
+ $this ->setReadAccess($val , true );
+ $this ->setWriteAccess($val , true );
+ } else if (is_array ($val )) {
+ forEach ($val as $id => $attr ) {
+ if (!is_string ($id )) {
+ throw new \RuntimeException("Invalid ACL target" );
+ }
+ if (isset ($attr ["read" ]) || isset ($attr ["write" ])) {
+ $this ->data[$id ] = $attr ;
+ } else {
+ throw new \RuntimeException("Invalid ACL access type" );
+ }
+ }
+ } else {
+ throw new \RuntimeException("Invalid ACL data." );
+ }
+ }
+
+
+ private function setAccess($target , $accessType , $flag ) {
+ if (empty ($target )) {
+ throw new \InvalidArgumentException("ACL target cannot be empty" );
+ }
+ if (!in_array ($accessType , array ("read" , "write" ))) {
+ throw new \InvalidArgumentException("ACL access type must be" .
+ " either read or write." );
+ }
+
+ $access = array ();
+ if (isset ($this ->data[$target ])) {
+ $access = $this ->data[$target ];
+ }
+ $access [$accessType ] = $flag ;
+ $this ->data[$target ] = $access ;
+ }
+
+
+ private function getAccess($target , $accessType ) {
+ if (empty ($target )) {
+ throw new \InvalidArgumentException("Access target cannot be empty" );
+ }
+ if (!in_array ($accessType , array ("read" , "write" ))) {
+ throw new \InvalidArgumentException("ACL access type must be" .
+ " either read or write." );
+ }
+ if (isset ($this ->data[$target ][$accessType ])) {
+ return $this ->data[$target ][$accessType ];
+ }
+ return false ;
+ }
+
+
+ public function getPublicReadAccess() {
+ return $this ->getAccess(self::PUBLIC_KEY, "read" );
+ }
+
+
+ public function getPublicWriteAccess() {
+ return $this ->getAccess(self::PUBLIC_KEY, "write" );
+ }
+
+
+ public function setPublicReadAccess($flag ) {
+ $this ->setAccess(self::PUBLIC_KEY, "read" , $flag );
+ return $this ;
+ }
+
+
+ public function setPublicWriteAccess($flag ) {
+ $this ->setAccess(self::PUBLIC_KEY, "write" , $flag );
+ return $this ;
+ }
+
+
+ public function getRoleReadAccess($role ) {
+ if ($role instanceof Role) {
+ $role = $role ->getName();
+ }
+ return $this ->getAccess("role: $role " , "read" );
+ }
+
+
+ public function getRoleWriteAccess($role ) {
+ if ($role instanceof Role) {
+ $role = $role ->getName();
+ }
+ return $this ->getAccess("role: $role " , "write" );
+ }
+
+
+ public function setRoleReadAccess($role , $flag ) {
+ if ($role instanceof Role) {
+ $role = $role ->getName();
+ }
+ if (!is_string ($role )) {
+ throw new \InvalidArgumentException("role must be either " .
+ "Role or string." );
+ }
+ $this ->setAccess("role: $role " , "read" , $flag );
+ return $this ;
+ }
+
+
+ public function setRoleWriteAccess($role , $flag ) {
+ if ($role instanceof Role) {
+ $role = $role ->getName();
+ }
+ if (!is_string ($role )) {
+ throw new \InvalidArgumentException("role must be either " .
+ "Role or string." );
+ }
+ $this ->setAccess("role: $role " , "write" , $flag );
+ return $this ;
+ }
+
+
+ public function getReadAccess($user ) {
+ if ($user instanceof User) {
+ $user = $user ->getObjectId();
+ }
+ return $this ->getAccess($user , "read" );
+ }
+
+
+ public function getWriteAccess($user ) {
+ if ($user instanceof User) {
+ $user = $user ->getObjectId();
+ }
+ return $this ->getAccess($user , "write" );
+ }
+
+
+ public function setReadAccess($user , $flag ) {
+ if ($user instanceof User) {
+ if (!$user ->getObjectId()) {
+ throw new \RuntimeException("user must be saved before " .
+ "being assigned in ACL." );
+ }
+ $user = $user ->getObjectId();
+ }
+ if (!is_string ($user )) {
+ throw new \InvalidArgumentException("user must be either " .
+ " User or objectId." );
+ }
+ $this ->setAccess($user , "read" , $flag );
+ return $this ;
+ }
+
+
+ public function setWriteAccess($user , $flag ) {
+ if ($user instanceof User) {
+ if (!$user ->getObjectId()) {
+ throw new \RuntimeException("user must be saved before " .
+ "being assigned in ACL." );
+ }
+ $user = $user ->getObjectId();
+ }
+ if (!is_string ($user )) {
+ throw new \InvalidArgumentException("user must be either " .
+ " User or objectId." );
+ }
+ $this ->setAccess($user , "write" , $flag );
+ return $this ;
+ }
+
+
+ public function encode() {
+ return empty ($this ->data) ? new \stdClass() : $this ->data;
+ }
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.AppRouter.html b/docs/source-class-LeanCloud.AppRouter.html
new file mode 100644
index 0000000..dc46525
--- /dev/null
+++ b/docs/source-class-LeanCloud.AppRouter.html
@@ -0,0 +1,372 @@
+
+
+
+
+
+
+ File LeanCloud/AppRouter.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225:
+
<?php
+
+ namespace LeanCloud;
+
+ use LeanCloud\Region;
+
+ class AppRouter {
+ const TTL_KEY = "ttl" ;
+ const API_SERVER_KEY = "api_server" ;
+ const PUSH_SERVER_KEY = "push_server" ;
+ const STATS_SERVER_KEY = "stats_server" ;
+ const ENGINE_SERVER_KEY = "engine_server" ;
+ const RTM_ROUTER_SERVER_KEY = "rtm_router_server" ;
+ private static $INSTANCES ;
+ private $appId ;
+ private $region ;
+ private $routeCache ;
+
+ private static $DEFAULT_REGION_ROUTE = array (
+ Region::US => "us-api.leancloud.cn" ,
+ Region::CN_E1 => "e1-api.leancloud.cn" ,
+ Region::CN_N1 => "api.leancloud.cn"
+ );
+
+ private static $DEFAULT_REGION_RTM_ROUTE = array (
+ Region::US => "router-a0-push.leancloud.cn" ,
+ Region::CN_E1 => "router-q0-push.leancloud.cn" ,
+ Region::CN_N1 => "router-g0-push.leancloud.cn"
+ );
+
+ private function __construct($appId ) {
+ $this ->appId = $appId ;
+ $region = getenv ("LEANCLOUD_REGION" );
+ if (!$region ) {
+ $region = Region::CN;
+ }
+ $this ->setRegion($region );
+ $this ->routeCache = RouteCache::create($appId );
+ }
+
+
+ public static function getInstance($appId ) {
+ if (isset (self::$INSTANCES [$appId ])) {
+ return self::$INSTANCES [$appId ];
+ } else {
+ $router = new AppRouter($appId );
+ self::$INSTANCES [$appId ] = $router ;
+ return $router ;
+ }
+ }
+
+
+ public function getRegionDefaultRoute($server_key ) {
+ $this ->validate_server_key($server_key );
+ return $this ->getDefaultRoutes()[$server_key ];
+ }
+
+
+ public function setRegion($region ) {
+ if (is_numeric ($region )) {
+ $this ->region = $region ;
+ } else {
+ $this ->region = Region::fromName($region );
+ }
+ }
+
+
+ public function getRoute($server_key ) {
+ $this ->validate_server_key($server_key );
+ $routes = $this ->routeCache->read();
+ if (isset ($routes [$server_key ])) {
+ return $routes [$server_key ];
+ }
+ $routes = $this ->getRoutes();
+ if (!$routes ) {
+ $routes = $this ->getDefaultRoutes();
+ }
+ $this ->routeCache->write($routes );
+ return isset ($routes [$server_key ]) ? $routes [$server_key ] : null ;
+ }
+
+ private function getRouterUrl() {
+ $url = getenv ("LEANCLOUD_APP_ROUTER" );
+ if (!$url ) {
+ $url = "https://app-router.leancloud.cn/2/route?appId=" ;
+ }
+ return " {$url}{$this->appId} " ;
+ }
+
+ private function validate_server_key($server_key ) {
+ $routes = $this ->getDefaultRoutes();
+ if (!isset ($routes [$server_key ])) {
+ throw new IllegalArgumentException("Invalid server key." );
+ }
+ }
+
+
+ private function detectRegion() {
+ if (!$this ->appId) {
+ return Region::CN_N1;
+ }
+ $parts = explode ("-" , $this ->appId);
+ if (count ($parts ) <= 1 ) {
+ return Region::CN_N1;
+ } else if ($parts [1 ] === "MdYXbMMI" ) {
+ return Region::US;
+ } else if ($parts [1 ] === "9Nh9j0Va" ) {
+ return Region::CN_E1;
+ } else {
+ $this ->region = Region::CN_N1;
+ }
+ }
+
+
+ private function getRoutes() {
+ $routes = @json_decode (file_get_contents ($this ->getRouterUrl()), true );
+ if (isset ($routes [self::TTL_KEY])) {
+ return $routes ;
+ }
+ return null ;
+ }
+
+
+ private function getDefaultRoutes() {
+ $host = self::$DEFAULT_REGION_ROUTE [$this ->region];
+
+ return array (
+ self::API_SERVER_KEY => $host ,
+ self::PUSH_SERVER_KEY => $host ,
+ self::STATS_SERVER_KEY => $host ,
+ self::ENGINE_SERVER_KEY => $host ,
+ self::RTM_ROUTER_SERVER_KEY => self::$DEFAULT_REGION_RTM_ROUTE [$this ->region],
+ self::TTL_KEY => 3600
+ );
+ }
+
+
+ }
+
+
+
+ class RouteCache {
+ private $filename ;
+ private $_cache ;
+
+ private function __construct($id ) {
+ $this ->filename = sys_get_temp_dir () . "/route_ {$id} .json" ;
+ }
+
+ public static function create($id ) {
+ return new RouteCache($id );
+ }
+
+
+ public function write($array ) {
+ $body = json_encode ($array );
+ if (file_put_contents ($this ->filename, $body , LOCK_EX) === false ) {
+ error_log ("WARNING: failed to write route cache ( {$this->filename} ), performance may be degraded." );
+ } else {
+ $this ->_cache = $array ;
+ }
+ }
+
+
+ public function read() {
+ if ($this ->_cache) {
+ return $this ->_cache;
+ }
+ $data = $this ->readFile ();
+ if (!empty ($data )) {
+ $this ->_cache = $data ;
+ return $data ;
+ }
+ return null ;
+ }
+
+ private function readFile () {
+ if (file_exists ($this ->filename)) {
+ $fp = fopen ($this ->filename, "rb" );
+ $body = null ;
+ if (flock ($fp , LOCK_SH)) {
+ $body = fread ($fp , filesize ($this ->filename));
+ flock ($fp , LOCK_UN);
+ }
+ fclose ($fp );
+ if (!empty ($body )) {
+ $data = @json_decode ($body , true );
+ if (!empty ($data )) {
+ return $data ;
+ }
+ }
+ }
+ return null ;
+ }
+ }
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.BatchRequestError.html b/docs/source-class-LeanCloud.BatchRequestError.html
new file mode 100644
index 0000000..5d946bf
--- /dev/null
+++ b/docs/source-class-LeanCloud.BatchRequestError.html
@@ -0,0 +1,229 @@
+
+
+
+
+
+
+ File LeanCloud/BatchRequestError.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82:
+
<?php
+namespace LeanCloud;
+
+
+ class BatchRequestError extends CloudException {
+
+
+ private $errors = [];
+
+ public function __construct($message ="" , $code = 1 ) {
+ $message = empty ($message ) ? "Batch request error." : $message ;
+ parent::__construct($message , $code );
+ }
+
+
+ public function add($request , $response ) {
+ $error ["code" ] = isset ($response ["code" ]) ? $response ["code" ] : 1 ;
+ $error ["error" ] = " {$error['code']} {$response['error']} :"
+ . json_encode ($request );
+ $this ->errors[] = $error ;
+ return $this ;
+ }
+
+
+ public function getAll() {
+ return $this ->errors;
+ }
+
+
+ public function getFirst() {
+ return isset ($this ->errors[0 ]) ? $this ->errors[0 ] : null ;
+ }
+
+
+ public function isEmpty() {
+ return count ($this ->errors) == 0 ;
+ }
+
+ public function __toString() {
+ $message = $this ->message;
+ if (!$this ->isEmpty()) {
+ $message .= json_encode ($this ->errors);
+ }
+ return __CLASS__ . ": [ {$this->code} ]: {$message} \n" ;
+ }
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Bytes.html b/docs/source-class-LeanCloud.Bytes.html
new file mode 100644
index 0000000..77c15bb
--- /dev/null
+++ b/docs/source-class-LeanCloud.Bytes.html
@@ -0,0 +1,227 @@
+
+
+
+
+
+
+ File LeanCloud/Bytes.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Client.html b/docs/source-class-LeanCloud.Client.html
new file mode 100644
index 0000000..604cc03
--- /dev/null
+++ b/docs/source-class-LeanCloud.Client.html
@@ -0,0 +1,877 @@
+
+
+
+
+
+
+ File LeanCloud/Client.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 527: 528: 529: 530: 531: 532: 533: 534: 535: 536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581: 582: 583: 584: 585: 586: 587: 588: 589: 590: 591: 592: 593: 594: 595: 596: 597: 598: 599: 600: 601: 602: 603: 604: 605: 606: 607: 608: 609: 610: 611: 612: 613: 614: 615: 616: 617: 618: 619: 620: 621: 622: 623: 624: 625: 626: 627: 628: 629: 630: 631: 632: 633: 634: 635: 636: 637: 638: 639: 640: 641: 642: 643: 644: 645: 646: 647: 648: 649: 650: 651: 652: 653: 654: 655: 656: 657: 658: 659: 660: 661: 662: 663: 664: 665: 666: 667: 668: 669: 670: 671: 672: 673: 674: 675: 676: 677: 678: 679: 680: 681: 682: 683: 684: 685: 686: 687: 688: 689: 690: 691: 692: 693: 694: 695: 696: 697: 698: 699: 700: 701: 702: 703: 704: 705: 706: 707: 708: 709: 710: 711: 712: 713: 714: 715: 716: 717: 718: 719: 720: 721: 722: 723: 724: 725: 726: 727: 728: 729: 730:
+
<?php
+
+ namespace LeanCloud;
+
+ use LeanCloud\Bytes;
+use LeanCloud\LeanObject;
+use LeanCloud\ACL;
+use LeanCloud\File ;
+use LeanCloud\User;
+use LeanCloud\Operation\IOperation;
+use LeanCloud\Storage\IStorage;
+use LeanCloud\Storage\SessionStorage;
+use LeanCloud\AppRouter;
+
+
+ class Client {
+
+ const VERSION = '0.14.0' ;
+
+
+ private static $apiVersion = "1.1" ;
+
+
+ private static $apiTimeout = 15 ;
+
+
+ private static $appId ;
+
+
+ private static $appKey ;
+
+
+ private static $appMasterKey ;
+
+
+ private static $serverUrl ;
+
+
+ private static $useMasterKey = false ;
+
+
+ public static $isProduction = false ;
+
+
+ private static $debugMode = false ;
+
+
+ private static $defaultHeaders ;
+
+
+ private static $storage ;
+
+
+ public static function initialize($appId , $appKey , $appMasterKey ) {
+ self::$appId = $appId ;
+ self::$appKey = $appKey ;
+ self::$appMasterKey = $appMasterKey ;
+
+ self::$defaultHeaders = array (
+ 'X-LC-Id' => self::$appId ,
+ 'Content-Type' => 'application/json;charset=utf-8' ,
+ 'Accept-Encoding' => 'gzip, deflate' ,
+ 'User-Agent' => self::getVersionString()
+ );
+
+
+ if (!self::$storage ) {
+ self::$storage = new SessionStorage();
+ }
+
+ self::useProduction(getenv ("LEANCLOUD_APP_ENV" ) == "production" );
+ User::registerClass();
+ Role::registerClass();
+ }
+
+
+ public static function setApiTimeout($seconds ) {
+ static ::$apiTimeout = intval ($seconds );
+ }
+
+
+ private static function assertInitialized() {
+ if (!isset (self::$appId ) &&
+ !isset (self::$appKey ) &&
+ !isset (self::$appMasterKey )) {
+ throw new \RuntimeException("Client is not initialized, " .
+ "please specify application key " .
+ "with Client::initialize." );
+ }
+ }
+
+
+ public static function getVersionString() {
+ return "LeanCloud PHP SDK " . self::VERSION;
+ }
+
+
+ public static function useRegion($region ) {
+ self::assertInitialized();
+ AppRouter::getInstance(self::$appId )->setRegion($region );
+ }
+
+
+ public static function useProduction($flag ) {
+ self::$isProduction = $flag ? true : false ;
+ }
+
+
+ public static function setDebug($flag ) {
+ self::$debugMode = $flag ? true : false ;
+ }
+
+
+ public static function useMasterKey($flag ) {
+ self::$useMasterKey = $flag ? true : false ;
+ }
+
+
+ public static function setServerUrl($url ) {
+ self::$serverUrl = rtrim ($url , "/" );
+ }
+
+
+ public static function getAPIEndPoint() {
+ if ($url = self::$serverUrl ) {
+ return $url . "/" . self::$apiVersion ;
+ } else if ($url = getenv ("LEANCLOUD_API_SERVER" )) {
+ return $url . "/" . self::$apiVersion ;
+ } else {
+ $host = AppRouter::getInstance(self::$appId )->getRoute(AppRouter::API_SERVER_KEY);
+ return "https:// {$host} /" . self::$apiVersion ;
+ }
+ }
+
+
+ public static function buildHeaders($sessionToken , $useMasterKey ) {
+ if (is_null ($useMasterKey )) {
+ $useMasterKey = self::$useMasterKey ;
+ }
+ $h = self::$defaultHeaders ;
+
+ $h ['X-LC-Prod' ] = self::$isProduction ? 1 : 0 ;
+
+ $timestamp = time ();
+ $key = $useMasterKey ? self::$appMasterKey : self::$appKey ;
+ $sign = md5 ($timestamp . $key );
+ $h ['X-LC-Sign' ] = $sign . "," . $timestamp ;
+
+ if ($useMasterKey ) {
+ $h ['X-LC-Sign' ] .= ",master" ;
+ }
+
+ if (!$sessionToken ) {
+ $sessionToken = User::getCurrentSessionToken();
+ }
+
+ if ($sessionToken ) {
+ $h ['X-LC-Session' ] = $sessionToken ;
+ }
+
+ return $h ;
+ }
+
+
+ public static function verifySign($appId , $sign ) {
+ if (!$appId || ($appId != self::$appId )) {
+ return false ;
+ }
+ $parts = explode ("," , $sign );
+ $key = self::$appKey ;
+ if (isset ($parts [2 ]) && "master" === trim ($parts [2 ])) {
+ $key = self::$appMasterKey ;
+ }
+ return $parts [0 ] === md5 (trim ($parts [1 ]) . $key );
+ }
+
+
+ public static function verifyKey($appId , $key ) {
+ if (!$appId || ($appId != self::$appId )) {
+ return false ;
+ }
+ $parts = explode ("," , $key );
+ if (isset ($parts [1 ]) && "master" === trim ($parts [1 ])) {
+ return self::$appMasterKey === $parts [0 ];
+ }
+ return self::$appKey === $parts [0 ];
+ }
+
+
+ public static function signHook($hookName , $msec ) {
+ $hash = hash_hmac ("sha1" , " {$hookName} : {$msec} " , self::$appMasterKey );
+ return " {$msec} , {$hash} " ;
+ }
+
+
+ public static function verifyHookSign($hookName , $sign ) {
+ if ($sign ) {
+ $parts = explode ("," , $sign );
+ $msec = $parts [0 ];
+ return self::signHook($hookName , $msec ) === $sign ;
+ }
+ return false ;
+ }
+
+
+ public static function request($method , $path , $data ,
+ $sessionToken =null ,
+ $headers =array (),
+ $useMasterKey =null ) {
+ self::assertInitialized();
+ $url = self::getAPIEndPoint();
+ $url .= $path ;
+
+ $defaultHeaders = self::buildHeaders($sessionToken , $useMasterKey );
+ if (empty ($headers )) {
+ $headers = $defaultHeaders ;
+ } else {
+ $headers = array_merge ($defaultHeaders , $headers );
+ }
+ if (strpos ($headers ["Content-Type" ], "/json" ) !== false ) {
+ $json = json_encode ($data );
+ }
+
+
+ $headersList = array_map (function ($key , $val ) { return " $key : $val " ;},
+ array_keys ($headers ),
+ $headers );
+
+ $req = curl_init ($url );
+ curl_setopt ($req , CURLOPT_SSL_VERIFYPEER, true );
+ curl_setopt ($req , CURLOPT_HTTPHEADER, $headersList );
+ curl_setopt ($req , CURLOPT_RETURNTRANSFER, true );
+ curl_setopt ($req , CURLOPT_TIMEOUT, self::$apiTimeout );
+
+
+ curl_setopt ($req , CURLOPT_ENCODING, '' );
+ switch ($method ) {
+ case "GET" :
+ if ($data ) {
+
+ $url .= "?" . http_build_query ($data );
+ curl_setopt ($req , CURLOPT_URL, $url );
+ }
+ break ;
+ case "POST" :
+ curl_setopt ($req , CURLOPT_POST, 1 );
+ curl_setopt ($req , CURLOPT_POSTFIELDS, $json );
+ break ;
+ case "PUT" :
+ curl_setopt ($req , CURLOPT_POSTFIELDS, $json );
+ curl_setopt ($req , CURLOPT_CUSTOMREQUEST, $method );
+ case "DELETE" :
+ curl_setopt ($req , CURLOPT_CUSTOMREQUEST, $method );
+ break ;
+ default :
+ break ;
+ }
+ $reqId = rand (100 ,999 );
+ if (self::$debugMode ) {
+ error_log ("[DEBUG] HEADERS {$reqId} :" . json_encode ($headersList ));
+ error_log ("[DEBUG] REQUEST {$reqId} : {$method} {$url} {$json} " );
+ }
+
+ $resp = curl_exec ($req );
+ $respCode = curl_getinfo ($req , CURLINFO_HTTP_CODE);
+ $respType = curl_getinfo ($req , CURLINFO_CONTENT_TYPE);
+ $error = curl_error ($req );
+ $errno = curl_errno ($req );
+ curl_close ($req );
+
+ if (self::$debugMode ) {
+ error_log ("[DEBUG] RESPONSE {$reqId} : {$resp} " );
+ }
+
+
+ if ($errno > 0 ) {
+ throw new \RuntimeException("CURL connection ( $url ) error: " .
+ " $errno $error " ,
+ $errno );
+ }
+ if (strpos ($respType , "text/html" ) !== false ) {
+ throw new CloudException("Bad response type text/html" , -1 , $respCode ,
+ $method , $url );
+ }
+
+ $data = json_decode ($resp , true );
+ if (isset ($data ["error" ])) {
+ $code = isset ($data ["code" ]) ? $data ["code" ] : -1 ;
+ throw new CloudException(" {$data['error']} " , $code , $respCode ,
+ $method , $url );
+ }
+ return $data ;
+ }
+
+
+ public static function get($path , $data =null , $sessionToken =null ,
+ $headers =array (), $useMasterKey =null ) {
+ return self::request("GET" , $path , $data , $sessionToken ,
+ $headers , $useMasterKey );
+ }
+
+
+ public static function post($path , $data , $sessionToken =null ,
+ $headers =array (), $useMasterKey =null ) {
+ return self::request("POST" , $path , $data , $sessionToken ,
+ $headers , $useMasterKey );
+ }
+
+
+ public static function put($path , $data , $sessionToken =null ,
+ $headers =array (), $useMasterKey =null ) {
+ return self::request("PUT" , $path , $data , $sessionToken ,
+ $headers , $useMasterKey );
+ }
+
+
+ public static function delete ($path , $sessionToken =null ,
+ $headers =array (), $useMasterKey =null ) {
+ return self::request("DELETE" , $path , null , $sessionToken ,
+ $headers , $useMasterKey );
+ }
+
+
+ public static function batch($requests , $sessionToken =null ,
+ $headers =array (), $useMasterKey =null ) {
+ $response = Client::post("/batch" ,
+ array ("requests" => $requests ),
+ $sessionToken ,
+ $headers ,
+ $useMasterKey );
+
+ $batchRequestError = new BatchRequestError();
+ forEach ($requests as $i => $req ) {
+ if (isset ($response [$i ]["error" ])) {
+ $batchRequestError ->add($req , $response [$i ]["error" ]);
+ }
+ }
+
+ if (!$batchRequestError ->isEmpty()) {
+ throw $batchRequestError ;
+ }
+
+ return $response ;
+ }
+
+
+ public static function encode($value ,
+ $encoder =null ,
+ $seen =array ()) {
+ if (is_null ($value ) || is_scalar ($value )) {
+ return $value ;
+ } else if (($value instanceof \DateTime) ||
+ ($value instanceof \DateTimeImmutable)) {
+ return array ("__type" => "Date" ,
+ "iso" => self::formatDate($value ));
+ } else if ($value instanceof LeanObject) {
+ if ($encoder && $value ->hasData() && !in_array ($value , $seen )) {
+ $seen [] = $value ;
+ return call_user_func (array ($value , $encoder ), $seen );
+ } else {
+ return $value ->getPointer();
+ }
+ } else if ($value instanceof IOperation ||
+ $value instanceof GeoPoint ||
+ $value instanceof Bytes ||
+ $value instanceof ACL ||
+ $value instanceof Relation ||
+ $value instanceof File ) {
+ return $value ->encode();
+ } else if (is_array ($value )) {
+ $res = array ();
+ forEach ($value as $key => $val ) {
+ $res [$key ] = self::encode($val , $encoder , $seen );
+ }
+ return $res ;
+ } else {
+ throw new \RuntimeException("Dont know how to encode " .
+ gettype ($value ));
+ }
+ }
+
+
+ public static function formatDate($date ) {
+ $utc = clone $date ;
+ $utc ->setTimezone(new \DateTimezone("UTC" ));
+ $iso = $utc ->format("Y-m-d\TH:i:s.u" );
+
+
+ $iso = substr ($iso , 0 , 23 ) . "Z" ;
+ return $iso ;
+ }
+
+
+ public static function decode($value , $key ) {
+ if (!is_array ($value )) {
+ return $value ;
+ }
+ if ($key === 'ACL' ) {
+ return new ACL($value );
+ }
+ if (!isset ($value ["__type" ])) {
+ $out = array ();
+ forEach ($value as $k => $v ) {
+ $out [$k ] = self::decode($v , $k );
+ }
+ return $out ;
+ }
+
+
+ $type = $value ["__type" ];
+
+ if ($type === "Date" ) {
+
+ $date = new \DateTime($value ["iso" ]);
+ $date ->setTimezone(new \DateTimeZone(date_default_timezone_get ()));
+ return $date ;
+ }
+ if ($type === "Bytes" ) {
+ return Bytes::createFromBase64Data($value ["base64" ]);
+ }
+ if ($type === "GeoPoint" ) {
+ return new GeoPoint($value ["latitude" ], $value ["longitude" ]);
+ }
+ if ($type === "File" ) {
+ $file = new File ($value ["name" ]);
+ $file ->mergeAfterFetch($value );
+ return $file ;
+ }
+ if ($type === "Pointer" || $type === "Object" ) {
+ $id = isset ($value ["objectId" ]) ? $value ["objectId" ] : null ;
+ $obj = LeanObject::create($value ["className" ], $id );
+ unset ($value ["__type" ]);
+ unset ($value ["className" ]);
+ if (!empty ($value )) {
+ $obj ->mergeAfterFetch($value );
+ }
+ return $obj ;
+ }
+ if ($type === "Relation" ) {
+ return new Relation(null , $key , $value ["className" ]);
+ }
+ }
+
+
+ public static function getStorage() {
+ return self::$storage ;
+ }
+
+
+ public static function setStorage($storage ) {
+ self::$storage = $storage ;
+ }
+
+
+ public static function randomFloat($min =0 , $max =1 ) {
+ $M = mt_getrandmax ();
+ return $min + (mt_rand (0 , $M - 1 ) / $M ) * ($max - $min );
+ }
+
+ }
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.CloudException.html b/docs/source-class-LeanCloud.CloudException.html
new file mode 100644
index 0000000..bd09c36
--- /dev/null
+++ b/docs/source-class-LeanCloud.CloudException.html
@@ -0,0 +1,191 @@
+
+
+
+
+
+
+ File LeanCloud/CloudException.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44:
+
<?php
+namespace LeanCloud;
+
+
+ class CloudException extends \Exception {
+
+
+ public $status ;
+
+
+ public $method ;
+
+
+ public $url ;
+
+ public function __construct($message , $code = 1 , $status = 400 ,
+ $method =null , $url =null ) {
+ parent::__construct($message , $code );
+ $this ->status = $status ;
+ $this ->method = $method ;
+ $this ->url = $url ;
+ }
+
+ public function __toString() {
+ $req = $this ->method ? ": {$this->method} {$this->url} " : "" ;
+ return __CLASS__ . ": [ {$this->code} ] {$this->message}{$req} \n" ;
+ }
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Engine.Cloud.html b/docs/source-class-LeanCloud.Engine.Cloud.html
new file mode 100644
index 0000000..c8deba7
--- /dev/null
+++ b/docs/source-class-LeanCloud.Engine.Cloud.html
@@ -0,0 +1,510 @@
+
+
+
+
+
+
+ File LeanCloud/Engine/Cloud.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363:
+
<?php
+namespace LeanCloud\Engine;
+
+ use LeanCloud\Client;
+
+
+
+ class Cloud {
+
+
+ private static $repo = array ();
+
+
+ private static $hookMap = array (
+ "beforeSave" => "__before_save_for_" ,
+ "afterSave" => "__after_save_for_" ,
+ "beforeUpdate" => "__before_update_for_" ,
+ "afterUpdate" => "__after_update_for_" ,
+ "beforeDelete" => "__before_delete_for_" ,
+ "afterDelete" => "__after_delete_for_" ,
+ "onLogin" => "__on_login_" ,
+ "onVerified" => "__on_verified_" ,
+ "onComplete" => "__on_complete_"
+ );
+
+ public static function getKeys() {
+ return array_keys (self::$repo );
+ }
+
+
+ private static function getFunc($funcName ) {
+ return (isset (self::$repo [$funcName ]) ? self::$repo [$funcName ] : null );
+ }
+
+
+ private static function getHookPrefix($hookName ) {
+ return (isset (self::$hookMap [$hookName ]) ?
+ self::$hookMap [$hookName ] : null );
+ }
+
+
+ public static function define ($funcName , $func ) {
+ self::$repo [$funcName ] = $func ;
+ }
+
+
+ public static function beforeSave($className , $func ) {
+ $name = self::getHookPrefix("beforeSave" ) . $className ;
+ self::define ($name , $func );
+ }
+
+
+ public static function afterSave($className , $func ) {
+ $name = self::getHookPrefix("afterSave" ) . $className ;
+ self::define ($name , $func );
+ }
+
+
+ public static function beforeUpdate($className , $func ) {
+ $name = self::getHookPrefix("beforeUpdate" ) . $className ;
+ self::define ($name , $func );
+ }
+
+
+ public static function afterUpdate($className , $func ) {
+ $name = self::getHookPrefix("afterUpdate" ) . $className ;
+ self::define ($name , $func );
+ }
+
+
+ public static function beforeDelete($className , $func ) {
+ $name = self::getHookPrefix("beforeDelete" ) . $className ;
+ self::define ($name , $func );
+ }
+
+
+ public static function afterDelete($className , $func ) {
+ $name = self::getHookPrefix("afterDelete" ) . $className ;
+ self::define ($name , $func );
+ }
+
+
+ public static function onLogin($func ) {
+ self::define ("__on_login__User" , $func );
+ }
+
+
+
+ public static function onVerified($type , $func ) {
+ self::define ("__on_verified_ {$type} " , $func );
+ }
+
+
+ public static function onInsight($func ) {
+ self::define ("__on_complete_bigquery_job" , $func );
+ }
+
+
+ public static function run($funcName , $params , $user =null , $meta =array ()) {
+ $func = self::getFunc($funcName );
+ if (!$func ) {
+ throw new FunctionError("Cloud function not found." , 404 );
+ }
+ return call_user_func ($func , $params , $user , $meta );
+ }
+
+
+ public static function start() {
+ Client::initialize(
+ getenv ("LEANCLOUD_APP_ID" ),
+ getenv ("LEANCLOUD_APP_KEY" ),
+ getenv ("LEANCLOUD_APP_MASTER_KEY" )
+ );
+
+ $engine = new LeanEngine();
+ $engine ->start();
+ }
+
+ public static function stop() {
+
+ }
+
+
+ public static function runHook($className , $hookName , $object ,
+ $user =null ,
+ $meta =array ()) {
+ $name = self::getHookPrefix($hookName ) . $className ;
+ $func = self::getFunc($name );
+ if (!$func ) {
+ throw new FunctionError("Cloud hook ` {$name} ' not found." ,
+ 404 );
+ }
+ return call_user_func ($func , $object , $user , $meta );
+ }
+
+
+ public static function runOnLogin($user , $meta =array ()) {
+ return self::runHook("_User" , "onLogin" , $user , $meta );
+ }
+
+
+ public static function runOnVerified($type , $user , $meta =array ()) {
+ $name = "__on_verified_ {$type} " ;
+ $func = self::getFunc($name );
+ if (!$func ) {
+ throw new FunctionError("Cloud hook ` {$name} ' not found." ,
+ 404 );
+ }
+ return call_user_func ($func , $user , $meta );
+ }
+
+
+ public static function runOnInsight($params , $meta =array ()) {
+ $name = "__on_complete_bigquery_job" ;
+ $func = self::getFunc($name );
+ if (!$func ) {
+ throw new FunctionError("Cloud hook ` {$name} ' not found." ,
+ 404 );
+ }
+ return call_user_func ($func , $params , $meta );
+ }
+ }
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Engine.FunctionError.html b/docs/source-class-LeanCloud.Engine.FunctionError.html
new file mode 100644
index 0000000..a5025e2
--- /dev/null
+++ b/docs/source-class-LeanCloud.Engine.FunctionError.html
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+ File LeanCloud/Engine/FunctionError.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
+
<?php
+namespace LeanCloud\Engine;
+
+
+ class FunctionError extends \Exception {
+
+
+ public $status ;
+
+ public function __construct($message , $code = 1 , $status = 400 ) {
+ parent::__construct($message , $code );
+ $this ->status = $status ;
+ }
+
+ public function __toString() {
+ return __CLASS__ . ": [ {$this->code} ] {$this->message} \n" ;
+ }
+ }
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Engine.LaravelEngine.html b/docs/source-class-LeanCloud.Engine.LaravelEngine.html
new file mode 100644
index 0000000..7e4c3cd
--- /dev/null
+++ b/docs/source-class-LeanCloud.Engine.LaravelEngine.html
@@ -0,0 +1,202 @@
+
+
+
+
+
+
+ File LeanCloud/Engine/LaravelEngine.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Engine.LeanEngine.html b/docs/source-class-LeanCloud.Engine.LeanEngine.html
new file mode 100644
index 0000000..8ea0827
--- /dev/null
+++ b/docs/source-class-LeanCloud.Engine.LeanEngine.html
@@ -0,0 +1,771 @@
+
+
+
+
+
+
+ File LeanCloud/Engine/LeanEngine.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 527: 528: 529: 530: 531: 532: 533: 534: 535: 536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581: 582: 583: 584: 585: 586: 587: 588: 589: 590: 591: 592: 593: 594: 595: 596: 597: 598: 599: 600: 601: 602: 603: 604: 605: 606: 607: 608: 609: 610: 611: 612: 613: 614: 615: 616: 617: 618: 619: 620: 621: 622: 623: 624:
+
<?php
+
+ namespace LeanCloud\Engine;
+
+ use LeanCloud\Client;
+use LeanCloud\User;
+use LeanCloud\CloudException;
+
+ class LeanEngine {
+
+
+ private static $allowedHeaders = array (
+ 'X-LC-Id' , 'X-LC-Key' , 'X-LC-Session' , 'X-LC-Sign' , 'X-LC-Prod' ,
+ 'X-LC-UA' ,
+ 'X-Uluru-Application-Key' ,
+ 'X-Uluru-Application-Id' ,
+ 'X-Uluru-Application-Production' ,
+ 'X-Uluru-Client-Version' ,
+ 'X-Uluru-Session-Token' ,
+ 'X-AVOSCloud-Application-Key' ,
+ 'X-AVOSCloud-Application-Id' ,
+ 'X-AVOSCloud-Application-Production' ,
+ 'X-AVOSCloud-Client-Version' ,
+ 'X-AVOSCloud-Super-Key' ,
+ 'X-AVOSCloud-Session-Token' ,
+ 'X-AVOSCloud-Request-sign' ,
+ 'X-Requested-With' ,
+ 'Content-Type'
+ );
+
+
+ protected static $useHttpsRedirect = false ;
+
+
+ protected $env = array ();
+
+
+ protected function getHeaderLine($key ) {
+ if (isset ($_SERVER [$key ])) {
+ return $_SERVER [$key ];
+ }
+ return null ;
+ }
+
+
+ protected function withHeader($key , $val ) {
+ header (" {$key} : {$val} " );
+ return $this ;
+ }
+
+
+ protected function send($body , $status ) {
+ http_response_code ($status );
+ echo $body ;
+ exit ;
+ }
+
+
+ protected function redirect($url ) {
+ http_response_code (302 );
+ header ("Location: {$url} " );
+ exit ;
+ }
+
+
+ protected function getBody() {
+ $body = file_get_contents ("php://input" );
+ return $body ;
+ }
+
+
+ private function renderJSON($data =null , $status =200 ) {
+ $out = is_null ($data ) ? "" : json_encode ($data );
+ $this ->withHeader("Content-Type" ,
+ "application/json; charset=utf-8;" )
+ ->send($out , $status );
+ }
+
+
+ private function renderError($message , $code =1 , $status =400 ) {
+ $data = json_encode (array (
+ "code" => $code ,
+ "error" => $message
+ ));
+ $this ->withHeader("Content-Type" , "application; charset=utf-8;" )
+ ->send($data , $status );
+ }
+
+
+ private function retrieveHeader($keys ) {
+ $val = null ;
+ forEach ($keys as $k ) {
+ $val = $this ->getHeaderLine($k );
+ if (!empty ($val )) {
+ return $val ;
+ }
+ }
+ return $val ;
+ }
+
+
+ private function parseHeaders() {
+ $this ->env["ORIGIN" ] = $this ->retrieveHeader(array (
+ "ORIGIN" ,
+ "HTTP_ORIGIN"
+ ));
+ $this ->env["CONTENT_TYPE" ] = $this ->retrieveHeader(array (
+ "CONTENT_TYPE" ,
+ "HTTP_CONTENT_TYPE"
+ ));
+ $this ->env["REMOTE_ADDR" ] = $this ->retrieveHeader(array (
+ "X_REAL_IP" ,
+ "HTTP_X_REAL_IP" ,
+ "X_FORWARDED_FOR" ,
+ "HTTP_X_FORWARDED_FOR" ,
+ "REMOTE_ADDR"
+ ));
+
+ $this ->env["LC_ID" ] = $this ->retrieveHeader(array (
+ "X_LC_ID" ,
+ "HTTP_X_LC_ID" ,
+ "X_AVOSCLOUD_APPLICATION_ID" ,
+ "HTTP_X_AVOSCLOUD_APPLICATION_ID" ,
+ "X_ULURU_APPLICATION_ID" ,
+ "HTTP_X_ULURU_APPLICATION_ID"
+ ));
+ $this ->env["LC_KEY" ] = $this ->retrieveHeader(array (
+ "X_LC_KEY" ,
+ "HTTP_X_LC_KEY" ,
+ "X_AVOSCLOUD_APPLICATION_KEY" ,
+ "HTTP_X_AVOSCLOUD_APPLICATION_KEY" ,
+ "X_ULURU_APPLICATION_KEY" ,
+ "HTTP_X_ULURU_APPLICATION_KEY"
+ ));
+ $this ->env["LC_MASTER_KEY" ] = $this ->retrieveHeader(array (
+ "X_AVOSCLOUD_MASTER_KEY" ,
+ "HTTP_X_AVOSCLOUD_MASTER_KEY" ,
+ "X_ULURU_MASTER_KEY" ,
+ "HTTP_X_ULURU_MASTER_KEY"
+ ));
+ $this ->env["LC_SESSION" ] = $this ->retrieveHeader(array (
+ "X_LC_SESSION" ,
+ "HTTP_X_LC_SESSION" ,
+ "X_AVOSCLOUD_SESSION_TOKEN" ,
+ "HTTP_X_AVOSCLOUD_SESSION_TOKEN" ,
+ "X_ULURU_SESSION_TOKEN" ,
+ "HTTP_X_ULURU_SESSION_TOKEN"
+ ));
+ $this ->env["LC_SIGN" ] = $this ->retrieveHeader(array (
+ "X_LC_SIGN" ,
+ "HTTP_X_LC_SIGN" ,
+ "X_AVOSCLOUD_REQUEST_SIGN" ,
+ "HTTP_X_AVOSCLOUD_REQUEST_SIGN"
+ ));
+ $prod = $this ->retrieveHeader(array (
+ "X_LC_PROD" ,
+ "HTTP_X_LC_PROD" ,
+ "X_AVOSCLOUD_APPLICATION_PRODUCTION" ,
+ "HTTP_X_AVOSCLOUD_APPLICATION_PRODUCTION" ,
+ "X_ULURU_APPLICATION_PRODUCTION" ,
+ "HTTP_X_ULURU_APPLICATION_PRODUCTION"
+ ));
+ $this ->env["useProd" ] = true ;
+ if ($prod === 0 || $prod === false ) {
+ $this ->env["useProd" ] = false ;
+ }
+ $this ->env["useMaster" ] = false ;
+ }
+
+
+ private function parsePlainBody($body ) {
+ $data = json_decode ($body , true );
+ if (!empty ($data )) {
+ $this ->env["LC_ID" ] = isset ($data ["_ApplicationId" ]) ?
+ $data ["_ApplicationId" ] : null ;
+ $this ->env["LC_KEY" ] = isset ($data ["_ApplicationKey" ]) ?
+ $data ["_ApplicationKey" ] : null ;
+ $this ->env["LC_MASTER_KEY" ] = isset ($data ["_MasterKey" ]) ?
+ $data ["_MasterKey" ] : null ;
+ $this ->env["LC_SESSION" ] = isset ($data ["_SessionToken" ]) ?
+ $data ["_SessionToken" ] : null ;
+ $this ->env["LC_SIGN" ] = null ;
+ $this ->env["useProd" ] = isset ($data ["_ApplicationProduction" ]) ?
+ (true && $data ["_ApplicationProduction" ]) :
+ true ;
+ $this ->env["useMaster" ] = false ;
+
+
+
+ forEach ($data as $key ) {
+ if ($key [0 ] === "_" && $key [1 ] !== "_" ) {
+ unset ($data [$key ]);
+ }
+ }
+ }
+ return $data ;
+ }
+
+
+ private function authRequest() {
+ $appId = $this ->env["LC_ID" ];
+ $sign = $this ->env["LC_SIGN" ];
+ if ($sign && Client::verifySign($appId , $sign )) {
+ if (strpos ($sign , "master" ) !== false ) {
+ $this ->env["useMaster" ] = true ;
+ }
+ return true ;
+ }
+
+ $appKey = $this ->env["LC_KEY" ];
+ if ($appKey && Client::verifyKey($appId , $appKey )) {
+ if (strpos ($appKey , "master" ) !== false ) {
+ $this ->env["useMaster" ] = true ;
+ }
+ return true ;
+ }
+
+ $masterKey = $this ->env["LC_MASTER_KEY" ];
+ $key = " {$masterKey} , master" ;
+ if ($masterKey && Client::verifyKey($appId , $key )) {
+ $this ->env["useMaster" ] = true ;
+ return true ;
+ }
+
+ $this ->renderError("Unauthorized" , 401 , 401 );
+ }
+
+ private function verifyHookSign($hookName , $sign ){
+ if (Client::verifyHookSign($hookName , $sign )) return true ;
+ error_log ("Invalid hook sign for {$hookName} " );
+ $this ->renderError("Unauthorized" , 142 , 401 );
+ }
+
+
+ private function processSession() {
+ $token = $this ->env["LC_SESSION" ];
+ if ($token ) {
+ User::become($token );
+ }
+ }
+
+
+ private function __dispatch($method , $url ) {
+ if (static ::$useHttpsRedirect ) {
+ $this ->httpsRedirect();
+ }
+ $path = parse_url ($url , PHP_URL_PATH);
+ $path = rtrim ($path , "/" );
+ if (strpos ($path , "/__engine/1/ping" ) === 0 ) {
+ $this ->renderJSON(array (
+ "runtime" => "php-" . phpversion (),
+ "version" => Client::VERSION
+ ));
+ }
+
+ $this ->parseHeaders();
+
+ $pathParts = array ();
+ if (preg_match ("/^\/(1|1\.1)\/(functions|call)(.*)/" ,
+ $path ,
+ $pathParts ) === 1 ) {
+ $pathParts ["version" ] = $pathParts [1 ];
+ $pathParts ["endpoint" ] = $pathParts [2 ];
+ $pathParts ["extra" ] = $pathParts [3 ];
+ $origin = $this ->env["ORIGIN" ];
+ $this ->withHeader("Access-Control-Allow-Origin" ,
+ $origin ? $origin : "*" );
+ if ($method == "OPTIONS" ) {
+ $this ->withHeader("Access-Control-Max-Age" , 86400 )
+ ->withHeader("Access-Control-Allow-Methods" ,
+ "PUT, GET, POST, DELETE, OPTIONS" )
+ ->withHeader("Access-Control-Allow-Headers" ,
+ implode (", " , self::$allowedHeaders ))
+ ->withHeader("Content-Length" , 0 )
+ ->renderJSON();
+ }
+
+ $body = $this ->getBody();
+ if (preg_match ("/text\/plain/" , $this ->env["CONTENT_TYPE" ])) {
+
+
+
+ $json = $this ->parsePlainBody($body );
+ } else {
+ $json = json_decode ($body , true );
+ }
+
+ $this ->authRequest();
+ $this ->processSession();
+ if (strpos ($pathParts ["extra" ], "/_ops/metadatas" ) === 0 ) {
+ if ($this ->env["useMaster" ]) {
+ $this ->renderJSON(array ("result" => Cloud::getKeys()));
+ } else {
+ $this ->renderError("Unauthorized." , 401 , 401 );
+ }
+ }
+
+
+
+ $funcParams = explode ("/" , ltrim ($pathParts ["extra" ], "/" ));
+ if (count ($funcParams ) == 1 ) {
+
+ $this ->dispatchFunc($funcParams [0 ], $json ,
+ $pathParts ["endpoint" ] === "call" );
+ } else {
+ if ($funcParams [0 ] == "onVerified" ) {
+
+ $this ->dispatchOnVerified($funcParams [1 ], $json );
+ } else if ($funcParams [0 ] == "_User" &&
+ $funcParams [1 ] == "onLogin" ) {
+
+ $this ->dispatchOnLogin($json );
+ } else if ($funcParams [0 ] == "BigQuery" ||
+ $funcParams [0 ] == "Insight" ) {
+
+ $this ->dispatchOnInsight($json );
+ } else if (count ($funcParams ) == 2 ) {
+
+ $this ->dispatchHook($funcParams [0 ], $funcParams [1 ], $json );
+ }
+ }
+ }
+ }
+
+
+ private function dispatchFunc($funcName , $body , $decodeObj =false ) {
+
+ if (in_array ($funcName , array (
+ '_messageReceived' , '_receiversOffline' , '_messageSent' , '_messageUpdate' ,
+ '_conversationStart' , '_conversationStarted' ,
+ '_conversationAdd' , '_conversationAdded' , '_conversationRemove' , '_conversationRemoved' , '_conversationUpdate' ,
+ '_clientOnline' , '_clientOffline' , '_rtmClientSign'
+ ))) {
+ static ::verifyHookSign($funcName , $body ["__sign" ]);
+ }
+
+ $params = $body ;
+ if ($decodeObj ) {
+ $params = Client::decode($body , null );
+ }
+
+ $meta ["remoteAddress" ] = $this ->env["REMOTE_ADDR" ];
+ $result = Cloud::run($funcName ,
+ $params ,
+ User::getCurrentUser(),
+ $meta );
+ if ($decodeObj ) {
+
+ $out = Client::encode($result , "toFullJSON" );
+ } else {
+
+ $out = Client::encode($result , "toJSON" );
+ }
+ $this ->renderJSON(array ("result" => $out ));
+ }
+
+
+ private function dispatchHook($className , $hookName , $body ) {
+ $verified = false ;
+ if (strpos ($hookName , "before" ) === 0 ) {
+ $this ->verifyHookSign("__before_for_ {$className} " ,
+ $body ["object" ]["__before" ]);
+ } else {
+ $this ->verifyHookSign("__after_for_ {$className} " ,
+ $body ["object" ]["__after" ]);
+ }
+
+ $json = $body ["object" ];
+ $json ["__type" ] = "Object" ;
+ $json ["className" ] = $className ;
+ $obj = Client::decode($json , null );
+
+
+
+
+ if (strpos ($hookName , "before" ) === 0 ) {
+ if (isset ($json ["__before" ])) {
+ $obj ->set("__before" , $json ["__before" ]);
+ } else {
+ $obj ->disableBeforeHook();
+ }
+ } else {
+ if (isset ($json ["__after" ])) {
+ $obj ->set("__after" , $json ["__after" ]);
+ } else {
+ $obj ->disableAfterHook();
+ }
+ }
+
+
+
+ if (isset ($json ["_updatedKeys" ])) {
+ $obj ->updatedKeys = $json ["_updatedKeys" ];
+ }
+
+ $meta ["remoteAddress" ] = $this ->env["REMOTE_ADDR" ];
+ $result = Cloud::runHook($className ,
+ $hookName ,
+ $obj ,
+ User::getCurrentUser(),
+ $meta );
+ if ($hookName == "beforeDelete" ) {
+ $this ->renderJSON(array ());
+ } else if (strpos ($hookName , "after" ) === 0 ) {
+ $this ->renderJSON(array ("result" => "ok" ));
+ } else {
+
+ $this ->renderJSON($obj ->toJSON());
+ }
+ }
+
+
+ private function dispatchOnVerified($type , $body ) {
+ $this ->verifyHookSign("__on_verified_ {$type} " ,
+ $body ["object" ]["__sign" ]);
+
+ $userObj = Client::decode($body ["object" ], null );
+ User::saveCurrentUser($userObj );
+ $meta ["remoteAddress" ] = $this ->env["REMOTE_ADDR" ];
+ Cloud::runOnVerified($type , $userObj , $meta );
+ $this ->renderJSON(array ("result" => "ok" ));
+ }
+
+
+ private function dispatchOnLogin($body ) {
+ $this ->verifyHookSign("__on_login__User" ,
+ $body ["object" ]["__sign" ]);
+
+ $userObj = Client::decode($body ["object" ], null );
+ $meta ["remoteAddress" ] = $this ->env["REMOTE_ADDR" ];
+ Cloud::runOnLogin($userObj , $meta );
+ $this ->renderJSON(array ("result" => "ok" ));
+ }
+
+
+ private function dispatchOnInsight($body ) {
+ $this ->verifyHookSign("__on_complete_bigquery_job" ,
+ $body ["__sign" ]);
+
+ $meta ["remoteAddress" ] = $this ->env["REMOTE_ADDR" ];
+ Cloud::runOnInsight($body , $meta );
+ $this ->renderJSON(array ("result" => "ok" ));
+ }
+
+
+ protected function dispatch($method , $url ) {
+ try {
+ $this ->__dispatch($method , $url );
+ } catch (FunctionError $ex ) {
+ $status = (int) $ex ->status;
+ if ( $status >= 500 ) {
+ error_log ($ex );
+ error_log ($ex ->getTraceAsString());
+ }
+ $this ->renderError(" {$ex->getMessage()} " , $ex ->getCode(), $ex ->status);
+ } catch (CloudException $ex ) {
+ error_log ($ex );
+ error_log ($ex ->getTraceAsString());
+ $this ->renderError(" {$ex->getMessage()} " , $ex ->getCode(), $ex ->status);
+ } catch (\Exception $ex ) {
+ error_log ($ex );
+ error_log ($ex ->getTraceAsString());
+ $this ->renderError($ex ->getMessage(),
+ $ex ->getCode() ? $ex ->getCode() : 1 ,
+
+ 500 );
+ }
+ }
+
+
+ public function start() {
+ $this ->dispatch($_SERVER ["REQUEST_METHOD" ],
+ $_SERVER ["REQUEST_URI" ]);
+ }
+
+
+ private function httpsRedirect() {
+ $reqProto = $this ->getHeaderLine("HTTP_X_FORWARDED_PROTO" );
+ if ($reqProto === "http" &&
+ in_array (getenv ("LEANCLOUD_APP_ENV" ), array ("production" , "stage" ))) {
+ $url = "https:// {$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']} " ;
+ $this ->redirect($url );
+ }
+ }
+
+
+ public static function enableHttpsRedirect() {
+ static ::$useHttpsRedirect = true ;
+ }
+
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Engine.SlimEngine.html b/docs/source-class-LeanCloud.Engine.SlimEngine.html
new file mode 100644
index 0000000..b0f8545
--- /dev/null
+++ b/docs/source-class-LeanCloud.Engine.SlimEngine.html
@@ -0,0 +1,214 @@
+
+
+
+
+
+
+ File LeanCloud/Engine/SlimEngine.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.File.html b/docs/source-class-LeanCloud.File.html
new file mode 100644
index 0000000..d7c4255
--- /dev/null
+++ b/docs/source-class-LeanCloud.File.html
@@ -0,0 +1,607 @@
+
+
+
+
+
+
+ File LeanCloud/File.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460:
+
<?php
+namespace LeanCloud;
+
+ use LeanCloud\Client;
+use LeanCloud\CloudException;
+use LeanCloud\MIMEType;
+use LeanCloud\Uploader\SimpleUploader;
+
+
+ class File {
+
+
+ private $_data ;
+
+
+ private $_metaData ;
+
+
+ private $_source ;
+
+
+ public function __construct($name , $data =null , $mimeType =null ) {
+ $this ->_data["name" ] = $name ;
+ $this ->_data["key" ] = null ;
+ $this ->_source = $data ;
+
+ if (!$mimeType ) {
+ $ext = pathinfo ($name , PATHINFO_EXTENSION);
+ $mimeType = MIMEType::getType ($ext );
+ }
+ $this ->_data["mime_type" ] = $mimeType ;
+
+ $this ->_metaData["owner" ] = "unknown" ;
+ if (User::$currentUser ) {
+ $this ->_metaData["owner" ] = User::$currentUser ->getObjectId();
+ }
+ if ($this ->_source) {
+ $this ->_metaData["size" ] = strlen ($this ->_source);
+ }
+ }
+
+
+ public static function createWithUrl($name , $url , $mimeType =null ) {
+ $file = new File ($name , null , $mimeType );
+ $file ->_data["url" ] = $url ;
+ $file ->_metaData["__source" ] = "external" ;
+ return $file ;
+ }
+
+
+ public static function createWithData($name , $data , $mimeType =null ) {
+ $file = new File ($name , $data , $mimeType );
+ return $file ;
+ }
+
+
+ public static function createWithLocalFile($filepath , $mimeType =null , $name =null ) {
+ $content = file_get_contents ($filepath );
+ if ($content === false ) {
+ throw new \RuntimeException("Read file error at $filepath " );
+ }
+ if (!$name ) {
+ $name = basename ($filepath );
+ }
+ return static ::createWithData($name , $content , $mimeType );
+ }
+
+
+ public function get($key ) {
+ if (isset ($this ->_data[$key ])) {
+ return $this ->_data[$key ];
+ }
+ return null ;
+ }
+
+
+ public function getName() {
+ return $this ->get("name" );
+ }
+
+
+ public function getKey() {
+ return $this ->get("key" );
+ }
+
+ public function setKey($val ) {
+ $this ->_data["key" ] = $val ;
+ return $this ;
+ }
+
+
+ public function getObjectId() {
+ return $this ->get("objectId" );
+ }
+
+
+ public function getCreatedAt() {
+ return $this ->get("createdAt" );
+ }
+
+
+ public function getUpdatedAt() {
+ return $this ->get("updatedAt" );
+ }
+
+
+ public function getMimeType() {
+ return $this ->get("mime_type" );
+ }
+
+
+ public function getUrl() {
+ return $this ->get("url" );
+ }
+
+
+ public function getThumbUrl($width , $height , $quality =100 ,
+ $scaleToFit =true , $format ="png" ) {
+ if (!$this ->getUrl()) {
+ throw new \RuntimeException("File resource not available." );
+ }
+ if ($width < 0 || $height < 0 ) {
+ throw new \InvalidArgumentException("Width or height must" .
+ " be positve." );
+ }
+ if ($quality > 100 || $quality < 0 ) {
+ throw new \InvalidArgumentException("Quality must be between" .
+ " 0 and 100." );
+ }
+ $mode = $scaleToFit ? 2 : 1 ;
+ return $this ->getUrl() . "?imageView/ {$mode} /w/ {$width} /h/ {$height} " .
+ "/q/ {$quality} /format/ {$format} " ;
+ }
+
+
+ public function getSize() {
+ return $this ->getMeta("size" );
+ }
+
+
+ public function getOwnerId() {
+ return $this ->getMeta("owner" );
+ }
+
+
+ public function setMeta($key , $val ) {
+ $this ->_metaData[$key ] = $val ;
+ return $this ;
+ }
+
+
+ public function getMeta($key =null ) {
+ if (!$key ) {
+ return $this ->_metaData;
+ }
+
+ if (isset ($this ->_metaData[$key ])) {
+ return $this ->_metaData[$key ];
+ }
+ return null ;
+ }
+
+
+ private function isExternal() {
+ return $this ->getMeta("__source" ) === "external" ;
+ }
+
+
+ private function _mergeData($data , $meta =array ()) {
+
+
+ forEach (array ("createdAt" , "updatedAt" ) as $key ) {
+ if (isset ($data [$key ]) && is_string ($data [$key ])) {
+ $data [$key ] = array ("__type" => "Date" ,
+ "iso" => $data [$key ]);
+ }
+ }
+
+ forEach ($data as $key => $val ) {
+ $this ->_data[$key ] = Client::decode($val , $key );
+ }
+
+ forEach ($meta as $key => $val ) {
+ $this ->_metaData[$key ] = Client::decode($val , $key );
+ }
+ }
+
+
+ public function mergeAfterSave($data ) {
+ $meta = array ();
+ if (isset ($data ["metaData" ])) {
+ $meta = $data ["metaData" ];
+ unset ($data ["metaData" ]);
+ }
+ if (isset ($data ["size" ])) {
+ $meta ["size" ] = $data ["size" ];
+ unset ($data ["size" ]);
+ }
+ $this ->_mergeData($data , $meta );
+ }
+
+
+ public function mergeAfterFetch($data ) {
+ $meta = array ();
+ if (isset ($data ["metaData" ])) {
+ $meta = $data ["metaData" ];
+ unset ($data ["metaData" ]);
+ }
+ $this ->_mergeData($data , $meta );
+ }
+
+
+ public function isDirty() {
+ $id = $this ->getObjectId();
+ return empty ($id );
+ }
+
+
+ public function save() {
+ if (!$this ->isDirty()) {
+ return ;
+ }
+
+ $data = array (
+ "name" => $this ->getName(),
+ "ACL" => $this ->get("ACL" ),
+ "mime_type" => $this ->getMimeType(),
+ "metaData" => $this ->getMeta(),
+ );
+
+ if ($this ->isExternal()) {
+ $data ["url" ] = $this ->getUrl();
+ $resp = Client::post("/files" , $data );
+ $this ->mergeAfterSave($resp );
+ } else {
+ $key = $this ->getKey();
+ if (isset ($key )) {
+ $data ["key" ] = $key ;
+ }
+ $data ["__type" ] = "File" ;
+ $resp = Client::post("/fileTokens" , $data );
+ if (!isset ($resp ["token" ])) {
+
+ $resp ["token" ] = null ;
+ }
+
+ $callbackParams = array ("token" => $resp ["token" ]);
+ try {
+ $uploader = SimpleUploader::createUploader($resp ["provider" ]);
+ $uploader ->initialize($resp ["upload_url" ], $resp ["token" ]);
+ $uploader ->upload($this ->_source, $this ->getMimeType(), $key );
+ $callbackParams ["result" ] = true ;
+ } catch (\Exception $ex ) {
+ $callbackParams ["result" ] = false ;
+ throw $ex ;
+ } finally {
+ try {
+ Client::post("/fileCallback" , $callbackParams );
+ } catch (\Exception $ex ) {
+ error_log ("Request /fileCallback failed." );
+ }
+ }
+
+ forEach (array ("upload_url" , "token" ) as $k ) {
+ if (isset ($resp [$k ])) {
+ unset ($resp [$k ]);
+ }
+ }
+
+ $this ->mergeAfterSave($resp );
+ }
+ }
+
+
+ public static function fetch($objectId ) {
+ $file = new File ("" );
+ $resp = Client::get("/files/ {$objectId} " );
+ $file ->mergeAfterFetch($resp );
+ return $file ;
+ }
+
+
+ public function destroy() {
+ if (!$this ->getObjectId()) {
+ return false ;
+ }
+ Client::delete ("/files/ {$this->getObjectId()} " );
+ }
+
+
+ public function encode() {
+ if (!$this ->getObjectId()) {
+ throw new \RuntimeException("Cannot serialize unsaved file." );
+ }
+ return array (
+ "__type" => "File" ,
+ "id" => $this ->getObjectId(),
+ "name" => $this ->getName(),
+ "url" => $this ->getUrl()
+ );
+ }
+ }
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.GeoPoint.html b/docs/source-class-LeanCloud.GeoPoint.html
new file mode 100644
index 0000000..2ea66fb
--- /dev/null
+++ b/docs/source-class-LeanCloud.GeoPoint.html
@@ -0,0 +1,254 @@
+
+
+
+
+
+
+ File LeanCloud/GeoPoint.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107:
+
<?php
+
+ namespace LeanCloud;
+
+
+ class GeoPoint {
+
+ private $latitude ;
+
+
+ private $longitude ;
+
+
+ public function __construct($latitude =0.0 , $longitude =0.0 ) {
+ if ($latitude <= 90.0 && $latitude >= -90.0 &&
+ $longitude <= 180.0 && $longitude >= -180.0 ) {
+ $this ->latitude = $latitude ;
+ $this ->longitude = $longitude ;
+ } else {
+ throw new \InvalidArgumentException("Invalid latitude or " .
+ "longitude for geo point" );
+ }
+ }
+
+
+ public function getLatitude() {
+ return $this ->latitude;
+ }
+
+
+ public function getLongitude() {
+ return $this ->longitude;
+ }
+
+
+ public function radiansTo(GeoPoint $point ) {
+ $d2r = M_PI / 180.0 ;
+ $lat1rad = $this ->getLatitude() * $d2r ;
+ $lon1rad = $this ->getLongitude() * $d2r ;
+ $lat2rad = $point ->getLatitude() * $d2r ;
+ $lon2rad = $point ->getLongitude() * $d2r ;
+ $deltaLat = $lat1rad - $lat2rad ;
+ $deltaLon = $lon1rad - $lon2rad ;
+ $sinLat = sin ($deltaLat / 2 );
+ $sinLon = sin ($deltaLon / 2 );
+ $a = $sinLat * $sinLat +
+ cos ($lat1rad ) * cos ($lat2rad ) * $sinLon * $sinLon ;
+ $a = min (1.0 , $a );
+ return 2 * asin (sqrt ($a ));
+ }
+
+
+ public function kilometersTo(GeoPoint $point ) {
+ return $this ->radiansTo($point ) * 6371.0 ;
+ }
+
+
+ public function milesTo(GeoPoint $point ) {
+ return $this ->radiansTo($point ) * 3958.8 ;
+ }
+
+ public function encode() {
+ return array (
+ '__type' => 'GeoPoint' ,
+ 'latitude' => $this ->latitude,
+ 'longitude' => $this ->longitude
+ );
+ }
+ }
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.LeanObject.html b/docs/source-class-LeanCloud.LeanObject.html
new file mode 100644
index 0000000..8e1317a
--- /dev/null
+++ b/docs/source-class-LeanCloud.LeanObject.html
@@ -0,0 +1,939 @@
+
+
+
+
+
+
+ File LeanCloud/LeanObject.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 527: 528: 529: 530: 531: 532: 533: 534: 535: 536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581: 582: 583: 584: 585: 586: 587: 588: 589: 590: 591: 592: 593: 594: 595: 596: 597: 598: 599: 600: 601: 602: 603: 604: 605: 606: 607: 608: 609: 610: 611: 612: 613: 614: 615: 616: 617: 618: 619: 620: 621: 622: 623: 624: 625: 626: 627: 628: 629: 630: 631: 632: 633: 634: 635: 636: 637: 638: 639: 640: 641: 642: 643: 644: 645: 646: 647: 648: 649: 650: 651: 652: 653: 654: 655: 656: 657: 658: 659: 660: 661: 662: 663: 664: 665: 666: 667: 668: 669: 670: 671: 672: 673: 674: 675: 676: 677: 678: 679: 680: 681: 682: 683: 684: 685: 686: 687: 688: 689: 690: 691: 692: 693: 694: 695: 696: 697: 698: 699: 700: 701: 702: 703: 704: 705: 706: 707: 708: 709: 710: 711: 712: 713: 714: 715: 716: 717: 718: 719: 720: 721: 722: 723: 724: 725: 726: 727: 728: 729: 730: 731: 732: 733: 734: 735: 736: 737: 738: 739: 740: 741: 742: 743: 744: 745: 746: 747: 748: 749: 750: 751: 752: 753: 754: 755: 756: 757: 758: 759: 760: 761: 762: 763: 764: 765: 766: 767: 768: 769: 770: 771: 772: 773: 774: 775: 776: 777: 778: 779: 780: 781: 782: 783: 784: 785: 786: 787: 788: 789: 790: 791: 792:
+
<?php
+namespace LeanCloud;
+
+
+ use LeanCloud\Operation\IOperation;
+use LeanCloud\Operation\SetOperation;
+use LeanCloud\Operation\DeleteOperation;
+use LeanCloud\Operation\ArrayOperation;
+use LeanCloud\Operation\IncrementOperation;
+
+
+ class LeanObject {
+
+
+ public static $PRESERVED_KEYS = array ("objectId" , "updatedAt" , "createdAt" );
+
+
+ private static $_registeredClasses = array ();
+
+
+ private $_className ;
+
+
+ private $_data ;
+
+
+ private $_operationSet ;
+
+
+ private $_saveOption ;
+
+
+ public function __construct($className =null , $objectId =null ) {
+ $class = get_called_class ();
+ $name = static ::getRegisteredClassName();
+
+ $className = $className ? $className : $name ;
+ if (!$className || ($class !== __CLASS__ && $className !== $name )) {
+ throw new \InvalidArgumentException(
+ "className is invalid." );
+ }
+ $this ->_className = $className ;
+ $this ->_data = array ();
+ $this ->_operationSet = array ();
+ $this ->_data["objectId" ] = $objectId ;
+ }
+
+
+ public static function create($className , $objectId =null ) {
+ if (isset (self::$_registeredClasses [$className ])) {
+ return new self::$_registeredClasses [$className ]($className ,
+ $objectId );
+ } else {
+ return new LeanObject($className , $objectId );
+ }
+ }
+
+
+ public static function registerClass() {
+ if (isset (static ::$className )) {
+ $class = get_called_class ();
+ $name = static ::$className ;
+ if (isset (self::$_registeredClasses [$name ])) {
+ $prevClass = self::$_registeredClasses [$name ];
+ if ($class !== $prevClass ) {
+ throw new \RuntimeException("className ' $name ' " .
+ "has already been registered." );
+ }
+ } else {
+ self::$_registeredClasses [static ::$className ] = get_called_class ();
+ }
+ } else {
+ throw new \RuntimeException("Cannot register class without " .
+ "::className." );
+ }
+ }
+
+
+ private static function getRegisteredClassName() {
+ return array_search (get_called_class (), self::$_registeredClasses );
+ }
+
+
+ public function getClassName() {
+ return $this ->_className;
+ }
+
+ public function disableBeforeHook() {
+ $this ->_set("__before" ,
+ Client::signHook("__before_for_ {$this->getClassName()} " ,
+ round (microtime (true ) * 1000 )));
+ }
+
+ public function disableAfterHook() {
+ $this ->_set("__after" ,
+ Client::signHook("__after_for_ {$this->getClassName()} " ,
+ round (microtime (true ) * 1000 )));
+ }
+
+
+ public function getPointer() {
+ if (!$this ->getObjectId()) {
+ throw new \RuntimeException("LeanObject without ID cannot " .
+ "be serialized." );
+ }
+ return array (
+ "__type" => "Pointer" ,
+ "className" => $this ->getClassName(),
+ "objectId" => $this ->getObjectId(),
+ );
+ }
+
+
+ public function toJSON() {
+ $out = $this ->toFullJSON();
+ unset ($out ["__type" ]);
+ unset ($out ["className" ]);
+ return $out ;
+ }
+
+
+ public function toFullJSON($seen =array ()) {
+ $out = array ();
+ forEach ($this ->_data as $key => $val ) {
+ $out [$key ] = Client::encode($val , "toFullJSON" , $seen );
+ }
+ $out ["__type" ] = "Object" ;
+ $out ["className" ] = $this ->getClassName();
+ return $out ;
+ }
+
+
+ public function getObjectId() {
+ return $this ->get("objectId" );
+ }
+
+
+ public function getCreatedAt() {
+ return $this ->get("createdAt" );
+ }
+
+
+ public function getUpdatedAt() {
+ return $this ->get("updatedAt" );
+ }
+
+ private function _set($key , $val ) {
+ if ($key === "ACL" &&
+ !($val instanceof ACL)) {
+ throw new RuntimeException("Invalid ACL." );
+ }
+ if (!($val instanceof IOperation)) {
+ $val = new SetOperation($key , $val );
+ }
+ $this ->_applyOperation($val );
+ return $this ;
+ }
+
+
+ public function set($key , $val ) {
+ if (in_array ($key , self::$PRESERVED_KEYS )) {
+ throw new \RuntimeException("Preserved field could not be set." );
+ }
+ return $this ->_set($key , $val );
+ }
+
+
+ public function setACL(ACL $acl ) {
+ return $this ->_set("ACL" , $acl );
+ }
+
+
+ public function getACL() {
+ return $this ->get("ACL" );
+ }
+
+
+ public function delete ($key ) {
+ $this ->_applyOperation(new DeleteOperation($key ));
+ return $this ;
+ }
+
+
+ public function get($key ) {
+ if (!isset ($this ->_data[$key ])) {
+ return null ;
+ }
+ $val = $this ->_data[$key ];
+ if ($val instanceof Relation) {
+ return $this ->getRelation($key );
+ }
+ return $this ->_data[$key ];
+ }
+
+
+ public function increment($key , $amount = 1 ) {
+ $this ->_applyOperation(new IncrementOperation($key , $amount ));
+ return $this ;
+ }
+
+
+ private function _getPreviousOp($key ) {
+ if (isset ($this ->_operationSet[$key ])) {
+ return $this ->_operationSet[$key ];
+ }
+ return null ;
+ }
+
+
+ private function _applyOperation($operation ) {
+ $key = $operation ->getKey();
+ $oldval = $this ->get($key );
+ $newval = $operation ->applyOn($oldval , $this );
+ if ($newval !== null ) {
+ $this ->_data[$key ] = $newval ;
+ } else if (isset ($this ->_data[$key ])) {
+ unset ($this ->_data[$key ]);
+ }
+
+ $prevOp = $this ->_getPreviousOp($key );
+ $newOp = $prevOp ? $operation ->mergeWith($prevOp ) : $operation ;
+ $this ->_operationSet[$key ] = $newOp ;
+ }
+
+
+ public function addIn($key , $val ) {
+ $this ->_applyOperation(new ArrayOperation($key , array ($val ), "Add" ));
+ return $this ;
+ }
+
+
+ public function addUniqueIn($key , $val ) {
+ $this ->_applyOperation(new ArrayOperation($key ,
+ array ($val ),
+ "AddUnique" ));
+ return $this ;
+ }
+
+
+ public function removeIn($key , $val ) {
+ $this ->_applyOperation(new ArrayOperation($key , array ($val ), "Remove" ));
+ return $this ;
+ }
+
+
+ public function hasData() {
+ $keys = array_keys ($this ->_data);
+ return $keys !== array ("objectId" );
+ }
+
+
+ public function isDirty() {
+
+ return !empty ($this ->_operationSet);
+ }
+
+
+ private function getSaveData() {
+ return Client::encode($this ->_operationSet);
+ }
+
+
+ public function save($option =null ) {
+ if (!$this ->isDirty()) {return ;}
+ if ($option ) {
+ $this ->_saveOption = $option ;
+ }
+ try {
+ $result = self::saveAll(array ($this ));
+ } catch (BatchRequestError $batchRequestError ) {
+ $err = $batchRequestError ->getFirst();
+ if ($err )
+ throw new CloudException($err ["error" ], $err ["code" ]);
+ }
+ return $result ;
+ }
+
+
+ private function _mergeData($data ) {
+
+
+ forEach (array ("createdAt" , "updatedAt" ) as $key ) {
+ if (isset ($data [$key ]) && is_string ($data [$key ])) {
+ $data [$key ] = array ("__type" => "Date" ,
+ "iso" => $data [$key ]);
+ }
+ }
+
+ forEach ($data as $key => $val ) {
+ $this ->_data[$key ] = Client::decode($val , $key );
+ }
+ }
+
+
+ public function mergeAfterSave($data ) {
+ $this ->_operationSet = array ();
+ $this ->_mergeData($data );
+ }
+
+
+ public function mergeAfterFetch($data ) {
+ forEach ($data as $key => $val ) {
+ if (isset ($this ->_operationSet[$key ])) {
+ unset ($this ->_operationSet[$key ]);
+ }
+ }
+ $this ->_mergeData($data );
+ }
+
+
+ public function fetch() {
+ try {
+ static ::fetchAll(array ($this ));
+ } catch (BatchRequestError $batchRequestError ) {
+ $err = $batchRequestError ->getFirst();
+ if ($err )
+ throw new CloudException($err ["error" ], $err ["code" ]);
+ }
+ }
+
+
+ public function fetchAll($objects ) {
+ $batch = array ();
+ forEach ($objects as $obj ) {
+ if (!$obj ->getObjectId()) {
+ throw new \RuntimeException("Cannot fetch object without ID." );
+ }
+
+ $batch [$obj ->getObjectId()] = $obj ;
+ }
+ if (empty ($batch )) { return ; }
+
+ $requests = array ();
+ $objects = array ();
+ forEach ($batch as $obj ) {
+ $requests [] = array (
+ "path" => "/1.1/classes/ {$obj->getClassName()} " .
+ "/ {$obj->getObjectId()} " ,
+ "method" => "GET"
+ );
+ $objects [] = $obj ;
+ }
+
+ $sessionToken = User::getCurrentSessionToken();
+ $response = Client::batch($requests , $sessionToken );
+
+ $batchRequestError = new BatchRequestError();
+ forEach ($objects as $i => $obj ) {
+ if (isset ($response [$i ]["success" ])) {
+ if (!empty ($response [$i ]["success" ])) {
+ $obj ->mergeAfterFetch($response [$i ]["success" ]);
+ } else {
+ $batchRequestError ->add($requests [$i ],
+ array ("error" => "Object not found." ));
+ }
+ }
+ }
+ if (!$batchRequestError ->isEmpty()) {
+ throw $batchRequestError ;
+ }
+ }
+
+
+ public function destroy() {
+ if (!$this ->getObjectId()) {
+ return false ;
+ }
+
+ try {
+ static ::destroyAll(array ($this ));
+ } catch (BatchRequestError $batchRequestError ) {
+ $err = $batchRequestError ->getFirst();
+ if ($err )
+ throw new CloudException($err ["error" ], $err ["code" ]);
+ }
+ }
+
+
+ public function getQuery() {
+ return new Query($this ->getClassName());
+ }
+
+
+ public function getRelation($key ) {
+ $val = isset ($this ->_data[$key ]) ? $this ->_data[$key ] : null ;
+ if ($val ) {
+ if ($val instanceof Relation) {
+ $val ->setParentAndKey($this , $key );
+ return $val ;
+ } else {
+ throw new \RuntimeException("Field {$key} is not relation." );
+ }
+ }
+ return new Relation($this , $key );
+ }
+
+
+ public static function traverse($value , &$seen , $func ) {
+ if ($value instanceof LeanObject) {
+ if (!in_array ($value , $seen )) {
+ $seen [] = $value ;
+ static ::traverse($value ->_data, $seen , $func );
+ $func ($value );
+ }
+ } else if (is_array ($value )) {
+ forEach ($value as $val ) {
+ if (is_array ($val )) {
+ static ::traverse($val , $seen , $func );
+ } else if ($val instanceof LeanObject) {
+ static ::traverse($val , $seen , $func );
+ } else {
+ $func ($val );
+ }
+ }
+ } else {
+ $func ($value );
+ }
+ }
+
+
+ public function findUnsavedChildren() {
+ $unsavedChildren = array ();
+ $seen = array ($this );
+ static ::traverse($this ->_data, $seen ,
+ function ($val ) use (&$unsavedChildren ) {
+ if (($val instanceof LeanObject) ||
+ ($val instanceof File )) {
+ if ($val ->isDirty()) {
+ $unsavedChildren [] = $val ;
+ }
+ }
+ });
+ return $unsavedChildren ;
+ }
+
+
+ public static function saveAll($objects ) {
+ if (empty ($objects )) { return ; }
+
+
+ $unsavedChildren = array ();
+ forEach ($objects as $obj ) {
+ $unsavedChildren = array_merge ($unsavedChildren ,
+ $obj ->findUnsavedChildren());
+ }
+
+ $children = array ();
+ forEach ($unsavedChildren as $obj ) {
+ if ($obj instanceof File ) {
+ $obj ->save();
+ } else if ($obj instanceof LeanObject) {
+ if (!in_array ($obj , $children )) {
+ $children [] = $obj ;
+ }
+ }
+ }
+
+ static ::batchSave($children );
+ static ::batchSave($objects );
+ }
+
+
+ private static function batchSave($objects , $batchSize =20 ) {
+ if (empty ($objects )) { return ; }
+ $batch = array ();
+ $remaining = array ();
+ $count = 0 ;
+ forEach ($objects as $obj ) {
+ if (!$obj ->isDirty()) {
+ continue ;
+ }
+ if ($count > $batchSize ) {
+ $remaining [] = $obj ;
+ $count ++;
+ continue ;
+ }
+ $count ++;
+ $batch [] = $obj ;
+ }
+
+ $path = "/1.1/classes" ;
+ $requests = array ();
+ $objects = array ();
+ forEach ($batch as $obj ) {
+ $req = array ("body" => $obj ->getSaveData());
+ if ($obj ->getObjectId()) {
+ $req ["method" ] = "PUT" ;
+ $req ["path" ] = " {$path} / {$obj->getClassName()} " .
+ "/ {$obj->getObjectId()} " ;
+ } else {
+ $req ["method" ] = "POST" ;
+ $req ["path" ] = " {$path} / {$obj->getClassName()} " ;
+ }
+ if ($obj ->_saveOption) {
+ $req ["params" ] = $obj ->_saveOption->encode();
+ }
+ $requests [] = $req ;
+ $objects [] = $obj ;
+ }
+
+ $sessionToken = User::getCurrentSessionToken();
+ $response = Client::batch($requests , $sessionToken );
+
+ forEach ($objects as $i => $obj ) {
+ if (isset ($response [$i ]["success" ])) {
+ $obj ->mergeAfterSave($response [$i ]["success" ]);
+ }
+ }
+
+
+ static ::batchSave($remaining , $batchSize );
+ }
+
+
+ public static function destroyAll($objects ) {
+ $batch = array ();
+ forEach ($objects as $obj ) {
+ if (!$obj ->getObjectId()) {
+ throw new \RuntimeException("Cannot destroy object without ID" );
+ }
+
+ $batch [$obj ->getObjectId()] = $obj ;
+ }
+ if (empty ($batch )) { return ; }
+
+ $requests = array ();
+ $objects = array ();
+ forEach ($batch as $obj ) {
+ $requests [] = array (
+ "path" => "/1.1/classes/ {$obj->getClassName()} " .
+ "/ {$obj->getObjectId()} " ,
+ "method" => "DELETE"
+ );
+ $objects [] = $obj ;
+ }
+
+ $sessionToken = User::getCurrentSessionToken();
+ $response = Client::batch($requests , $sessionToken );
+ }
+ }
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.MIMEType.html b/docs/source-class-LeanCloud.MIMEType.html
new file mode 100644
index 0000000..88da8df
--- /dev/null
+++ b/docs/source-class-LeanCloud.MIMEType.html
@@ -0,0 +1,359 @@
+
+
+
+
+
+
+ File LeanCloud/MIMEType.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212:
+
<?php
+namespace LeanCloud;
+
+ class MIMEType {
+
+
+ public static function getType ($ext ) {
+ if (isset (static ::$knownTypes [$ext ])) {
+ return static ::$knownTypes [$ext ];
+ }
+ return null ;
+ }
+
+ public static $knownTypes = array (
+ "ai" => "application/postscript" ,
+ "aif" => "audio/x-aiff" ,
+ "aifc" => "audio/x-aiff" ,
+ "aiff" => "audio/x-aiff" ,
+ "asc" => "text/plain" ,
+ "atom" => "application/atom+xml" ,
+ "au" => "audio/basic" ,
+ "avi" => "video/x-msvideo" ,
+ "bcpio" => "application/x-bcpio" ,
+ "bin" => "application/octet-stream" ,
+ "bmp" => "image/bmp" ,
+ "cdf" => "application/x-netcdf" ,
+ "cgm" => "image/cgm" ,
+ "class" => "application/octet-stream" ,
+ "cpio" => "application/x-cpio" ,
+ "cpt" => "application/mac-compactpro" ,
+ "csh" => "application/x-csh" ,
+ "css" => "text/css" ,
+ "dcr" => "application/x-director" ,
+ "dif" => "video/x-dv" ,
+ "dir" => "application/x-director" ,
+ "djv" => "image/vnd.djvu" ,
+ "djvu" => "image/vnd.djvu" ,
+ "dll" => "application/octet-stream" ,
+ "dmg" => "application/octet-stream" ,
+ "dms" => "application/octet-stream" ,
+ "doc" => "application/msword" ,
+ "docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document" ,
+ "dotx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.template" ,
+ "docm" => "application/vnd.ms-word.document.macroEnabled.12" ,
+ "dotm" => "application/vnd.ms-word.template.macroEnabled.12" ,
+ "dtd" => "application/xml-dtd" ,
+ "dv" => "video/x-dv" ,
+ "dvi" => "application/x-dvi" ,
+ "dxr" => "application/x-director" ,
+ "eps" => "application/postscript" ,
+ "etx" => "text/x-setext" ,
+ "exe" => "application/octet-stream" ,
+ "ez" => "application/andrew-inset" ,
+ "gif" => "image/gif" ,
+ "gram" => "application/srgs" ,
+ "grxml" => "application/srgs+xml" ,
+ "gtar" => "application/x-gtar" ,
+ "hdf" => "application/x-hdf" ,
+ "hqx" => "application/mac-binhex40" ,
+ "htm" => "text/html" ,
+ "html" => "text/html" ,
+ "ice" => "x-conference/x-cooltalk" ,
+ "ico" => "image/x-icon" ,
+ "ics" => "text/calendar" ,
+ "ief" => "image/ief" ,
+ "ifb" => "text/calendar" ,
+ "iges" => "model/iges" ,
+ "igs" => "model/iges" ,
+ "jnlp" => "application/x-java-jnlp-file" ,
+ "jp2" => "image/jp2" ,
+ "jpe" => "image/jpeg" ,
+ "jpeg" => "image/jpeg" ,
+ "jpg" => "image/jpeg" ,
+ "js" => "application/x-javascript" ,
+ "kar" => "audio/midi" ,
+ "latex" => "application/x-latex" ,
+ "lha" => "application/octet-stream" ,
+ "lzh" => "application/octet-stream" ,
+ "m3u" => "audio/x-mpegurl" ,
+ "m4a" => "audio/mp4a-latm" ,
+ "m4b" => "audio/mp4a-latm" ,
+ "m4p" => "audio/mp4a-latm" ,
+ "m4u" => "video/vnd.mpegurl" ,
+ "m4v" => "video/x-m4v" ,
+ "mac" => "image/x-macpaint" ,
+ "man" => "application/x-troff-man" ,
+ "mathml" => "application/mathml+xml" ,
+ "me" => "application/x-troff-me" ,
+ "mesh" => "model/mesh" ,
+ "mid" => "audio/midi" ,
+ "midi" => "audio/midi" ,
+ "mif" => "application/vnd.mif" ,
+ "mov" => "video/quicktime" ,
+ "movie" => "video/x-sgi-movie" ,
+ "mp2" => "audio/mpeg" ,
+ "mp3" => "audio/mpeg" ,
+ "mp4" => "video/mp4" ,
+ "mpe" => "video/mpeg" ,
+ "mpeg" => "video/mpeg" ,
+ "mpg" => "video/mpeg" ,
+ "mpga" => "audio/mpeg" ,
+ "ms" => "application/x-troff-ms" ,
+ "msh" => "model/mesh" ,
+ "mxu" => "video/vnd.mpegurl" ,
+ "nc" => "application/x-netcdf" ,
+ "oda" => "application/oda" ,
+ "ogg" => "application/ogg" ,
+ "pbm" => "image/x-portable-bitmap" ,
+ "pct" => "image/pict" ,
+ "pdb" => "chemical/x-pdb" ,
+ "pdf" => "application/pdf" ,
+ "pgm" => "image/x-portable-graymap" ,
+ "pgn" => "application/x-chess-pgn" ,
+ "pic" => "image/pict" ,
+ "pict" => "image/pict" ,
+ "png" => "image/png" ,
+ "pnm" => "image/x-portable-anymap" ,
+ "pnt" => "image/x-macpaint" ,
+ "pntg" => "image/x-macpaint" ,
+ "ppm" => "image/x-portable-pixmap" ,
+ "ppt" => "application/vnd.ms-powerpoint" ,
+ "pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation" ,
+ "potx" => "application/vnd.openxmlformats-officedocument.presentationml.template" ,
+ "ppsx" => "application/vnd.openxmlformats-officedocument.presentationml.slideshow" ,
+ "ppam" => "application/vnd.ms-powerpoint.addin.macroEnabled.12" ,
+ "pptm" => "application/vnd.ms-powerpoint.presentation.macroEnabled.12" ,
+ "potm" => "application/vnd.ms-powerpoint.template.macroEnabled.12" ,
+ "ppsm" => "application/vnd.ms-powerpoint.slideshow.macroEnabled.12" ,
+ "ps" => "application/postscript" ,
+ "qt" => "video/quicktime" ,
+ "qti" => "image/x-quicktime" ,
+ "qtif" => "image/x-quicktime" ,
+ "ra" => "audio/x-pn-realaudio" ,
+ "ram" => "audio/x-pn-realaudio" ,
+ "ras" => "image/x-cmu-raster" ,
+ "rdf" => "application/rdf+xml" ,
+ "rgb" => "image/x-rgb" ,
+ "rm" => "application/vnd.rn-realmedia" ,
+ "roff" => "application/x-troff" ,
+ "rtf" => "text/rtf" ,
+ "rtx" => "text/richtext" ,
+ "sgm" => "text/sgml" ,
+ "sgml" => "text/sgml" ,
+ "sh" => "application/x-sh" ,
+ "shar" => "application/x-shar" ,
+ "silo" => "model/mesh" ,
+ "sit" => "application/x-stuffit" ,
+ "skd" => "application/x-koan" ,
+ "skm" => "application/x-koan" ,
+ "skp" => "application/x-koan" ,
+ "skt" => "application/x-koan" ,
+ "smi" => "application/smil" ,
+ "smil" => "application/smil" ,
+ "snd" => "audio/basic" ,
+ "so" => "application/octet-stream" ,
+ "spl" => "application/x-futuresplash" ,
+ "src" => "application/x-wais-source" ,
+ "sv4cpio" => "application/x-sv4cpio" ,
+ "sv4crc" => "application/x-sv4crc" ,
+ "svg" => "image/svg+xml" ,
+ "swf" => "application/x-shockwave-flash" ,
+ "t" => "application/x-troff" ,
+ "tar" => "application/x-tar" ,
+ "tcl" => "application/x-tcl" ,
+ "tex" => "application/x-tex" ,
+ "texi" => "application/x-texinfo" ,
+ "texinfo" => "application/x-texinfo" ,
+ "tif" => "image/tiff" ,
+ "tiff" => "image/tiff" ,
+ "tr" => "application/x-troff" ,
+ "tsv" => "text/tab-separated-values" ,
+ "txt" => "text/plain" ,
+ "ustar" => "application/x-ustar" ,
+ "vcd" => "application/x-cdlink" ,
+ "vrml" => "model/vrml" ,
+ "vxml" => "application/voicexml+xml" ,
+ "wav" => "audio/x-wav" ,
+ "wbmp" => "image/vnd.wap.wbmp" ,
+ "wbmxl" => "application/vnd.wap.wbxml" ,
+ "wml" => "text/vnd.wap.wml" ,
+ "wmlc" => "application/vnd.wap.wmlc" ,
+ "wmls" => "text/vnd.wap.wmlscript" ,
+ "wmlsc" => "application/vnd.wap.wmlscriptc" ,
+ "wrl" => "model/vrml" ,
+ "xbm" => "image/x-xbitmap" ,
+ "xht" => "application/xhtml+xml" ,
+ "xhtml" => "application/xhtml+xml" ,
+ "xls" => "application/vnd.ms-excel" ,
+ "xml" => "application/xml" ,
+ "xpm" => "image/x-xpixmap" ,
+ "xsl" => "application/xml" ,
+ "xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ,
+ "xltx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.template" ,
+ "xlsm" => "application/vnd.ms-excel.sheet.macroEnabled.12" ,
+ "xltm" => "application/vnd.ms-excel.template.macroEnabled.12" ,
+ "xlam" => "application/vnd.ms-excel.addin.macroEnabled.12" ,
+ "xlsb" => "application/vnd.ms-excel.sheet.binary.macroEnabled.12" ,
+ "xslt" => "application/xslt+xml" ,
+ "xul" => "application/vnd.mozilla.xul+xml" ,
+ "xwd" => "image/x-xwindowdump" ,
+ "xyz" => "chemical/x-xyz" ,
+ "zip" => "application/zip"
+ );
+
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Operation.ArrayOperation.html b/docs/source-class-LeanCloud.Operation.ArrayOperation.html
new file mode 100644
index 0000000..cf4738c
--- /dev/null
+++ b/docs/source-class-LeanCloud.Operation.ArrayOperation.html
@@ -0,0 +1,367 @@
+
+
+
+
+
+
+ File LeanCloud/Operation/ArrayOperation.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220:
+
<?php
+namespace LeanCloud\Operation;
+
+ use LeanCloud\Client;
+use LeanCloud\LeanObject;
+use LeanCloud\Operation\SetOperation;
+use LeanCloud\Operation\DeleteOperation;
+
+
+ class ArrayOperation implements IOperation {
+
+ private $key ;
+
+
+ private $value ;
+
+
+ private $opType ;
+
+
+ public function __construct($key , $val , $opType ) {
+ if (!in_array ($opType , array ("Add" , "AddUnique" , "Remove" ))) {
+ throw new \InvalidArgumentException("Operation on array not " .
+ "supported: {$opType} ." );
+ }
+ if (!is_array ($val )) {
+ throw new \InvalidArgumentException("Operand must be array." );
+ }
+ $this ->key = $key ;
+ $this ->value = $val ;
+ $this ->opType = $opType ;
+ }
+
+
+ public function getKey() {
+ return $this ->key ;
+ }
+
+
+ public function getOpType() {
+ return $this ->opType;
+ }
+
+
+ public function getValue() {
+ return $this ->value;
+ }
+
+
+ public function encode() {
+ return array (
+ "__op" => $this ->getOpType(),
+ "objects" => Client::encode($this ->value),
+ );
+ }
+
+
+ private function add($oldval ) {
+ return array_merge ($oldval , $this ->getValue());
+ }
+
+
+ private function addUnique($oldval ) {
+ $newval = $oldval ;
+ $found = array ();
+ forEach ($oldval as $obj ) {
+ if (($obj instanceof LeanObject) && ($obj ->getObjectId())) {
+ $found [$obj ->getObjectId()] = true ;
+ }
+ }
+ forEach ($this ->getValue() as $obj ) {
+ if (($obj instanceof LeanObject) && ($obj ->getObjectId())) {
+ if (isset ($found [$obj ->getObjectId()])) {
+
+ } else {
+ $found [$obj ->getObjectId()] = true ;
+ $newval [] = $obj ;
+ }
+ } else if (!in_array ($obj , $newval )) {
+ $newval [] = $obj ;
+ }
+ }
+ return $newval ;
+ }
+
+
+ private function remove($oldval ) {
+ $newval = array ();
+ $remove = $this ->getValue();
+ forEach ($oldval as $item ) {
+ if (!in_array ($item , $remove )) {
+ $newval [] = $item ;
+ }
+ }
+ return $newval ;
+ }
+
+
+ public function applyOn($oldval ) {
+ if (!$oldval ) { $oldval = array ();}
+
+ if (!is_array ($oldval )) {
+ throw new \RuntimeException("Operation incompatible" .
+ " with previous value." );
+ }
+
+
+ if ($this ->getOpType() === "Add" ) {
+ return $this ->add($oldval );
+ }
+ if ($this ->getOpType() === "AddUnique" ) {
+ return $this ->addUnique($oldval );
+ }
+ if ($this ->getOpType() === "Remove" ) {
+ return $this ->remove($oldval );
+ }
+ throw new \RuntimeException("Operation type {$this->getOptype()} " .
+ " not supported." );
+ }
+
+
+ public function mergeWith($prevOp ) {
+ if (!$prevOp ) {
+ return $this ;
+ } else if ($prevOp instanceof SetOperation) {
+ if (!is_array ($prevOp ->getValue())) {
+ throw new \RuntimeException("Operation incompatible " .
+ "with previous value." );
+ }
+ return new SetOperation($this ->key ,
+ $this ->applyOn($prevOp ->getValue()));
+ } else if (($prevOp instanceof ArrayOperation) &&
+ ($this ->getOpType() === $prevOp ->getOpType())) {
+ if ($this ->getOpType() === "Remove" ) {
+ $objects = array_merge ($prevOp ->getValue(), $this ->getValue());
+ } else {
+ $objects = $this ->applyOn($prevOp ->getValue());
+ }
+ return new ArrayOperation($this ->key ,
+ $objects ,
+ $this ->getOpType());
+ } else if ($prevOp instanceof DeleteOperation) {
+ if ($this ->getOpType() === "Remove" ) {
+ return $prevOp ;
+ } else {
+ return new SetOperation($this ->getKey(), $this ->applyOn(null ));
+ }
+ } else {
+ throw new \RuntimeException("Operation incompatible with" .
+ " previous one." );
+ }
+ }
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Operation.DeleteOperation.html b/docs/source-class-LeanCloud.Operation.DeleteOperation.html
new file mode 100644
index 0000000..433c48a
--- /dev/null
+++ b/docs/source-class-LeanCloud.Operation.DeleteOperation.html
@@ -0,0 +1,207 @@
+
+
+
+
+
+
+ File LeanCloud/Operation/DeleteOperation.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Operation.IOperation.html b/docs/source-class-LeanCloud.Operation.IOperation.html
new file mode 100644
index 0000000..c19386f
--- /dev/null
+++ b/docs/source-class-LeanCloud.Operation.IOperation.html
@@ -0,0 +1,179 @@
+
+
+
+
+
+
+ File LeanCloud/Operation/IOperation.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Operation.IncrementOperation.html b/docs/source-class-LeanCloud.Operation.IncrementOperation.html
new file mode 100644
index 0000000..d2c2402
--- /dev/null
+++ b/docs/source-class-LeanCloud.Operation.IncrementOperation.html
@@ -0,0 +1,249 @@
+
+
+
+
+
+
+ File LeanCloud/Operation/IncrementOperation.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102:
+
<?php
+namespace LeanCloud\Operation;
+
+ use LeanCloud\Operation\SetOperation;
+
+
+
+ class IncrementOperation implements IOperation {
+
+ private $key ;
+
+
+ private $value ;
+
+
+ public function __construct($key , $val ) {
+ if (!is_numeric ($val )) {
+ throw new \InvalidArgumentException("Operand must be number." );
+ }
+ $this ->key = $key ;
+ $this ->value = $val ;
+ }
+
+
+ public function getKey() {
+ return $this ->key ;
+ }
+
+
+ public function getValue() {
+ return $this ->value;
+ }
+
+
+ public function encode() {
+ return array ("__op" => "Increment" ,
+ "amount" => $this ->value);
+ }
+
+
+ public function applyOn($oldval ) {
+ $oldval = is_null ($oldval ) ? 0 : $oldval ;
+ if (is_numeric ($oldval )) {
+ return $this ->value + $oldval ;
+ }
+ throw new \RuntimeException("Operation incompatible with previous value." );
+ }
+
+
+ public function mergeWith($prevOp ) {
+ if (!$prevOp ) {
+ return $this ;
+ } else if ($prevOp instanceof SetOperation) {
+ return new SetOperation($this ->getKey(),
+ $this ->applyOn($prevOp ->getValue()));
+ } else if ($prevOp instanceof IncrementOperation) {
+ return new IncrementOperation($this ->getKey(),
+ $this ->applyOn($prevOp ->getValue()));
+ } else if ($prevOp instanceof DeleteOperation){
+ return new SetOperation($this ->getKey(), $this ->getValue());
+ } else {
+ throw new \RuntimeException("Operation incompatible with previous one." );
+ }
+ }
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Operation.RelationOperation.html b/docs/source-class-LeanCloud.Operation.RelationOperation.html
new file mode 100644
index 0000000..a842cd7
--- /dev/null
+++ b/docs/source-class-LeanCloud.Operation.RelationOperation.html
@@ -0,0 +1,346 @@
+
+
+
+
+
+
+ File LeanCloud/Operation/RelationOperation.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199:
+
<?php
+namespace LeanCloud\Operation;
+
+ use LeanCloud\Relation;
+
+
+ class RelationOperation implements IOperation {
+
+ private $key ;
+
+
+ private $targetClassName ;
+
+
+ private $objects_to_add = array ();
+
+
+ private $objects_to_remove = array ();
+
+
+ public function __construct($key , $adds , $removes ) {
+ if (empty ($adds ) && empty ($removes )) {
+ throw new \InvalidArgumentException("Operands are empty." );
+ }
+ $this ->key = $key ;
+
+ $this ->remove($removes );
+ $this ->add($adds );
+ }
+
+
+ public function getKey() {
+ return $this ->key ;
+ }
+
+
+ public function getTargetClassName() {
+ return $this ->targetClassName;
+ }
+
+
+ public function encode() {
+ $adds = array ("__op" => "AddRelation" ,
+ "objects" => array ());
+ $removes = array ("__op" => "RemoveRelation" ,
+ "objects" => array ());
+ forEach ($this ->objects_to_add as $obj ) {
+ $adds ["objects" ][] = $obj ->getPointer();
+ }
+ forEach ($this ->objects_to_remove as $obj ) {
+ $removes ["objects" ][] = $obj ->getPointer();
+ }
+
+ if (empty ($this ->objects_to_remove)) {
+ return $adds ;
+ }
+ if (empty ($this ->objects_to_add)) {
+ return $removes ;
+ }
+ return array ("__op" => "Batch" ,
+ "ops" => array ($adds , $removes ));
+ }
+
+
+ private function add($objects ) {
+ if (empty ($objects )) { return ; }
+ if (!$this ->targetClassName) {
+ $this ->targetClassName = current ($objects )->getClassName();
+ }
+ forEach ($objects as $obj ) {
+ if (!$obj ->getObjectId()) {
+ throw new \RuntimeException("Cannot add unsaved object" .
+ " to relation." );
+ }
+ if ($obj ->getClassName() !== $this ->targetClassName) {
+ throw new \RuntimeException("LeanObject type incompatible" .
+ " with relation." );
+ }
+ if (isset ($this ->objects_to_remove[$obj ->getObjectID()])) {
+ unset ($this ->objects_to_remove[$obj ->getObjectID()]);
+ }
+ $this ->objects_to_add[$obj ->getObjectId()] = $obj ;
+ }
+ }
+
+
+ private function remove($objects ) {
+ if (empty ($objects )) { return ; }
+ if (!$this ->targetClassName) {
+ $this ->targetClassName = current ($objects )->getClassName();
+ }
+ forEach ($objects as $obj ) {
+ if (!$obj ->getObjectId()) {
+ throw new \RuntimeException("Cannot remove unsaved object" .
+ " from relation." );
+ }
+ if ($obj ->getClassName() !== $this ->targetClassName) {
+ throw new \RuntimeException("LeanObject type incompatible" .
+ " with relation." );
+ }
+ if (isset ($this ->objects_to_add[$obj ->getObjectID()])) {
+ unset ($this ->objects_to_add[$obj ->getObjectID()]);
+ }
+ $this ->objects_to_remove[$obj ->getObjectId()] = $obj ;
+ }
+ }
+
+
+ public function applyOn($relation , $object =null ) {
+ if (!$relation ) {
+ return new Relation($object , $this ->getKey(),
+ $this ->getTargetClassName());
+ }
+ if (!($relation instanceof Relation)) {
+ throw new \RuntimeException("Operation incompatible with " .
+ "previous value." );
+ }
+
+ return $relation ;
+ }
+
+
+ public function mergeWith($prevOp ) {
+ if (!$prevOp ) {
+ return $this ;
+ }
+ if ($prevOp instanceof RelationOperation) {
+ $adds = array_merge ($this ->objects_to_add,
+ $prevOp ->objects_to_add);
+ $removes = array_merge ($this ->objects_to_remove,
+ $prevOp ->objects_to_remove);
+ return new RelationOperation($this ->getKey(), $adds , $removes );
+ } else {
+ throw new \RuntimeException("Operation incompatible with " .
+ "previous one." );
+ }
+ }
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Operation.SetOperation.html b/docs/source-class-LeanCloud.Operation.SetOperation.html
new file mode 100644
index 0000000..36f4976
--- /dev/null
+++ b/docs/source-class-LeanCloud.Operation.SetOperation.html
@@ -0,0 +1,233 @@
+
+
+
+
+
+
+ File LeanCloud/Operation/SetOperation.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Push.html b/docs/source-class-LeanCloud.Push.html
new file mode 100644
index 0000000..7e7570b
--- /dev/null
+++ b/docs/source-class-LeanCloud.Push.html
@@ -0,0 +1,336 @@
+
+
+
+
+
+
+ File LeanCloud/Push.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189:
+
<?php
+
+ namespace LeanCloud;
+
+
+ class Push {
+
+ private $data ;
+
+
+ private $options ;
+
+
+ public function __construct($data =array (), $options =array ()) {
+ $this ->data = $data ;
+ $this ->options = $options ;
+ $this ->options["prod" ] = Client::$isProduction ? "prod" : "dev" ;
+ }
+
+
+ public function setData($key , $val ) {
+ $this ->data[$key ] = $val ;
+ }
+
+
+ public function setOption($key , $val ) {
+ $this ->options[$key ] = $val ;
+ return $this ;
+ }
+
+
+ public function setChannels($channels ) {
+ return $this ->setOption("channels" , $channels );
+ }
+
+
+ public function setWhere(Query $query ) {
+ if ($query ->getClassName() != "_Installation" ) {
+ throw new \RuntimeException("Query must be over " .
+ "_Installation table." );
+ }
+ return $this ->setOption("where" , $query );
+ }
+
+
+ public function setPushTime(\DateTime $time ) {
+ return $this ->setOption("push_time" , $time );
+ }
+
+
+ public function setExpirationInterval($interval ) {
+ return $this ->setOption("expiration_interval" , $interval );
+ }
+
+
+ public function setExpirationTime(\DateTime $time ) {
+ return $this ->setOption("expiration_time" , $time );
+ }
+
+
+ public function setFlowControl($flowControl ) {
+ return $this ->setOption("flow_control" , $flowControl );
+ }
+
+
+ public function encode() {
+ $out = $this ->options;
+ $out ["data" ] = $this ->data;
+ $expire = isset ($this ->options["expiration_time" ]) ? $this ->options["expiration_time" ] : null ;
+ if (($expire instanceof \DateTime) ||
+ ($expire instanceof \DateTimeImmutable)) {
+ $out ["expiration_time" ] = Client::formatDate($expire );
+ }
+ $pushTime = isset ($this ->options["push_time" ]) ? $this ->options["push_time" ] : null ;
+ if (($pushTime instanceof \DateTime) ||
+ ($pushTime instanceof \DateTimeImmutable)){
+ $out ["push_time" ] = Client::formatDate($pushTime );
+ }
+ if (isset ($this ->options["where" ])) {
+ $query = $this ->options["where" ]->encode();
+ $out ["where" ] = json_decode ($query ["where" ], true );
+ }
+ return $out ;
+ }
+
+
+ public function send() {
+ $out = $this ->encode();
+ $resp = Client::post("/push" , $out );
+ return $resp ;
+ }
+ }
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Query.html b/docs/source-class-LeanCloud.Query.html
new file mode 100644
index 0000000..bc4292a
--- /dev/null
+++ b/docs/source-class-LeanCloud.Query.html
@@ -0,0 +1,940 @@
+
+
+
+
+
+
+ File LeanCloud/Query.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 527: 528: 529: 530: 531: 532: 533: 534: 535: 536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581: 582: 583: 584: 585: 586: 587: 588: 589: 590: 591: 592: 593: 594: 595: 596: 597: 598: 599: 600: 601: 602: 603: 604: 605: 606: 607: 608: 609: 610: 611: 612: 613: 614: 615: 616: 617: 618: 619: 620: 621: 622: 623: 624: 625: 626: 627: 628: 629: 630: 631: 632: 633: 634: 635: 636: 637: 638: 639: 640: 641: 642: 643: 644: 645: 646: 647: 648: 649: 650: 651: 652: 653: 654: 655: 656: 657: 658: 659: 660: 661: 662: 663: 664: 665: 666: 667: 668: 669: 670: 671: 672: 673: 674: 675: 676: 677: 678: 679: 680: 681: 682: 683: 684: 685: 686: 687: 688: 689: 690: 691: 692: 693: 694: 695: 696: 697: 698: 699: 700: 701: 702: 703: 704: 705: 706: 707: 708: 709: 710: 711: 712: 713: 714: 715: 716: 717: 718: 719: 720: 721: 722: 723: 724: 725: 726: 727: 728: 729: 730: 731: 732: 733: 734: 735: 736: 737: 738: 739: 740: 741: 742: 743: 744: 745: 746: 747: 748: 749: 750: 751: 752: 753: 754: 755: 756: 757: 758: 759: 760: 761: 762: 763: 764: 765: 766: 767: 768: 769: 770: 771: 772: 773: 774: 775: 776: 777: 778: 779: 780: 781: 782: 783: 784: 785: 786: 787: 788: 789: 790: 791: 792: 793:
+
<?php
+namespace LeanCloud;
+
+ use LeanCloud\Client;
+use LeanCloud\LeanObject;
+
+
+ class Query {
+
+ private $className ;
+
+
+ private $select ;
+
+
+ private $include ;
+
+
+ private $limit ;
+
+
+ private $skip ;
+
+
+ private $order ;
+
+
+ private $extraOption ;
+
+
+ public function __construct($queryClass ) {
+ if (is_string ($queryClass )) {
+ $this ->className = $queryClass ;
+ } else if (is_subclass_of ($queryClass , "LeanObject" )) {
+ $this ->className = $queryClass ::$className ;
+ } else {
+ throw new \InvalidArgumentException("Query class invalid." );
+ }
+ $this ->where = array ();
+ $this ->select = array ();
+ $this ->include = array ();
+ $this ->order = array ();
+ $this ->limit = -1 ;
+ $this ->skip = 0 ;
+ $this ->extraOption = array ();
+ }
+
+
+ public function getClassName() {
+ return $this ->className;
+ }
+
+
+ private function _addCondition($key , $op , $val ) {
+ $this ->where[$key ][$op ] = Client::encode($val );
+ }
+
+
+ public function equalTo($key , $val ) {
+ $this ->where[$key ] = Client::encode($val );
+ return $this ;
+ }
+
+
+ public function notEqualTo($key , $val ) {
+ $this ->_addCondition($key , '$ne' , $val );
+ return $this ;
+ }
+
+
+ public function lessThan($key , $val ) {
+ $this ->_addCondition($key , '$lt' , $val );
+ return $this ;
+ }
+
+
+ public function lessThanOrEqualTo($key , $val ) {
+ $this ->_addCondition($key , '$lte' , $val );
+ return $this ;
+ }
+
+
+ public function greaterThan($key , $val ) {
+ $this ->_addCondition($key , '$gt' , $val );
+ return $this ;
+ }
+
+
+ public function greaterThanOrEqualTo($key , $val ) {
+ $this ->_addCondition($key , '$gte' , $val );
+ return $this ;
+ }
+
+
+ public function containedIn($key , $vals ) {
+ $this ->_addCondition($key , '$in' , $vals );
+ return $this ;
+ }
+
+
+ public function notContainedIn($key , $vals ) {
+ $this ->_addCondition($key , '$nin' , $vals );
+ return $this ;
+ }
+
+
+ public function containsAll($key , $vals ) {
+ $this ->_addCondition($key , '$all' , $vals );
+ return $this ;
+ }
+
+
+ public function sizeEqualTo($key , $val ) {
+ $this ->_addCondition($key , '$size' , $val );
+ return $this ;
+ }
+
+
+ public function exists($key ) {
+ $this ->_addCondition($key , '$exists' , true );
+ return $this ;
+ }
+
+
+ public function notExists($key ) {
+ $this ->_addCondition($key , '$exists' , false );
+ return $this ;
+ }
+
+
+
+
+ public function contains($key , $val ) {
+ $this ->_addCondition($key , '$regex' , $val );
+ return $this ;
+ }
+
+
+ public function startsWith($key , $val ) {
+ $this ->_addCondition($key , '$regex' , '^' . $val );
+ return $this ;
+ }
+
+
+ public function endsWith($key , $val ) {
+ $this ->_addCondition($key , '$regex' , $val . '$' );
+ return $this ;
+ }
+
+
+ public function matches($key , $regex , $modifiers ="" ) {
+ $this ->_addCondition($key , '$regex' , $regex );
+ if (!empty ($modifiers )) {
+ $this ->_addCondition($key , '$options' , $modifiers );
+ }
+ return $this ;
+ }
+
+
+
+
+
+
+ public function matchesInQuery($key , $query ) {
+ $this ->_addCondition($key , '$inQuery' , array (
+ "where" => $query ->where,
+ "className" => $query ->getClassName()
+ ));
+ return $this ;
+ }
+
+
+ public function notMatchInQuery($key , $query ) {
+ $this ->_addCondition($key , '$notInQuery' , array (
+ "where" => $query ->where,
+ "className" => $query ->getClassName()
+ ));
+ return $this ;
+ }
+
+
+ public function matchesFieldInQuery($key , $queryKey , $query ) {
+ $this ->_addCondition($key , '$select' , array (
+ "key" => $queryKey ,
+ "query" => array (
+ "where" => $query ->where,
+ "className" => $query ->getClassName()
+ )
+ ));
+ return $this ;
+ }
+
+
+ public function notMatchFieldInQuery($key , $queryKey , $query ) {
+ $this ->_addCondition($key , '$dontSelect' , array (
+ "key" => $queryKey ,
+ "query" => array (
+ "where" => $query ->where,
+ "className" => $query ->getClassName()
+ )
+ ));
+ return $this ;
+ }
+
+
+ public function relatedTo($key , $obj ) {
+ $this ->where['$relatedTo' ] = array (
+ "key" => $key ,
+ "object" => $obj ->getPointer()
+ );
+ return $this ;
+ }
+
+
+
+
+
+
+ public function near($key , GeoPoint $point ) {
+ $this ->_addCondition($key , '$nearSphere' , $point );
+ return $this ;
+ }
+
+
+ public function withinRadians($key , GeoPoint $point , $distance ) {
+ $this ->near($key , $point );
+ $this ->_addCondition($key , '$maxDistanceInRadians' , $distance );
+ return $this ;
+ }
+
+
+ public function withinKilometers($key , GeoPoint $point , $distance ) {
+ $this ->near($key , $point );
+ $this ->_addCondition($key , '$maxDistanceInKilometers' , $distance );
+ return $this ;
+ }
+
+
+ public function withinMiles($key , GeoPoint $point , $distance ) {
+ $this ->near($key , $point );
+ $this ->_addCondition($key , '$maxDistanceInMiles' , $distance );
+ return $this ;
+ }
+
+
+ public function withinBox($key , GeoPoint $southwest , GeoPoint $northeast ) {
+ $this ->_addCondition($key , '$within' , array (
+ '$box' => array ($southwest , $northeast )
+ ));
+ return $this ;
+ }
+
+
+
+
+ public function select($keys ) {
+ if (!is_array ($keys )) {
+ $keys = func_get_args ();
+ }
+ $this ->select = array_merge ($this ->select, $keys );
+ return $this ;
+ }
+
+
+ public function _include($keys ) {
+ if (!is_array ($keys )) {
+ $keys = func_get_args ();
+ }
+ $this ->include = array_merge ($this ->include , $keys );
+ return $this ;
+ }
+
+
+ public function limit($n ) {
+ $this ->limit = $n ;
+ return $this ;
+ }
+
+
+ public function skip($n ) {
+ $this ->skip = $n ;
+ return $this ;
+ }
+
+
+ public function ascend($key ) {
+ $this ->order = array ($key );
+ return $this ;
+ }
+
+
+ public function descend($key ) {
+ $this ->order = array ("- $key " );
+ return $this ;
+ }
+
+
+ public function addAscend($key ) {
+ $this ->order[] = $key ;
+ return $this ;
+ }
+
+
+ public function addDescend($key ) {
+ $this ->order[] = "- $key " ;
+ return $this ;
+ }
+
+
+ private static function composeQuery($op , $queries ) {
+ $className = $queries [0 ]->getClassName();
+ $conds = array ();
+ forEach ($queries as $q ) {
+ if ($q ->getClassName() != $className ) {
+ throw new \RuntimeException("Query class incompatible." );
+ }
+ $conds [] = $q ->where;
+ }
+ $query = new Query($className );
+ $query ->where[$op ] = $conds ;
+ return $query ;
+ }
+
+
+ public static function orQuery($queries ) {
+ if (!is_array ($queries )) {
+ $queries = func_get_args ();
+ }
+ return self::composeQuery('$or' , $queries );
+ }
+
+
+ public static function andQuery($queries ) {
+ if (!is_array ($queries )) {
+ $queries = func_get_args ();
+ }
+ return self::composeQuery('$and' , $queries );
+ }
+
+
+ public function addOption($key , $val ) {
+ $this ->extraOption[$key ] = $val ;
+ return $this ;
+ }
+
+
+ public function encode() {
+ $out = array ();
+ if (!empty ($this ->extraOption)) {
+
+ $out = $this ->extraOption;
+ }
+ if (!empty ($this ->where)) {
+ $out ["where" ] = json_encode ($this ->where);
+ }
+ if (!empty ($this ->select)) {
+ $out ["keys" ] = implode ("," , $this ->select);
+ }
+ if (!empty ($this ->include )) {
+ $out ["include" ] = implode ("," , $this ->include );
+ }
+ if ($this ->skip > 0 ) {
+ $out ["skip" ] = $this ->skip;
+ }
+ if ($this ->limit > -1 ) {
+ $out ["limit" ] = $this ->limit;
+ }
+ if (!empty ($this ->order)) {
+ $out ["order" ] = implode ("," , $this ->order);
+ }
+ return $out ;
+ }
+
+
+ public function get($objectId ) {
+ $this ->equalTo('objectId' , $objectId );
+ return $this ->first();
+ }
+
+
+ public function first() {
+ $objects = $this ->find($this ->skip, 1 );
+ return empty ($objects ) ? null : $objects [0 ];
+ }
+
+
+ public function find($skip =-1 , $limit =-1 ) {
+ $params = $this ->encode();
+ if ($skip >= 0 ) {
+ $params ["skip" ] = $skip ;
+ }
+ if ($limit >= 0 ) {
+ $params ["limit" ] = $limit ;
+ }
+
+ $resp = Client::get("/classes/ {$this->getClassName()} " , $params );
+ $objects = array ();
+ forEach ($resp ["results" ] as $props ) {
+ $obj = LeanObject::create($this ->getClassName());
+ $obj ->mergeAfterFetch($props );
+ $objects [] = $obj ;
+ }
+ return $objects ;
+ }
+
+
+ public function count () {
+ $params = $this ->encode();
+ $params ["limit" ] = 0 ;
+ $params ["count" ] = 1 ;
+ $resp = Client::get("/classes/ {$this->getClassName()} " , $params );
+ return $resp ["count" ];
+ }
+
+
+ public static function doCloudQuery($cql , $pvalues =array ()) {
+ $data = array ("cql" => $cql );
+ if (!empty ($pvalues )) {
+ $data ["pvalues" ] = json_encode (Client::encode($pvalues ));
+ }
+ $resp = Client::get('/cloudQuery' , $data );
+ $objects = array ();
+ forEach ($resp ["results" ] as $val ) {
+ $obj = LeanObject::create($resp ["className" ], $val ["objectId" ]);
+ $obj ->mergeAfterFetch($val );
+ $objects [] = $obj ;
+ }
+ $resp ["results" ] = $objects ;
+
+ return $resp ;
+ }
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Region.html b/docs/source-class-LeanCloud.Region.html
new file mode 100644
index 0000000..2a6c39d
--- /dev/null
+++ b/docs/source-class-LeanCloud.Region.html
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+ File LeanCloud/Region.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
+
<?php
+
+ namespace LeanCloud;
+
+ abstract class Region {
+ const CN_N1 = 0 ;
+ const US = 1 ;
+ const CN_E1 = 2 ;
+
+
+ const CN = self::CN_N1;
+
+
+ public static function fromName($name ) {
+ return constant (self::class . "::" . strtoupper ($name ));
+ }
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Relation.html b/docs/source-class-LeanCloud.Relation.html
new file mode 100644
index 0000000..7b1fbf0
--- /dev/null
+++ b/docs/source-class-LeanCloud.Relation.html
@@ -0,0 +1,294 @@
+
+
+
+
+
+
+ File LeanCloud/Relation.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147:
+
<?php
+
+ namespace LeanCloud;
+
+ use LeanCloud\Operation\RelationOperation;
+
+
+ class Relation {
+
+ private $parent ;
+
+
+ private $key ;
+
+
+ private $targetClassName ;
+
+
+ public function __construct($parent , $key , $className =null ) {
+ $this ->parent = $parent ;
+ $this ->key = $key ;
+ $this ->targetClassName = $className ;
+ }
+
+
+ public function encode() {
+ return array ("__type" => "Relation" ,
+ "className" => $this ->targetClassName);
+ }
+
+
+ public function setParentAndKey($parent , $key ) {
+ if ($this ->parent && $this ->parent != $parent ) {
+ throw new \RuntimeException("Relation does not belong to the object" );
+ }
+ if ($this ->key && $this ->key != $key ) {
+ throw new \RuntimeException("Relation does not belong to the field" );
+ }
+ $this ->parent = $parent ;
+ $this ->key = $key ;
+ }
+
+
+ public function getTargetClassName() {
+ return $this ->targetClassName;
+ }
+
+
+ public function add($objects ) {
+ if (!is_array ($objects )) { $objects = array ($objects ); }
+ $op = new RelationOperation($this ->key , $objects , null );
+ $this ->parent->set($this ->key , $op );
+ if (!$this ->targetClassName) {
+ $this ->targetClassName = $op ->getTargetClassName();
+ }
+ }
+
+
+ public function remove($objects ) {
+ if (!is_array ($objects )) { $objects = array ($objects ); }
+ $op = new RelationOperation($this ->key , null , $objects );
+ $this ->parent->set($this ->key , $op );
+ if (!$this ->targetClassName) {
+ $this ->targetClassName = $op ->getTargetClassName();
+ }
+ }
+
+
+ public function getQuery() {
+ if ($this ->targetClassName) {
+ $query = new Query($this ->targetClassName);
+ } else {
+ $query = new Query($this ->parent->getClassName());
+ $query ->addOption("redirectClassNameForKey" , $this ->key );
+ }
+ $query ->relatedTo($this ->key , $this ->parent);
+ return $query ;
+ }
+
+
+ public function getReverseQuery(LeanObject $child ) {
+ $query = new Query($this ->parent->getClassName());
+ $query ->equalTo($this ->key , $child ->getPointer());
+ return $query ;
+ }
+
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Role.html b/docs/source-class-LeanCloud.Role.html
new file mode 100644
index 0000000..948823a
--- /dev/null
+++ b/docs/source-class-LeanCloud.Role.html
@@ -0,0 +1,216 @@
+
+
+
+
+
+
+ File LeanCloud/Role.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.RouteCache.html b/docs/source-class-LeanCloud.RouteCache.html
new file mode 100644
index 0000000..dc46525
--- /dev/null
+++ b/docs/source-class-LeanCloud.RouteCache.html
@@ -0,0 +1,372 @@
+
+
+
+
+
+
+ File LeanCloud/AppRouter.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225:
+
<?php
+
+ namespace LeanCloud;
+
+ use LeanCloud\Region;
+
+ class AppRouter {
+ const TTL_KEY = "ttl" ;
+ const API_SERVER_KEY = "api_server" ;
+ const PUSH_SERVER_KEY = "push_server" ;
+ const STATS_SERVER_KEY = "stats_server" ;
+ const ENGINE_SERVER_KEY = "engine_server" ;
+ const RTM_ROUTER_SERVER_KEY = "rtm_router_server" ;
+ private static $INSTANCES ;
+ private $appId ;
+ private $region ;
+ private $routeCache ;
+
+ private static $DEFAULT_REGION_ROUTE = array (
+ Region::US => "us-api.leancloud.cn" ,
+ Region::CN_E1 => "e1-api.leancloud.cn" ,
+ Region::CN_N1 => "api.leancloud.cn"
+ );
+
+ private static $DEFAULT_REGION_RTM_ROUTE = array (
+ Region::US => "router-a0-push.leancloud.cn" ,
+ Region::CN_E1 => "router-q0-push.leancloud.cn" ,
+ Region::CN_N1 => "router-g0-push.leancloud.cn"
+ );
+
+ private function __construct($appId ) {
+ $this ->appId = $appId ;
+ $region = getenv ("LEANCLOUD_REGION" );
+ if (!$region ) {
+ $region = Region::CN;
+ }
+ $this ->setRegion($region );
+ $this ->routeCache = RouteCache::create($appId );
+ }
+
+
+ public static function getInstance($appId ) {
+ if (isset (self::$INSTANCES [$appId ])) {
+ return self::$INSTANCES [$appId ];
+ } else {
+ $router = new AppRouter($appId );
+ self::$INSTANCES [$appId ] = $router ;
+ return $router ;
+ }
+ }
+
+
+ public function getRegionDefaultRoute($server_key ) {
+ $this ->validate_server_key($server_key );
+ return $this ->getDefaultRoutes()[$server_key ];
+ }
+
+
+ public function setRegion($region ) {
+ if (is_numeric ($region )) {
+ $this ->region = $region ;
+ } else {
+ $this ->region = Region::fromName($region );
+ }
+ }
+
+
+ public function getRoute($server_key ) {
+ $this ->validate_server_key($server_key );
+ $routes = $this ->routeCache->read();
+ if (isset ($routes [$server_key ])) {
+ return $routes [$server_key ];
+ }
+ $routes = $this ->getRoutes();
+ if (!$routes ) {
+ $routes = $this ->getDefaultRoutes();
+ }
+ $this ->routeCache->write($routes );
+ return isset ($routes [$server_key ]) ? $routes [$server_key ] : null ;
+ }
+
+ private function getRouterUrl() {
+ $url = getenv ("LEANCLOUD_APP_ROUTER" );
+ if (!$url ) {
+ $url = "https://app-router.leancloud.cn/2/route?appId=" ;
+ }
+ return " {$url}{$this->appId} " ;
+ }
+
+ private function validate_server_key($server_key ) {
+ $routes = $this ->getDefaultRoutes();
+ if (!isset ($routes [$server_key ])) {
+ throw new IllegalArgumentException("Invalid server key." );
+ }
+ }
+
+
+ private function detectRegion() {
+ if (!$this ->appId) {
+ return Region::CN_N1;
+ }
+ $parts = explode ("-" , $this ->appId);
+ if (count ($parts ) <= 1 ) {
+ return Region::CN_N1;
+ } else if ($parts [1 ] === "MdYXbMMI" ) {
+ return Region::US;
+ } else if ($parts [1 ] === "9Nh9j0Va" ) {
+ return Region::CN_E1;
+ } else {
+ $this ->region = Region::CN_N1;
+ }
+ }
+
+
+ private function getRoutes() {
+ $routes = @json_decode (file_get_contents ($this ->getRouterUrl()), true );
+ if (isset ($routes [self::TTL_KEY])) {
+ return $routes ;
+ }
+ return null ;
+ }
+
+
+ private function getDefaultRoutes() {
+ $host = self::$DEFAULT_REGION_ROUTE [$this ->region];
+
+ return array (
+ self::API_SERVER_KEY => $host ,
+ self::PUSH_SERVER_KEY => $host ,
+ self::STATS_SERVER_KEY => $host ,
+ self::ENGINE_SERVER_KEY => $host ,
+ self::RTM_ROUTER_SERVER_KEY => self::$DEFAULT_REGION_RTM_ROUTE [$this ->region],
+ self::TTL_KEY => 3600
+ );
+ }
+
+
+ }
+
+
+
+ class RouteCache {
+ private $filename ;
+ private $_cache ;
+
+ private function __construct($id ) {
+ $this ->filename = sys_get_temp_dir () . "/route_ {$id} .json" ;
+ }
+
+ public static function create($id ) {
+ return new RouteCache($id );
+ }
+
+
+ public function write($array ) {
+ $body = json_encode ($array );
+ if (file_put_contents ($this ->filename, $body , LOCK_EX) === false ) {
+ error_log ("WARNING: failed to write route cache ( {$this->filename} ), performance may be degraded." );
+ } else {
+ $this ->_cache = $array ;
+ }
+ }
+
+
+ public function read() {
+ if ($this ->_cache) {
+ return $this ->_cache;
+ }
+ $data = $this ->readFile ();
+ if (!empty ($data )) {
+ $this ->_cache = $data ;
+ return $data ;
+ }
+ return null ;
+ }
+
+ private function readFile () {
+ if (file_exists ($this ->filename)) {
+ $fp = fopen ($this ->filename, "rb" );
+ $body = null ;
+ if (flock ($fp , LOCK_SH)) {
+ $body = fread ($fp , filesize ($this ->filename));
+ flock ($fp , LOCK_UN);
+ }
+ fclose ($fp );
+ if (!empty ($body )) {
+ $data = @json_decode ($body , true );
+ if (!empty ($data )) {
+ return $data ;
+ }
+ }
+ }
+ return null ;
+ }
+ }
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.SMS.html b/docs/source-class-LeanCloud.SMS.html
new file mode 100644
index 0000000..10640a0
--- /dev/null
+++ b/docs/source-class-LeanCloud.SMS.html
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+ File LeanCloud/SMS.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.SaveOption.html b/docs/source-class-LeanCloud.SaveOption.html
new file mode 100644
index 0000000..795a52c
--- /dev/null
+++ b/docs/source-class-LeanCloud.SaveOption.html
@@ -0,0 +1,192 @@
+
+
+
+
+
+
+ File LeanCloud/SaveOption.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45:
+
<?php
+namespace LeanCloud;
+
+
+
+ class SaveOption {
+
+
+ public $fetchWhenSave ;
+
+
+ public $where ;
+
+
+ public function encode() {
+ $params = array ();
+ if (!is_null ($this ->fetchWhenSave)) {
+ $params ["fetchWhenSave" ] = $this ->fetchWhenSave ? true : false ;
+ }
+ if (!is_null ($this ->where)) {
+ if ($this ->where instanceof Query) {
+ $out = $this ->where->encode();
+ $params ["where" ] = $out ["where" ];
+ } else {
+ throw new \RuntimeException("where of SaveOption must be Query object." );
+ }
+ }
+ return $params ;
+ }
+ }
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Storage.CookieStorage.html b/docs/source-class-LeanCloud.Storage.CookieStorage.html
new file mode 100644
index 0000000..43c18c5
--- /dev/null
+++ b/docs/source-class-LeanCloud.Storage.CookieStorage.html
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+ File LeanCloud/Storage/CookieStorage.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106:
+
<?php
+
+ namespace LeanCloud\Storage;
+
+
+ class CookieStorage implements IStorage {
+
+ private $domain ;
+
+
+ private $path ;
+
+
+ private $expireIn ;
+
+
+ public function __construct($seconds =0 , $path ="/" , $domain =null ) {
+ if ($seconds <= 0 ) {
+
+ $seconds = 60 * 60 * 24 * 7 ;
+ }
+ $this ->expireIn = time () + $seconds ;
+ $this ->path = $path ;
+ $this ->domain = $domain ;
+ }
+
+
+ public function set($key , $val , $seconds =null ) {
+ $expire = $seconds ? (time () + seconds) : $this ->expireIn;
+ setcookie ($key , $val , $expire , $this ->path, $this ->domain);
+ }
+
+
+ public function get($key ) {
+ if (isset ($_COOKIE [$key ])) {
+ return $_COOKIE [$key ];
+ }
+ return null ;
+ }
+
+
+ public function remove($key ) {
+ setcookie ($key , false , 1 );
+ }
+
+
+ public function clear() {
+ throw new \RuntimeException("Not implemented error." );
+ }
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Storage.IStorage.html b/docs/source-class-LeanCloud.Storage.IStorage.html
new file mode 100644
index 0000000..163d493
--- /dev/null
+++ b/docs/source-class-LeanCloud.Storage.IStorage.html
@@ -0,0 +1,194 @@
+
+
+
+
+
+
+ File LeanCloud/Storage/IStorage.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Storage.SessionStorage.html b/docs/source-class-LeanCloud.Storage.SessionStorage.html
new file mode 100644
index 0000000..e449039
--- /dev/null
+++ b/docs/source-class-LeanCloud.Storage.SessionStorage.html
@@ -0,0 +1,225 @@
+
+
+
+
+
+
+ File LeanCloud/Storage/SessionStorage.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Uploader.QCloudUploader.html b/docs/source-class-LeanCloud.Uploader.QCloudUploader.html
new file mode 100644
index 0000000..1bfbc8e
--- /dev/null
+++ b/docs/source-class-LeanCloud.Uploader.QCloudUploader.html
@@ -0,0 +1,217 @@
+
+
+
+
+
+
+ File LeanCloud/Uploader/QCloudUploader.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70:
+
<?php
+
+ namespace LeanCloud\Uploader;
+use LeanCloud\Client;
+
+
+
+ class QCloudUploader extends SimpleUploader {
+
+ protected static function getFileFieldName() {
+ return "filecontent" ;
+ }
+
+ public function upload($content , $mimeType , $key ) {
+ $boundary = md5 (microtime (true ));
+
+ $body = $this ->multipartEncode(array (
+ "name" => $key ,
+ "mimeType" => $mimeType ,
+ "content" => $content ,
+ ), array (
+ "op" => "upload" ,
+ "sha" => hash ("sha1" , $content )
+ ), $boundary );
+
+ $headers [] = "User-Agent: " . Client::getVersionString();
+ $headers [] = "Content-Type: multipart/form-data;" .
+ " boundary= {$boundary} " ;
+
+ $headers [] = "Authorization: {$this->getAuthToken()} " ;
+ $url = $this ->getUploadUrl();
+ $ch = curl_init ($url );
+ curl_setopt ($ch , CURLOPT_SSL_VERIFYPEER, true );
+ curl_setopt ($ch , CURLOPT_HTTPHEADER, $headers );
+ curl_setopt ($ch , CURLOPT_RETURNTRANSFER, true );
+ curl_setopt ($ch , CURLOPT_POST, 1 );
+ curl_setopt ($ch , CURLOPT_POSTFIELDS, $body );
+ $resp = curl_exec ($ch );
+ $respCode = curl_getinfo ($ch , CURLINFO_HTTP_CODE);
+ $respType = curl_getinfo ($ch , CURLINFO_CONTENT_TYPE);
+ $error = curl_error ($ch );
+ $errno = curl_errno ($ch );
+ curl_close ($ch );
+
+
+ if ($errno > 0 ) {
+ throw new \RuntimeException("CURL ( $url ) error: " .
+ " {$errno} {$error} " ,
+ $errno );
+ }
+
+ $data = json_decode ($resp , true );
+ if ($data ["code" ] != 0 ) {
+ throw new \RuntimeException("Upload to Qcloud ( {$url} ) failed: " .
+ " {$data['code']} {$data['message']} " ,
+ $data ["code" ]);
+ }
+ return $data ;
+ }
+
+ }
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Uploader.QiniuUploader.html b/docs/source-class-LeanCloud.Uploader.QiniuUploader.html
new file mode 100644
index 0000000..40dc7ce
--- /dev/null
+++ b/docs/source-class-LeanCloud.Uploader.QiniuUploader.html
@@ -0,0 +1,231 @@
+
+
+
+
+
+
+ File LeanCloud/Uploader/QiniuUploader.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84:
+
<?php
+
+ namespace LeanCloud\Uploader;
+
+ use LeanCloud\Client;
+
+
+ class QiniuUploader extends SimpleUploader {
+
+ public function getUploadUrl() {
+ return "https://up.qbox.me/" ;
+ }
+
+ public function crc32Data($data ) {
+ $hex = hash ("crc32b" , $data );
+ $ints = unpack ("N" , pack ("H*" , $hex ));
+ return sprintf ("%u" , $ints [1 ]);
+ }
+
+
+ public function upload($content , $mimeType , $key ) {
+ $boundary = md5 (microtime (true ));
+
+ $body = $this ->multipartEncode(array (
+ "name" => $key ,
+ "mimeType" => $mimeType ,
+ "content" => $content ,
+ ), array (
+ "token" => $this ->getAuthToken(),
+ "key" => $key ,
+ "crc32" => $this ->crc32Data($content )
+ ), $boundary );
+
+ $headers [] = "User-Agent: " . Client::getVersionString();
+ $headers [] = "Content-Type: multipart/form-data;" .
+ " boundary= {$boundary} " ;
+ $headers [] = "Content-Length: " . strlen ($body );
+
+ $url = $this ->getUploadUrl();
+ $ch = curl_init ($url );
+ curl_setopt ($ch , CURLOPT_SSL_VERIFYPEER, true );
+ curl_setopt ($ch , CURLOPT_HTTPHEADER, $headers );
+ curl_setopt ($ch , CURLOPT_RETURNTRANSFER, true );
+ curl_setopt ($ch , CURLOPT_POST, 1 );
+ curl_setopt ($ch , CURLOPT_POSTFIELDS, $body );
+ $resp = curl_exec ($ch );
+ $respCode = curl_getinfo ($ch , CURLINFO_HTTP_CODE);
+ $respType = curl_getinfo ($ch , CURLINFO_CONTENT_TYPE);
+ $error = curl_error ($ch );
+ $errno = curl_errno ($ch );
+ curl_close ($ch );
+
+
+ if ($errno > 0 ) {
+ throw new \RuntimeException("CURL ( $url ) error: " .
+ " {$errno} {$error} " ,
+ $errno );
+ }
+
+ $data = json_decode ($resp , true );
+ if (isset ($data ["error" ])) {
+ $code = isset ($data ["code" ]) ? $data ["code" ] : 1 ;
+ throw new \RuntimeException("Upload to Qiniu ( {$url} ) failed: " .
+ " {$code} {$data['error']} " , $code );
+ }
+ return $data ;
+ }
+
+ }
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Uploader.S3Uploader.html b/docs/source-class-LeanCloud.Uploader.S3Uploader.html
new file mode 100644
index 0000000..4f52ea3
--- /dev/null
+++ b/docs/source-class-LeanCloud.Uploader.S3Uploader.html
@@ -0,0 +1,195 @@
+
+
+
+
+
+
+ File LeanCloud/Uploader/S3Uploader.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48:
+
<?php
+
+ namespace LeanCloud\Uploader;
+use LeanCloud\Client;
+
+
+
+ class S3Uploader extends SimpleUploader {
+
+ public function upload($content , $mimeType , $name =null ) {
+ if (!$this ->getUploadUrl()) {
+ throw new \RuntimeException("Please initialize with pre-signed url." );
+ }
+ $headers [] = "User-Agent: " . Client::getVersionString();
+ $headers [] = "Content-Type: $mimeType " ;
+ $url = $this ->getUploadUrl();
+ $ch = curl_init ($url );
+ curl_setopt ($ch , CURLOPT_SSL_VERIFYPEER, true );
+ curl_setopt ($ch , CURLOPT_HTTPHEADER, $headers );
+ curl_setopt ($ch , CURLOPT_RETURNTRANSFER, true );
+ curl_setopt ($ch , CURLOPT_CUSTOMREQUEST, "PUT" );
+ curl_setopt ($ch , CURLOPT_POSTFIELDS, $content );
+ $resp = curl_exec ($ch );
+ $respCode = curl_getinfo ($ch , CURLINFO_HTTP_CODE);
+ $respType = curl_getinfo ($ch , CURLINFO_CONTENT_TYPE);
+ $error = curl_error ($ch );
+ $errno = curl_errno ($ch );
+ curl_close ($ch );
+
+ if ($errno > 0 ) {
+ throw new \RuntimeException("CURL ( {$url} ) error: " .
+ " {$errno} {$error} " ,
+ $errno );
+ }
+
+ if ($respCode >= "300" ) {
+ $S3Error = simplexml_load_string ($resp );
+ throw new \RuntimeException("Upload to S3 ( {$url} ) failed: " .
+ " {$S3Error->Code} {$S3Error->Message} " );
+ }
+ return true ;
+ }
+ }
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.Uploader.SimpleUploader.html b/docs/source-class-LeanCloud.Uploader.SimpleUploader.html
new file mode 100644
index 0000000..8d8f40f
--- /dev/null
+++ b/docs/source-class-LeanCloud.Uploader.SimpleUploader.html
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+ File LeanCloud/Uploader/SimpleUploader.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95:
+
<?php
+
+ namespace LeanCloud\Uploader;
+
+ abstract class SimpleUploader {
+ protected $uploadUrl ;
+ protected $authToken ;
+
+
+ public static function createUploader($provider ) {
+ if ($provider === "qiniu" ) {
+ return new QiniuUploader();
+ } else if ($provider === "s3" ) {
+ return new S3Uploader();
+ } else if ($provider === "qcloud" ) {
+ return new QCloudUploader();
+ }
+ throw new \RuntimeException("File provider not supported: {$provider} " );
+ }
+
+
+ protected static function getFileFieldName() {
+ return "file" ;
+ }
+
+
+ public function multipartEncode($file , $params , $boundary ) {
+ $body = "\r\n" ;
+
+ forEach ($params as $key => $val ) {
+ $body .= "-- {$boundary} \r\n" ;
+ $body .= "Content-Disposition: form-data; name=\" {$key} \"\r\n\r\n" ;
+ $body .= " {$val} \r\n" ;
+ }
+
+ if (!empty ($file )) {
+ $mimeType = "application/octet-stream" ;
+ if (isset ($file ["mimeType" ])) {
+ $mimeType = $file ["mimeType" ];
+ }
+ $fieldname = static ::getFileFieldName();
+
+ $filename = filter_var ($file ["name" ],
+ FILTER_SANITIZE_MAGIC_QUOTES);
+
+ $body .= "-- {$boundary} \r\n" ;
+ $body .= "Content-Disposition: form-data; name=\" {$fieldname} \"; filename=\" {$filename} \"\r\n" ;
+ $body .= "Content-Type: {$mimeType} \r\n\r\n" ;
+ $body .= " {$file['content']} \r\n" ;
+ }
+
+
+ $body .= "-- {$boundary} --\r\n" ;
+
+ return $body ;
+ }
+
+
+ public function initialize($uploadUrl , $authToken ) {
+ $this ->uploadUrl = $uploadUrl ;
+ $this ->authToken = $authToken ;
+ }
+
+ public function getUploadUrl() {
+ return $this ->uploadUrl;
+ }
+
+ public function getAuthToken() {
+ return $this ->authToken;
+ }
+
+ abstract public function upload($content , $mimeType , $key );
+ }
+
+
+
+
+
+
+
+
+
diff --git a/docs/source-class-LeanCloud.User.html b/docs/source-class-LeanCloud.User.html
new file mode 100644
index 0000000..8b013d7
--- /dev/null
+++ b/docs/source-class-LeanCloud.User.html
@@ -0,0 +1,742 @@
+
+
+
+
+
+
+ File LeanCloud/User.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 527: 528: 529: 530: 531: 532: 533: 534: 535: 536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581: 582: 583: 584: 585: 586: 587: 588: 589: 590: 591: 592: 593: 594: 595:
+
<?php
+namespace LeanCloud;
+
+ use LeanCloud\Client;
+use LeanCloud\LeanObject;
+use LeanCloud\CloudException;
+
+
+
+ class User extends LeanObject {
+
+
+ protected static $className = "_User" ;
+
+
+ public static $currentUser = null ;
+
+
+ public function setUsername($username ) {
+ $this ->set("username" , $username );
+ return $this ;
+ }
+
+
+ public function setEmail($email ) {
+ $this ->set("email" , $email );
+ return $this ;
+ }
+
+
+ public function setPassword($password ) {
+ $this ->set("password" , $password );
+ return $this ;
+ }
+
+
+ public function setMobilePhoneNumber($number ) {
+ $this ->set("mobilePhoneNumber" , $number );
+ return $this ;
+ }
+
+
+ public function signUp() {
+ if ($this ->getObjectId()) {
+ throw new CloudException("User has already signed up." );
+ }
+ parent::save();
+ static ::saveCurrentUser($this );
+ }
+
+
+ public function save($option =null ) {
+ if ($this ->getObjectId()) {
+ parent::save($option );
+ } else {
+ throw new CloudException("Cannot save new user, please signUp " .
+ "first." );
+ }
+ }
+
+
+ public function updatePassword($old , $new ) {
+ if ($this ->getObjectId()) {
+ $path = "/users/ {$this->getObjectId()} /updatePassword" ;
+ $resp = Client::put($path , array ("old_password" => $old ,
+ "new_password" => $new ),
+ $this ->getSessionToken());
+ $this ->mergeAfterFetch($resp );
+ static ::saveCurrentUser($this );
+ } else {
+ throw new CloudException("Cannot update password on new user." );
+ }
+ }
+
+
+ public function getUsername() {
+ return $this ->get("username" );
+ }
+
+
+ public function getEmail() {
+ return $this ->get("email" );
+ }
+
+
+ public function getMobilePhoneNumber() {
+ return $this ->get("mobilePhoneNumber" );
+ }
+
+
+ public function getSessionToken() {
+ return $this ->get("sessionToken" );
+ }
+
+
+
+
+
+ public static function setCurrentSessionToken($token ) {
+ Client::getStorage()->set("LC_SessionToken" , $token );
+ }
+
+
+ public static function getCurrentSessionToken() {
+ return Client::getStorage()->get("LC_SessionToken" );
+ }
+
+
+ public static function getCurrentUser() {
+ if (self::$currentUser instanceof User) {
+ return self::$currentUser ;
+ }
+ $token = static ::getCurrentSessionToken();
+ if ($token ) {
+ return static ::become($token );
+ }
+ }
+
+
+ public static function saveCurrentUser($user ) {
+ self::$currentUser = $user ;
+ self::setCurrentSessionToken($user ->getSessionToken());
+ }
+
+
+ public static function clearCurrentUser() {
+ self::$currentUser = null ;
+ self::setCurrentSessionToken(null );
+ }
+
+
+ public function refreshSessionToken() {
+ $resp = Client::put("/users/ {$this->getObjectId()} /refreshSessionToken" ,
+ null );
+ $this ->mergeAfterFetch($resp );
+ static ::saveCurrentUser($this );
+ }
+
+
+ public function isAuthenticated() {
+ $token = $this ->getSessionToken();
+ if (!$token ) {
+ return false ;
+ }
+ try {
+ $resp = Client::get("/users/me" ,
+ array ("session_token" => $token ));
+ } catch (CloudException $ex ) {
+ if ($ex ->getCode() === 211 ) {
+ return false ;
+ }
+ throw ex;
+ }
+ return true ;
+ }
+
+
+ public function getRoles() {
+ if (!$this ->getObjectId()) {
+ return array ();
+ }
+ $query = new Query("_Role" );
+ $query ->equalTo("users" , $this );
+ $roles = $query ->find();
+ return $roles ;
+ }
+
+
+ public static function become($token ) {
+ $resp = Client::get("/users/me" ,
+ array ("session_token" => $token ));
+ $user = new static ();
+ $user ->mergeAfterFetch($resp );
+ static ::saveCurrentUser($user );
+
+ return $user ;
+ }
+
+
+ private static function _login($userData ) {
+ $resp = Client::post("/login" , $userData );
+ $user = new static ();
+ $user ->mergeAfterFetch($resp );
+ static ::saveCurrentUser($user );
+ return $user ;
+ }
+
+ public static function logIn($username , $password ) {
+ $user = static ::_login(array ("username" => $username ,
+ "password" => $password ));
+ return $user ;
+ }
+
+ public static function logInWithEmail($email , $password ) {
+ $user = static ::_login(array ("email" => $email ,
+ "password" => $password ));
+ return $user ;
+ }
+
+
+
+ public static function logOut() {
+ $user = static ::getCurrentUser();
+ if ($user ) {
+ try {
+ Client::post("/logout" , null , $user ->getSessionToken());
+ } catch (CloudException $exp ) {
+
+ }
+ static ::clearCurrentUser($user );
+ }
+ }
+
+
+ public static function logInWithMobilePhoneNumber($phoneNumber , $password ) {
+ $params = array ("mobilePhoneNumber" => $phoneNumber ,
+ "password" => $password );
+ $resp = Client::post("/login" , $params );
+ $user = new static ();
+ $user ->mergeAfterFetch($resp );
+ static ::saveCurrentUser($user );
+ return $user ;
+ }
+
+
+ public static function logInWithSmsCode($phoneNumber , $smsCode ) {
+ $params = array ("mobilePhoneNumber" => $phoneNumber ,
+ "smsCode" => $smsCode );
+ $resp = Client::get("/login" , $params );
+ $user = new static ();
+ $user ->mergeAfterFetch($resp );
+ static ::saveCurrentUser($user );
+ return $user ;
+ }
+
+
+ public static function requestLoginSmsCode($phoneNumber ) {
+ Client::post("/requestLoginSmsCode" ,
+ array ("mobilePhoneNumber" => $phoneNumber ));
+ }
+
+
+ public static function requestEmailVerify($email ) {
+ Client::post("/requestEmailVerify" , array ("email" => $email ));
+ }
+
+
+ public static function requestPasswordReset($email ) {
+ Client::post("/requestPasswordReset" , array ("email" => $email ));
+ }
+
+
+ public static function requestPasswordResetBySmsCode($phoneNumber ) {
+ Client::post("/requestPasswordResetBySmsCode" ,
+ array ("mobilePhoneNumber" => $phoneNumber ));
+ }
+
+
+ public static function resetPasswordBySmsCode($smsCode , $newPassword ) {
+ Client::put("/resetPasswordBySmsCode/ {$smsCode} " ,
+ array ("password" => $newPassword ));
+ }
+
+
+ public static function requestMobilePhoneVerify($phoneNumber ) {
+ Client::post("/requestMobilePhoneVerify" ,
+ array ("mobilePhoneNumber" => $phoneNumber ));
+ }
+
+
+ public static function verifyMobilePhone($smsCode ) {
+ Client::post("/verifyMobilePhone/ {$smsCode} " , null );
+ }
+
+
+ public static function requestChangePhoneNumber($phoneNumber ) {
+ Client::post("/requestChangePhoneNumber" , array (
+ "mobilePhoneNumber" => $phoneNumber
+ ));
+ }
+
+
+ public static function changePhoneNumber($smsCode , $phoneNumber ) {
+ Client::post("/changePhoneNumber" , array (
+ "mobilePhoneNumber" => $phoneNumber ,
+ "code" => $smsCode
+ ));
+ }
+
+
+ public static function signUpOrLoginByMobilePhone($phoneNumber , $smsCode ) {
+ $resp = Client::post("/usersByMobilePhone" , array (
+ "mobilePhoneNumber" => $phoneNumber ,
+ "smsCode" => $smsCode
+ ));
+ $user = new static ();
+ $user ->mergeAfterFetch($resp );
+ static ::saveCurrentUser($user );
+ return $user ;
+ }
+
+
+
+
+ public static function logInWith($provider , $authToken ) {
+ $user = new static ();
+ $user ->linkWith($provider , $authToken );
+ static ::saveCurrentUser($user );
+ return $user ;
+ }
+
+
+ public function linkWith($provider , $authToken ) {
+ if (!is_string ($provider ) || empty ($provider )) {
+ throw new \InvalidArgumentException("Provider name can only " .
+ "be string." );
+ }
+ $data = $this ->get("authData" );
+ if (!$data ) {
+ $data = array ();
+ }
+ $data [$provider ] = $authToken ;
+ $this ->set("authData" , $data );
+ parent::save();
+
+ return $this ;
+ }
+
+
+ public function unlinkWith($provider ) {
+ if (!is_string ($provider ) || empty ($provider )) {
+ throw new \InvalidArgumentException("Provider name can only " .
+ "be string." );
+ }
+ if (!$this ->getObjectId()) {
+ throw new \RuntimeException("Cannot unlink with unsaved user." );
+ }
+
+ $data = $this ->get("authData" );
+ if (isset ($data [$provider ])) {
+ $data [$provider ] = null ;
+ $this ->set("authData" , $data );
+ $this ->save();
+ }
+ return $this ;
+ }
+
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/fabfile.py b/fabfile.py
new file mode 100644
index 0000000..ffb8403
--- /dev/null
+++ b/fabfile.py
@@ -0,0 +1,49 @@
+import os
+
+# Usage:
+# fab -H username@hostname deploy_docs:local_dir='folder',platform='php'
+#
+#
+
+from fabric.api import run, sudo, env, cd, local, prefix, put, lcd, settings
+from fabric.contrib.files import exists, sed
+from fabric.contrib.project import rsync_project
+
+env.use_ssh_config = True
+
+user = 'deploy'
+doc_dir = '/var/www/avoscloud-api-docs'
+
+project_dir = "."
+dist = 'debian'
+host_count = len(env.hosts)
+
+def _set_user_dir():
+ global dist,user,doc_dir
+ with settings(warn_only=True):
+ issue = run('id ubuntu').lower()
+ if 'id: ubuntu' in issue:
+ dist = 'debian'
+ elif 'uid=' in issue:
+ dist = 'ubuntu'
+ user = 'ubuntu'
+ doc_dir = '/mnt/avos/avoscloud-api-docs'
+
+def prepare_remote_dirs(remote_dir):
+ _set_user_dir()
+ if not exists(remote_dir):
+ sudo('mkdir -p %s' % remote_dir)
+ sudo('chown %s %s' % (user, remote_dir))
+
+def deploy_docs(local_dir='', platform='unknown'):
+ global host_count
+ _set_user_dir()
+ remote_dir = '%s/%s/' % (doc_dir, platform)
+
+ prepare_remote_dirs(remote_dir)
+ rsync_project(local_dir=local_dir + '/',
+ remote_dir=remote_dir,
+ delete=True)
+ host_count -= 1
+ if (host_count == 0):
+ print("Finished to public api docs!")
diff --git a/phpunit.xml b/phpunit.xml
index 9e27115..b4242cc 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -4,14 +4,14 @@
forceCoversAnnotation="false">
- tests
+ test
+ tests/engine
src
- src/LeanCloud/LeanClient.php
diff --git a/release.sh b/release.sh
index 6eb87a1..29a72a2 100755
--- a/release.sh
+++ b/release.sh
@@ -9,22 +9,17 @@ version="$1"
#### Build new changelog
echo "" >> Changelog.md.0
-echo "$version 发布日期:`date +%Y-%m-%d`" >> Changelog.md.0
+echo "$version Released on `date +%Y-%m-%d`" >> Changelog.md.0
echo "----" >> Changelog.md.0
-git log `git describe --tags --abbrev=0`..HEAD | \
- grep "^(changelog)" >> Changelog.md.0
+git log `git describe --tags --abbrev=0`..HEAD --pretty=%s >> Changelog.md.0
#### prepend changelog
cat Changelog.md >> Changelog.md.0
mv Changelog.md.0 Changelog.md
#### update version string client
-# sed -i '' -e"s/const VERSION = .*\;/const VERSION = \'$version\'\;/" \
-# src/LeanCloud/LeanClient.php
-
-# portable solution in perl
perl -pi -e "s/const VERSION = .*\;/const VERSION = \'$version\'\;/" \
- src/LeanCloud/LeanClient.php
+ src/LeanCloud/Client.php
echo "Done! Ready to commit and release $version!"
diff --git a/src/LeanCloud/LeanACL.php b/src/LeanCloud/ACL.php
similarity index 87%
rename from src/LeanCloud/LeanACL.php
rename to src/LeanCloud/ACL.php
index 91685d3..bf8002d 100644
--- a/src/LeanCloud/LeanACL.php
+++ b/src/LeanCloud/ACL.php
@@ -9,9 +9,9 @@
* users and roles. There can be as many users and roles as possible
* in an ACL.
*
- * @see LeanRole
+ * @see Role
*/
-class LeanACL {
+class ACL {
/**
* Public access key in ACL field
*/
@@ -33,12 +33,12 @@ class LeanACL {
*
* With empty param, it creates an ACL with no permission granted.
*
- * @param mixed $val LeanUser or JSON encoded ACL array
+ * @param mixed $val User or JSON encoded ACL array
*/
public function __construct($val=array()) {
$this->data = array();
- if ($val instanceof LeanUser) {
+ if ($val instanceof User) {
$this->setReadAccess($val, true);
$this->setWriteAccess($val, true);
} else if (is_array($val)) {
@@ -151,11 +151,11 @@ public function setPublicWriteAccess($flag) {
* Even if it returns false, the group may still be able to access
* object if object is accessible to public.
*
- * @param string|LeanRole Role object or name
+ * @param string|Role Role object or name
* @return bool
*/
public function getRoleReadAccess($role) {
- if ($role instanceof LeanRole) {
+ if ($role instanceof Role) {
$role = $role->getName();
}
return $this->getAccess("role:$role", "read");
@@ -167,11 +167,11 @@ public function getRoleReadAccess($role) {
* Even if it returns false, the group may still be able to access
* object if object is accessible to public.
*
- * @param string|LeanRole Role object or name
+ * @param string|Role Role object or name
* @return bool
*/
public function getRoleWriteAccess($role) {
- if ($role instanceof LeanRole) {
+ if ($role instanceof Role) {
$role = $role->getName();
}
return $this->getAccess("role:$role", "write");
@@ -180,17 +180,17 @@ public function getRoleWriteAccess($role) {
/**
* Set read access for role
*
- * @param string|LeanRole $role Role object or role name
+ * @param string|Role $role Role object or role name
* @param bool $flag
* @return self
*/
public function setRoleReadAccess($role, $flag) {
- if ($role instanceof LeanRole) {
+ if ($role instanceof Role) {
$role = $role->getName();
}
if (!is_string($role)) {
throw new \InvalidArgumentException("role must be either " .
- "LeanRole or string.");
+ "Role or string.");
}
$this->setAccess("role:$role", "read", $flag);
return $this;
@@ -199,17 +199,17 @@ public function setRoleReadAccess($role, $flag) {
/**
* Set write access for role
*
- * @param string|LeanRole $role Role object or role name
+ * @param string|Role $role Role object or role name
* @param bool $flag
* @return self
*/
public function setRoleWriteAccess($role, $flag) {
- if ($role instanceof LeanRole) {
+ if ($role instanceof Role) {
$role = $role->getName();
}
if (!is_string($role)) {
throw new \InvalidArgumentException("role must be either " .
- "LeanRole or string.");
+ "Role or string.");
}
$this->setAccess("role:$role", "write", $flag);
return $this;
@@ -222,11 +222,11 @@ public function setRoleWriteAccess($role, $flag) {
* object if object is accessible to public or a role the user
* belongs to.
*
- * @param string|LeanUser $user Target user or user id
+ * @param string|User $user Target user or user id
* @return bool
*/
public function getReadAccess($user) {
- if ($user instanceof LeanUser) {
+ if ($user instanceof User) {
$user = $user->getObjectId();
}
return $this->getAccess($user, "read");
@@ -239,11 +239,11 @@ public function getReadAccess($user) {
* object if object is accessible to public or a role the user
* belongs to.
*
- * @param string|LeanUser $user Target user or user id
+ * @param string|User $user Target user or user id
* @return bool
*/
public function getWriteAccess($user) {
- if ($user instanceof LeanUser) {
+ if ($user instanceof User) {
$user = $user->getObjectId();
}
return $this->getAccess($user, "write");
@@ -252,12 +252,12 @@ public function getWriteAccess($user) {
/**
* Set read access for user
*
- * @param string|LeanUser $user Target user or user id
+ * @param string|User $user Target user or user id
* @param bool $flag Enable or disable read for user
* @return self
*/
public function setReadAccess($user, $flag) {
- if ($user instanceof LeanUser) {
+ if ($user instanceof User) {
if (!$user->getObjectId()) {
throw new \RuntimeException("user must be saved before " .
"being assigned in ACL.");
@@ -266,7 +266,7 @@ public function setReadAccess($user, $flag) {
}
if (!is_string($user)) {
throw new \InvalidArgumentException("user must be either " .
- " LeanUser or objectId.");
+ " User or objectId.");
}
$this->setAccess($user, "read", $flag);
return $this;
@@ -275,12 +275,12 @@ public function setReadAccess($user, $flag) {
/**
* Set write access for user
*
- * @param string|LeanUser $user Target user or user id
+ * @param string|User $user Target user or user id
* @param bool $flag Enable or disable write for user
* @return self
*/
public function setWriteAccess($user, $flag) {
- if ($user instanceof LeanUser) {
+ if ($user instanceof User) {
if (!$user->getObjectId()) {
throw new \RuntimeException("user must be saved before " .
"being assigned in ACL.");
@@ -289,7 +289,7 @@ public function setWriteAccess($user, $flag) {
}
if (!is_string($user)) {
throw new \InvalidArgumentException("user must be either " .
- " LeanUser or objectId.");
+ " User or objectId.");
}
$this->setAccess($user, "write", $flag);
return $this;
diff --git a/src/LeanCloud/AppRouter.php b/src/LeanCloud/AppRouter.php
new file mode 100644
index 0000000..8878d8b
--- /dev/null
+++ b/src/LeanCloud/AppRouter.php
@@ -0,0 +1,225 @@
+ "us-api.leancloud.cn",
+ Region::CN_E1 => "e1-api.leancloud.cn",
+ Region::CN_N1 => "api.leancloud.cn"
+ );
+
+ private static $DEFAULT_REGION_RTM_ROUTE = array(
+ Region::US => "router-a0-push.leancloud.cn",
+ Region::CN_E1 => "router-q0-push.leancloud.cn",
+ Region::CN_N1 => "router-g0-push.leancloud.cn"
+ );
+
+ private function __construct($appId) {
+ $this->appId = $appId;
+ $region = getenv("LEANCLOUD_REGION");
+ if (!$region) {
+ $region = Region::CN;
+ }
+ $this->setRegion($region);
+ $this->routeCache = RouteCache::create($appId);
+ }
+
+ /**
+ * Get instance of AppRouter.
+ */
+ public static function getInstance($appId) {
+ if (isset(self::$INSTANCES[$appId])) {
+ return self::$INSTANCES[$appId];
+ } else {
+ $router = new AppRouter($appId);
+ self::$INSTANCES[$appId] = $router;
+ return $router;
+ }
+ }
+
+ /**
+ * Get app region default route host
+ */
+ public function getRegionDefaultRoute($server_key) {
+ $this->validate_server_key($server_key);
+ return $this->getDefaultRoutes()[$server_key];
+ }
+
+ /**
+ * Set region
+ *
+ * See `LeanCloud\Region` for available regions.
+ *
+ * @param mixed $region
+ */
+ public function setRegion($region) {
+ if (is_numeric($region)) {
+ $this->region = $region;
+ } else {
+ $this->region = Region::fromName($region);
+ }
+ }
+
+ /**
+ * Get and return route host by server type, or null if not found.
+ */
+ public function getRoute($server_key) {
+ $this->validate_server_key($server_key);
+ $routes = $this->routeCache->read();
+ if (isset($routes[$server_key])) {
+ return $routes[$server_key];
+ }
+ $routes = $this->getRoutes();
+ if (!$routes) {
+ $routes = $this->getDefaultRoutes();
+ }
+ $this->routeCache->write($routes);
+ return isset($routes[$server_key]) ? $routes[$server_key] : null;
+ }
+
+ private function getRouterUrl() {
+ $url = getenv("LEANCLOUD_APP_ROUTER");
+ if (!$url) {
+ $url = "https://app-router.leancloud.cn/2/route?appId=";
+ }
+ return "{$url}{$this->appId}";
+ }
+
+ private function validate_server_key($server_key) {
+ $routes = $this->getDefaultRoutes();
+ if (!isset($routes[$server_key])) {
+ throw new IllegalArgumentException("Invalid server key.");
+ }
+ }
+
+ /**
+ * Detect region by app-id
+ */
+ private function detectRegion() {
+ if (!$this->appId) {
+ return Region::CN_N1;
+ }
+ $parts = explode("-", $this->appId);
+ if (count($parts) <= 1) {
+ return Region::CN_N1;
+ } else if ($parts[1] === "MdYXbMMI") {
+ return Region::US;
+ } else if ($parts[1] === "9Nh9j0Va") {
+ return Region::CN_E1;
+ } else {
+ $this->region = Region::CN_N1;
+ }
+ }
+
+ /**
+ * Get routes remotely from app router, return array.
+ */
+ private function getRoutes() {
+ $routes = @json_decode(file_get_contents($this->getRouterUrl()), true);
+ if (isset($routes[self::TTL_KEY])) {
+ return $routes;
+ }
+ return null;
+ }
+
+ /**
+ * Fallback default routes, if app router not available.
+ */
+ private function getDefaultRoutes() {
+ $host = self::$DEFAULT_REGION_ROUTE[$this->region];
+
+ return array(
+ self::API_SERVER_KEY => $host,
+ self::PUSH_SERVER_KEY => $host,
+ self::STATS_SERVER_KEY => $host,
+ self::ENGINE_SERVER_KEY => $host,
+ self::RTM_ROUTER_SERVER_KEY => self::$DEFAULT_REGION_RTM_ROUTE[$this->region],
+ self::TTL_KEY => 3600
+ );
+ }
+
+
+}
+
+
+/**
+ * Route cache
+ *
+ * Ideally we should use ACPu for caching, but it can be inconvenient to
+ * install, esp. on Windows[1], thus we implement a naive file based
+ * cache.
+ *
+ * [1]: https://stackoverflow.com/a/28124144/108112
+ */
+class RouteCache {
+ private $filename;
+ private $_cache;
+
+ private function __construct($id) {
+ $this->filename = sys_get_temp_dir() . "/route_{$id}.json";
+ }
+
+ public static function create($id) {
+ return new RouteCache($id);
+ }
+
+ /**
+ * Serialize array and store in file, array must be json_encode safe.
+ */
+ public function write($array) {
+ $body = json_encode($array);
+ if (file_put_contents($this->filename, $body, LOCK_EX) === false) {
+ error_log("WARNING: failed to write route cache ({$this->filename}), performance may be degraded.");
+ } else {
+ $this->_cache = $array;
+ }
+ }
+
+ /**
+ * Read routes either from cache or file, return json_decoded array.
+ */
+ public function read() {
+ if ($this->_cache) {
+ return $this->_cache;
+ }
+ $data = $this->readFile();
+ if (!empty($data)) {
+ $this->_cache = $data;
+ return $data;
+ }
+ return null;
+ }
+
+ private function readFile() {
+ if (file_exists($this->filename)) {
+ $fp = fopen($this->filename, "rb");
+ $body = null;
+ if (flock($fp, LOCK_SH)) {
+ $body = fread($fp, filesize($this->filename));
+ flock($fp, LOCK_UN);
+ }
+ fclose($fp);
+ if (!empty($body)) {
+ $data = @json_decode($body, true);
+ if (!empty($data)) {
+ return $data;
+ }
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/LeanCloud/BatchRequestError.php b/src/LeanCloud/BatchRequestError.php
index d250cc3..b04eecf 100644
--- a/src/LeanCloud/BatchRequestError.php
+++ b/src/LeanCloud/BatchRequestError.php
@@ -15,8 +15,8 @@ class BatchRequestError extends CloudException {
*
* @var array
*/
- private $errors;
-
+ private $errors = [];
+
public function __construct($message="", $code = 1) {
$message = empty($message) ? "Batch request error." : $message;
parent::__construct($message, $code);
@@ -32,14 +32,10 @@ public function __construct($message="", $code = 1) {
* @return BatchRequestError
*/
public function add($request, $response) {
- if (!isset($response["error"])) {
- throw new \InvalidArgumentException("Invalid error response.");
- }
- if (!isset($response["code"])) {
- $response["code"] = 1;
- }
- $response["request"] = $request;
- $this->errors[] = $response;
+ $error["code"] = isset($response["code"]) ? $response["code"] : 1;
+ $error["error"] = "{$error['code']} {$response['error']}:"
+ . json_encode($request);
+ $this->errors[] = $error;
return $this;
}
@@ -77,7 +73,7 @@ public function isEmpty() {
public function __toString() {
$message = $this->message;
if (!$this->isEmpty()) {
- $message .= json_encode($this-errors);
+ $message .= json_encode($this->errors);
}
return __CLASS__ . ": [{$this->code}]: {$message}\n";
}
diff --git a/src/LeanCloud/LeanBytes.php b/src/LeanCloud/Bytes.php
similarity index 85%
rename from src/LeanCloud/LeanBytes.php
rename to src/LeanCloud/Bytes.php
index 31b5e82..021fa99 100644
--- a/src/LeanCloud/LeanBytes.php
+++ b/src/LeanCloud/Bytes.php
@@ -5,7 +5,7 @@
/**
* Byte array data type for LeanObject
*/
-class LeanBytes {
+class Bytes {
/**
* Byte array
*
@@ -14,25 +14,25 @@ class LeanBytes {
private $byteArray = array();
/**
- * Create LeanBytes from byte array
+ * Create Bytes from byte array
*
* @param array $byteArray
- * @return LeanBytes
+ * @return Bytes
*/
public static function createFromByteArray(array $byteArray) {
- $bytes = new LeanBytes();
+ $bytes = new Bytes();
$bytes->byteArray = $byteArray;
return $bytes;
}
/**
- * Create LeanBytes from base64 encoded string
+ * Create Bytes from base64 encoded string
*
* @param string $data Base64 encoded string
- * @return LeanBytes
+ * @return Bytes
*/
public static function createFromBase64Data($data) {
- $bytes = new LeanBytes();
+ $bytes = new Bytes();
// convert unpacked associative array to sequence array
$byteMap = unpack('C*', base64_decode($data));
diff --git a/src/LeanCloud/LeanClient.php b/src/LeanCloud/Client.php
similarity index 64%
rename from src/LeanCloud/LeanClient.php
rename to src/LeanCloud/Client.php
index 38662aa..07090de 100644
--- a/src/LeanCloud/LeanClient.php
+++ b/src/LeanCloud/Client.php
@@ -2,14 +2,15 @@
namespace LeanCloud;
-use LeanCloud\LeanBytes;
+use LeanCloud\Bytes;
use LeanCloud\LeanObject;
-use LeanCloud\LeanACL;
-use LeanCloud\LeanFile;
-use LeanCloud\LeanUser;
+use LeanCloud\ACL;
+use LeanCloud\File;
+use LeanCloud\User;
use LeanCloud\Operation\IOperation;
use LeanCloud\Storage\IStorage;
use LeanCloud\Storage\SessionStorage;
+use LeanCloud\AppRouter;
/**
* Client interfacing with LeanCloud REST API
@@ -19,29 +20,11 @@
* such as `::randomFloat` to generate a random float number.
*
*/
-class LeanClient {
+class Client {
/**
* Client version
*/
- const VERSION = '0.2.6';
-
- /**
- * API Endpoints for Regions
- *
- * @var array
- */
- private static $api = array(
- "CN" => "https://api.leancloud.cn",
- "US" => "https://us-api.leancloud.cn");
-
- /**
- * API Region
- *
- * Default to CN
- *
- * @var string
- */
- private static $apiRegion = "CN";
+ const VERSION = '0.14.2';
/**
* API Version string
@@ -80,6 +63,13 @@ class LeanClient {
*/
private static $appMasterKey;
+ /**
+ * Server url
+ *
+ * @var string
+ */
+ private static $serverUrl;
+
/**
* Use master key or not
*
@@ -88,11 +78,18 @@ class LeanClient {
private static $useMasterKey = false;
/**
- * Use production or not
+ * Is in production or not
+ *
+ * @var bool
+ */
+ public static $isProduction = false;
+
+ /**
+ * Is in debug mode or not
*
* @var bool
*/
- private static $useProduction = false;
+ private static $debugMode = false;
/**
* Default request headers
@@ -108,7 +105,6 @@ class LeanClient {
*/
private static $storage;
-
/**
* Initialize application key and settings
*
@@ -124,6 +120,7 @@ public static function initialize($appId, $appKey, $appMasterKey) {
self::$defaultHeaders = array(
'X-LC-Id' => self::$appId,
'Content-Type' => 'application/json;charset=utf-8',
+ 'Accept-Encoding' => 'gzip, deflate',
'User-Agent' => self::getVersionString()
);
@@ -132,8 +129,20 @@ public static function initialize($appId, $appKey, $appMasterKey) {
self::$storage = new SessionStorage();
}
- LeanUser::registerClass();
- LeanRole::registerClass();
+ self::useProduction(getenv("LEANCLOUD_APP_ENV") == "production");
+ User::registerClass();
+ Role::registerClass();
+ }
+
+ /**
+ * Set a deadline for requests to complete.
+ *
+ * Note that file upload requests are not affected.
+ *
+ * @param integer $seconds
+ */
+ public static function setApiTimeout($seconds) {
+ static::$apiTimeout = intval($seconds);
}
/**
@@ -147,7 +156,7 @@ private static function assertInitialized() {
!isset(self::$appMasterKey)) {
throw new \RuntimeException("Client is not initialized, " .
"please specify application key " .
- "with LeanClient::initialize.");
+ "with Client::initialize.");
}
}
@@ -156,31 +165,40 @@ private static function assertInitialized() {
*
* @return string
*/
- private static function getVersionString() {
+ public static function getVersionString() {
return "LeanCloud PHP SDK " . self::VERSION;
}
/**
* Set API region
*
- * Available regions are "CN" and "US".
+ * See `LeanCloud\Region` for available regions.
*
- * @param string $region
+ * @param mixed $region
*/
public static function useRegion($region) {
- if (!isset(self::$api[$region])) {
- throw new \RuntimeException("Invalid API region: {$region}.");
- }
- self::$apiRegion = $region;
+ self::assertInitialized();
+ AppRouter::getInstance(self::$appId)->setRegion($region);
}
/**
* Use production or not
*
- * @param bool $flag
+ * @param bool $flag Default false
*/
public static function useProduction($flag) {
- self::$useProduction = $flag ? true : false;
+ self::$isProduction = $flag ? true : false;
+ }
+
+ /**
+ * Set debug mode
+ *
+ * Enable debug mode to log request params and response.
+ *
+ * @param bool $flag Default false
+ */
+ public static function setDebug($flag) {
+ self::$debugMode = $flag ? true : false;
}
/**
@@ -192,22 +210,41 @@ public static function useMasterKey($flag) {
self::$useMasterKey = $flag ? true : false;
}
+ /**
+ * Set server url
+ *
+ * Explicitly set server url with which this client will communicate.
+ * Url shall be in the form of: `https://api.leancloud.cn` .
+ *
+ * @param string $url
+ */
+ public static function setServerUrl($url) {
+ self::$serverUrl = rtrim($url, "/");
+ }
+
/**
* Get API Endpoint
*
- * Build the API endpoint, the returned endpoint will include
- * version string. For example: https://api.leancloud.cn/1.1 .
+ * The returned endpoint will include version string.
+ * For example: https://api.leancloud.cn/1.1 .
*
* @return string
*/
public static function getAPIEndPoint() {
- return self::$api[self::$apiRegion] . "/" . self::$apiVersion;
+ if ($url = self::$serverUrl) {
+ return $url . "/" . self::$apiVersion;
+ } else if ($url = getenv("LEANCLOUD_API_SERVER")) {
+ return $url . "/" . self::$apiVersion;
+ } else {
+ $host = AppRouter::getInstance(self::$appId)->getRoute(AppRouter::API_SERVER_KEY);
+ return "https://{$host}/" . self::$apiVersion;
+ }
}
/**
* Build authentication headers
*
- * @param string $sessionToken Session token of a LeanUser
+ * @param string $sessionToken Session token of a User
* @param bool $useMasterKey
* @return array
*/
@@ -217,7 +254,7 @@ public static function buildHeaders($sessionToken, $useMasterKey) {
}
$h = self::$defaultHeaders;
- $h['X-LC-Prod'] = self::$useProduction ? 1 : 0;
+ $h['X-LC-Prod'] = self::$isProduction ? 1 : 0;
$timestamp = time();
$key = $useMasterKey ? self::$appMasterKey : self::$appKey;
@@ -229,7 +266,7 @@ public static function buildHeaders($sessionToken, $useMasterKey) {
}
if (!$sessionToken) {
- $sessionToken = LeanUser::getCurrentSessionToken();
+ $sessionToken = User::getCurrentSessionToken();
}
if ($sessionToken) {
@@ -239,6 +276,77 @@ public static function buildHeaders($sessionToken, $useMasterKey) {
return $h;
}
+ /**
+ * Verify app ID and sign
+ *
+ * The sign must be in the format of "{md5sum},{timestamp}[,master]",
+ * which follows the format as in header "X-LC-Sign".
+ *
+ * @param string $appId App Id
+ * @param string $sign Request sign
+ * @return bool
+ */
+ public static function verifySign($appId, $sign) {
+ if (!$appId || ($appId != self::$appId)) {
+ return false;
+ }
+ $parts = explode(",", $sign);
+ $key = self::$appKey;
+ if (isset($parts[2]) && "master" === trim($parts[2])) {
+ $key = self::$appMasterKey;
+ }
+ return $parts[0] === md5(trim($parts[1]) . $key);
+ }
+
+ /**
+ * Verify app ID and key
+ *
+ * The key shall be in format of "{key}[,master]", it will be verified
+ * as master key if master suffix present.
+ *
+ * @param string $appId App Id
+ * @param string $key App key or master key
+ * @return bool
+ */
+ public static function verifyKey($appId, $key) {
+ if (!$appId || ($appId != self::$appId)) {
+ return false;
+ }
+ $parts = explode(",", $key);
+ if (isset($parts[1]) && "master" === trim($parts[1])) {
+ return self::$appMasterKey === $parts[0];
+ }
+ return self::$appKey === $parts[0];
+ }
+
+ /**
+ * Generate a sign used to auth hook invocation on LeanEngine
+ *
+ * @param string $hookName E.g. "__before_for_Object"
+ * @param integer $msec Timestamap in microseconds
+ * @return string
+ */
+ public static function signHook($hookName, $msec) {
+ $hash = hash_hmac("sha1", "{$hookName}:{$msec}", self::$appMasterKey);
+ return "{$msec},{$hash}";
+ }
+
+ /**
+ * Verify a signed hook
+ *
+ * @param string $hookName
+ * @param string $sign
+ * @return bool
+ */
+ public static function verifyHookSign($hookName, $sign) {
+ if ($sign) {
+ $parts = explode(",", $sign);
+ $msec = $parts[0];
+ return self::signHook($hookName, $msec) === $sign;
+ }
+ return false;
+ }
+
/**
* Issue request to LeanCloud
*
@@ -252,7 +360,7 @@ public static function buildHeaders($sessionToken, $useMasterKey) {
* @param string $method GET, POST, PUT, DELETE
* @param string $path Request path (without version string)
* @param array $data Payload data
- * @param string $sessionToken Session token of a LeanUser
+ * @param string $sessionToken Session token of a User
* @param array $headers Optional headers
* @param bool $useMasterkey Use master key or not
* @return array JSON decoded associative array
@@ -287,12 +395,14 @@ public static function request($method, $path, $data,
curl_setopt($req, CURLOPT_RETURNTRANSFER, true);
curl_setopt($req, CURLOPT_TIMEOUT, self::$apiTimeout);
// curl_setopt($req, CURLINFO_HEADER_OUT, true);
+ // curl_setopt($req, CURLOPT_HEADER, true);
+ curl_setopt($req, CURLOPT_ENCODING, '');
switch($method) {
case "GET":
if ($data) {
// append GET data as query string
- curl_setopt($req, CURLOPT_URL,
- $url ."?". http_build_query($data));
+ $url .= "?" . http_build_query($data);
+ curl_setopt($req, CURLOPT_URL, $url);
}
break;
case "POST":
@@ -308,13 +418,23 @@ public static function request($method, $path, $data,
default:
break;
}
+ $reqId = rand(100,999);
+ if (self::$debugMode) {
+ error_log("[DEBUG] HEADERS {$reqId}:" . json_encode($headersList));
+ error_log("[DEBUG] REQUEST {$reqId}: {$method} {$url} {$json}");
+ }
+ // list($headers, $resp) = explode("\r\n\r\n", curl_exec($req), 2);
$resp = curl_exec($req);
$respCode = curl_getinfo($req, CURLINFO_HTTP_CODE);
$respType = curl_getinfo($req, CURLINFO_CONTENT_TYPE);
- $error = curl_errno($req);
+ $error = curl_error($req);
$errno = curl_errno($req);
curl_close($req);
+ if (self::$debugMode) {
+ error_log("[DEBUG] RESPONSE {$reqId}: {$resp}");
+ }
+
/** type of error:
* - curl connection error
* - http status error 4xx, 5xx
@@ -326,13 +446,15 @@ public static function request($method, $path, $data,
$errno);
}
if (strpos($respType, "text/html") !== false) {
- throw new CloudException("Bad request", -1);
+ throw new CloudException("Bad response type text/html", -1, $respCode,
+ $method, $url);
}
$data = json_decode($resp, true);
if (isset($data["error"])) {
$code = isset($data["code"]) ? $data["code"] : -1;
- throw new CloudException("{$code} {$data['error']}", $code);
+ throw new CloudException("{$data['error']}", $code, $respCode,
+ $method, $url);
}
return $data;
}
@@ -342,7 +464,7 @@ public static function request($method, $path, $data,
*
* @param string $path Request path (without version string)
* @param array $data Payload data
- * @param string $sessionToken Session token of a LeanUser
+ * @param string $sessionToken Session token of a User
* @param array $headers Optional headers
* @param bool $useMasterkey Use master key or not
* @return array JSON decoded associated array
@@ -359,7 +481,7 @@ public static function get($path, $data=null, $sessionToken=null,
*
* @param string $path Request path (without version string)
* @param array $data Payload data
- * @param string $sessionToken Session token of a LeanUser
+ * @param string $sessionToken Session token of a User
* @param array $headers Optional headers
* @param bool $useMasterkey Use master key or not, optional
* @return array JSON decoded associated array
@@ -376,7 +498,7 @@ public static function post($path, $data, $sessionToken=null,
*
* @param string $path Request path (without version string)
* @param array $data Payload data
- * @param string $sessionToken Session token of a LeanUser
+ * @param string $sessionToken Session token of a User
* @param array $headers Optional headers
* @param bool $useMasterkey Use master key or not, optional
* @return array JSON decoded associated array
@@ -392,7 +514,7 @@ public static function put($path, $data, $sessionToken=null,
* Issue DELETE request to LeanCloud
*
* @param string $path Request path (without version string)
- * @param string $sessionToken Session token of a LeanUser
+ * @param string $sessionToken Session token of a User
* @param array $headers Optional headers
* @param bool $useMasterkey Use master key or not, optional
* @return array JSON decoded associated array
@@ -408,7 +530,7 @@ public static function delete($path, $sessionToken=null,
* Issue a batch request
*
* @param array $requests Array of requests in batch op
- * @param string $sessionToken Session token of a LeanUser
+ * @param string $sessionToken Session token of a User
* @param array $headers Optional headers
* @param bool $useMasterkey Use master key or not, optional
* @return array JSON decoded associated array
@@ -416,7 +538,7 @@ public static function delete($path, $sessionToken=null,
*/
public static function batch($requests, $sessionToken=null,
$headers=array(), $useMasterKey=null) {
- $response = LeanClient::post("/batch",
+ $response = Client::post("/batch",
array("requests" => $requests),
$sessionToken,
$headers,
@@ -437,122 +559,36 @@ public static function batch($requests, $sessionToken=null,
}
/**
- * Encode file with params in multipart format
+ * Recursively encode value as JSON representation
*
- * @param array $file File data and attributes
- * @param array $params Key-value params
- * @param string $boundary Boundary string used for frontier
- * @return string Multipart encoded string
- */
- public static function multipartEncode($file, $params,
- $boundary=null) {
- if (!$boundary) {
- $boundary = md5(microtime());
- }
-
- $body = "";
- forEach($params as $key => $val) {
- $body .= << $name,
- "content" => $content,
- "mimeType" => $mimeType);
- $params = array("token" => $token, "key" => $name);
- $body = static::multipartEncode($file, $params, $boundary);
-
- $headers[] = "User-Agent: " . self::getVersionString();
- $headers[] = "Content-Type: multipart/form-data;" .
- " boundary={$boundary}";
- $headers[] = "Content-Length: " . strlen($body);
-
- $url = "http://upload.qiniu.com";
- $ch = curl_init($url);
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
- curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_POST, 1);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
- $resp = curl_exec($ch);
- $respCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- $respType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
- $error = curl_errno($ch);
- $errno = curl_errno($ch);
- curl_close($ch);
-
- /** type of error:
- * - curl error
- * - http status error 4xx, 5xx
- * - rest api error
- */
- if ($errno > 0) {
- throw new \RuntimeException("CURL connection ($url) error: " .
- "$errno $error",
- $errno);
- }
-
- $data = json_decode($resp, true);
- if (isset($data["error"])) {
- $code = isset($data["code"]) ? $data["code"] : -1;
- throw new CloudException("{$code} {$data['error']}", $code);
- }
- return $data;
- }
-
- /**
- * Encode value for sending to LeanCloud
+ * By default LeanObject will be encoded as pointer, though
+ * `$encoder` could be provided to encode to customized type, such
+ * as full `__type` annotated json object. The $encoder must be
+ * name of instance method of object.
+ *
+ * To avoid infinite loop in the case of circular object
+ * references, previously seen objects (`$seen`) are encoded
+ * in pointer, even a customized encoder was provided.
+ *
+ * ```php
+ * $obj = new TestObject();
+ * $obj->set("owner", $user);
+ *
+ * // encode object to full JSON, with `__type` and `className`
+ * Client::encode($obj, "toFullJSON");
*
- * @param mixed $value
+ * // encode object to literal JSON, without `__type` and `className`
+ * Client::encode($obj, "toJSON");
+ * ```
+ *
+ * @param mixed $value
+ * @param string $encoder Object encoder name, e.g.: getPointer, toJSON
+ * @param array $seen Array of Object that has been traversed
* @return mixed
*/
- public static function encode($value) {
+ public static function encode($value,
+ $encoder=null,
+ $seen=array()) {
if (is_null($value) || is_scalar($value)) {
return $value;
} else if (($value instanceof \DateTime) ||
@@ -560,17 +596,23 @@ public static function encode($value) {
return array("__type" => "Date",
"iso" => self::formatDate($value));
} else if ($value instanceof LeanObject) {
- return $value->getPointer();
+ if ($encoder && $value->hasData() && !in_array($value, $seen)) {
+ $seen[] = $value;
+ return call_user_func(array($value, $encoder), $seen);
+ } else {
+ return $value->getPointer();
+ }
} else if ($value instanceof IOperation ||
$value instanceof GeoPoint ||
- $value instanceof LeanBytes ||
- $value instanceof LeanACL ||
- $value instanceof LeanFile) {
+ $value instanceof Bytes ||
+ $value instanceof ACL ||
+ $value instanceof Relation ||
+ $value instanceof File) {
return $value->encode();
} else if (is_array($value)) {
$res = array();
forEach($value as $key => $val) {
- $res[$key] = self::encode($val);
+ $res[$key] = self::encode($val, $encoder, $seen);
}
return $res;
} else {
@@ -586,12 +628,11 @@ public static function encode($value) {
* @return string
*/
public static function formatDate($date) {
- $utc = new \DateTime($date->format("c"));
+ $utc = clone $date;
$utc->setTimezone(new \DateTimezone("UTC"));
$iso = $utc->format("Y-m-d\TH:i:s.u");
- // PHP does not support sub seconds well, it will always gives 6 zero
- // digits as microseconds. We chop 3 zeros off:
- // `2015-09-18T08:06:20.000000Z` -> `2015-09-18T08:06:20.000Z`
+ // Milliseconds precision is required for server to correctly parse time,
+ // thus we have to chop off last 3 microseconds to milliseconds.
$iso = substr($iso, 0, 23) . "Z";
return $iso;
}
@@ -608,7 +649,7 @@ public static function decode($value, $key) {
return $value;
}
if ($key === 'ACL') {
- return new LeanACL($value);
+ return new ACL($value);
}
if (!isset($value["__type"])) {
$out = array();
@@ -623,21 +664,24 @@ public static function decode($value, $key) {
if ($type === "Date") {
// return time in default time zone
- return new \DateTime($value["iso"]);
+ $date = new \DateTime($value["iso"]);
+ $date->setTimezone(new \DateTimeZone(date_default_timezone_get()));
+ return $date;
}
if ($type === "Bytes") {
- return LeanBytes::createFromBase64Data($value["base64"]);
+ return Bytes::createFromBase64Data($value["base64"]);
}
if ($type === "GeoPoint") {
return new GeoPoint($value["latitude"], $value["longitude"]);
}
if ($type === "File") {
- $file = new LeanFile($value["name"]);
+ $file = new File($value["name"]);
$file->mergeAfterFetch($value);
return $file;
}
if ($type === "Pointer" || $type === "Object") {
- $obj = LeanObject::create($value["className"], $value["objectId"]);
+ $id = isset($value["objectId"]) ? $value["objectId"] : null;
+ $obj = LeanObject::create($value["className"], $id);
unset($value["__type"]);
unset($value["className"]);
if (!empty($value)) {
@@ -646,7 +690,7 @@ public static function decode($value, $key) {
return $obj;
}
if ($type === "Relation") {
- return new LeanRelation(null, $key, $value["className"]);
+ return new Relation(null, $key, $value["className"]);
}
}
@@ -683,4 +727,3 @@ public static function randomFloat($min=0, $max=1) {
}
}
-
diff --git a/src/LeanCloud/CloudException.php b/src/LeanCloud/CloudException.php
index 6b73842..2e9961d 100644
--- a/src/LeanCloud/CloudException.php
+++ b/src/LeanCloud/CloudException.php
@@ -5,12 +5,39 @@
* Exception thrown when cloud API returns error
*/
class CloudException extends \Exception {
- public function __construct($message, $code = 0) {
+
+ /**
+ * Http status returned by API
+ *
+ * @var int
+ */
+ public $status;
+
+ /**
+ * Http method request to API
+ *
+ * @var string
+ */
+ public $method;
+
+ /**
+ * Http url request to API
+ *
+ * @var string
+ */
+ public $url;
+
+ public function __construct($message, $code = 1, $status = 400,
+ $method=null, $url=null) {
parent::__construct($message, $code);
+ $this->status = $status;
+ $this->method = $method;
+ $this->url = $url;
}
public function __toString() {
- return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
+ $req = $this->method ? ": {$this->method} {$this->url}": "";
+ return __CLASS__ . ": [{$this->code}] {$this->message}{$req}\n";
}
}
diff --git a/src/LeanCloud/Engine/Cloud.php b/src/LeanCloud/Engine/Cloud.php
new file mode 100644
index 0000000..1210866
--- /dev/null
+++ b/src/LeanCloud/Engine/Cloud.php
@@ -0,0 +1,384 @@
+ "__before_save_for_",
+ "afterSave" => "__after_save_for_",
+ "beforeUpdate" => "__before_update_for_",
+ "afterUpdate" => "__after_update_for_",
+ "beforeDelete" => "__before_delete_for_",
+ "afterDelete" => "__after_delete_for_",
+ "onLogin" => "__on_login_",
+ "onVerified" => "__on_verified_",
+ "onComplete" => "__on_complete_"
+ );
+
+ public static function getKeys() {
+ return array_keys(self::$repo);
+ }
+
+ /**
+ * Get defined function or hook by internal name
+ *
+ * @param string $funcName Name of function or hook
+ * @return callable|null
+ */
+ private static function getFunc($funcName) {
+ return (isset(self::$repo[$funcName]) ? self::$repo[$funcName] : null);
+ }
+
+ /**
+ * Get internal hook name
+ *
+ * @param string $hookName
+ * @return string
+ */
+ private static function getHookPrefix($hookName) {
+ return (isset(self::$hookMap[$hookName]) ?
+ self::$hookMap[$hookName] : null);
+ }
+
+ /**
+ * Define a cloud function
+ *
+ * The function shall take two arguments: the first is an array of
+ * parameters, the second is user in the session. Example:
+ *
+ * ```php
+ * Cloud::define("sayHello", function($params, $user) {
+ * return "Hello {$params['name']}!";
+ * });
+ * ```
+ *
+ * @param string $funcName
+ * @param callable $func
+ * @see self::run
+ */
+ public static function define($funcName, $func) {
+ self::$repo[$funcName] = $func;
+ }
+
+ /**
+ * Define before save hook for a class
+ *
+ * The function shall take two arguments: the first one is class
+ * object, the second is user if available in session. If your $func
+ * throws `FunctionError`, the save will be rejected. Example:
+ *
+ * ```php
+ * Cloud::beforeSave("TestObject", function($object, $user) {
+ * $title = $object->get("title");
+ * if (strlen($title) > 140) {
+ * // Throw error and reject the save operation.
+ * throw new FunctionError("Title is too long", 1);
+ * }
+ * // else object will be saved.
+ * });
+ * ```
+ *
+ * @param string $className
+ * @param callable $func
+ * @see FunctionError
+ */
+ public static function beforeSave($className, $func) {
+ $name = self::getHookPrefix("beforeSave") . $className;
+ self::define($name, $func);
+ }
+
+ /**
+ * Define after save hook for a class
+ *
+ * The function shall take two arguments: the first one is class
+ * object, the second is user if available in session. Any error
+ * in after hook will be ignored.
+ *
+ * @param string $className
+ * @param callable $func
+ * @see FunctionError
+ */
+ public static function afterSave($className, $func) {
+ $name = self::getHookPrefix("afterSave") . $className;
+ self::define($name, $func);
+ }
+
+ /**
+ * Define before update hook for a class
+ *
+ * The function shall take two arguments: the first one is class
+ * object, the second is user if available in session. If your $func
+ * throws `FunctionError`, the update will be rejected.
+ *
+ * @param string $className
+ * @param callable $func
+ * @see FunctionError
+ */
+ public static function beforeUpdate($className, $func) {
+ $name = self::getHookPrefix("beforeUpdate") . $className;
+ self::define($name, $func);
+ }
+
+ /**
+ * Define after update hook for a class
+ *
+ * The function shall take two arguments: the first one is class
+ * object, the second is user if available in session. Any error
+ * in $func will be ignored.
+ *
+ * @param string $className
+ * @param callable $func
+ * @see FunctionError
+ */
+ public static function afterUpdate($className, $func) {
+ $name = self::getHookPrefix("afterUpdate") . $className;
+ self::define($name, $func);
+ }
+
+ /**
+ * Define before delete hook for a class
+ *
+ * The function shall take two arguments: the first one is class
+ * object, the second is user if available in session. If your $func
+ * throws `FunctionError`, the delete will be rejected.
+ *
+ * @param string $className
+ * @param callable $func
+ * @see FunctionError
+ */
+ public static function beforeDelete($className, $func) {
+ $name = self::getHookPrefix("beforeDelete") . $className;
+ self::define($name, $func);
+ }
+
+ /**
+ * Define after delete hook for a class
+ *
+ * The function shall take two arguments: the first one is class
+ * object, the second is user if available in session. Any error
+ * in $func will be ignored.
+ *
+ * @param string $className
+ * @param callable $func
+ * @see FunctionError
+ */
+ public static function afterDelete($className, $func) {
+ $name = self::getHookPrefix("afterDelete") . $className;
+ self::define($name, $func);
+ }
+
+ /**
+ * Define hook for when user tries to login
+ *
+ * The function takes one argument, the login user. A `FunctionError`
+ * could be thrown in the $func, which will reject the user for login.
+ *
+ * @param callable $func
+ * @see self::runOnLogin
+ */
+ public static function onLogin($func) {
+ self::define("__on_login__User", $func);
+ }
+
+
+ /**
+ * Define hook for when user verified sms or email
+ *
+ * The function takes one argument, the verified user.
+ *
+ * @param string $type Either "sms" or "email"
+ * @param callable $func
+ * @see self::runOnVerified
+ */
+ public static function onVerified($type, $func) {
+ self::define("__on_verified_{$type}", $func);
+ }
+
+ /**
+ * Define on complete hook for big query
+ *
+ * The function takes one argument, the big query job info as array:
+ *
+ * ```php
+ * array(
+ * "id" => "job id",
+ * "status" => "OK/ERROR",
+ * "message" => "..."
+ * );
+ * ```
+ *
+ * @param callable $func
+ * @see self::runOnInsight
+ */
+ public static function onInsight($func) {
+ self::define("__on_complete_bigquery_job", $func);
+ }
+
+ /**
+ * Run cloud function
+ *
+ * Example:
+ *
+ * ```php
+ * LeanEngine::run("sayHello", array("name" => "alice"), $user);
+ * // sayHello(array("name" => "alice"), $user);
+ * ```
+ *
+ * @param string $funcName Name of defined function
+ * @param array $params Array of parameters passed to function
+ * @param \LeanCloud\User $user Request user
+ * @param array $meta Optional parameters that will be passed to
+ * user function
+ * @return mixed
+ * @throws FunctionError
+ * @see self::define
+ */
+ public static function run($funcName, $params, $user=null, $meta=array()) {
+ $func = self::getFunc($funcName);
+ if (!$func) {
+ throw new FunctionError("Cloud function not found.", 404);
+ }
+ return call_user_func($func, $params, $user, $meta);
+ }
+
+ /**
+ * Invokes a remote cloud function
+ *
+ * Example:
+ *
+ * ```php
+ * LeanEngine::runRemote("sayHello", array("name" => "alice"));
+ * ```
+ *
+ * @param string $funcName Name of defined function
+ * @param array $params Array of parameters passed to function
+ * @param string $sessionToken run this function as the user corresponding to this session token
+ *
+ * @return array JSON decoded associated array
+ * @see self::run
+ */
+ public static function runRemote($funcName, $params, $sessionToken=null) {
+ return Client::post("/functions/{$funcName}", $params, $sessionToken);
+ }
+
+ /**
+ * Start cloud function Stand-alone mode, start to process request.
+ */
+ public static function start() {
+ Client::initialize(
+ getenv("LEANCLOUD_APP_ID"),
+ getenv("LEANCLOUD_APP_KEY"),
+ getenv("LEANCLOUD_APP_MASTER_KEY")
+ );
+
+ $engine = new LeanEngine();
+ $engine->start();
+ }
+
+ public static function stop() {
+
+ }
+
+ /**
+ * Run cloud hook
+ *
+ * Example:
+ *
+ * ```php
+ * LeanEngine::runHook("TestObject", "beforeUpdate", $object, $user);
+ * // hook($object, $user);
+ * ```
+ *
+ * @param string $className Classname
+ * @param string $hookName Hook name, e.g. beforeUpdate
+ * @param \LeanCloud\LeanObject $object The object of attached hook
+ * @param \LeanCloud\User $user Request user
+ * @param array $meta Optional parameters that will be passed to
+ * user function
+ * @return mixed
+ * @throws FunctionError
+ */
+ public static function runHook($className, $hookName, $object,
+ $user=null,
+ $meta=array()) {
+ $name = self::getHookPrefix($hookName) . $className;
+ $func = self::getFunc($name);
+ if (!$func) {
+ throw new FunctionError("Cloud hook `{$name}' not found.",
+ 404);
+ }
+ return call_user_func($func, $object, $user, $meta);
+ }
+
+ /**
+ * Run hook when a user logs in
+ *
+ * @param \LeanCloud\User $user The user object that tries to login
+ * @param array $meta Optional parameters that will be passed to
+ * user function
+ * @return mixed
+ * @throws FunctionError
+ * @see self::onLogin
+ */
+ public static function runOnLogin($user, $meta=array()) {
+ return self::runHook("_User", "onLogin", $user, $meta);
+ }
+
+ /**
+ * Run hook when user verified by Email or SMS
+ *
+ * @param string $type Either "sms" or "email", case-sensitive
+ * @param \LeanCloud\User $user The verifying user
+ * @param array $meta Optional parameters that will be passed to
+ * user function
+ * @return mixed
+ * @throws FunctionError
+ * @see self::onVerified
+ */
+ public static function runOnVerified($type, $user, $meta=array()) {
+ $name = "__on_verified_{$type}";
+ $func = self::getFunc($name);
+ if (!$func) {
+ throw new FunctionError("Cloud hook `{$name}' not found.",
+ 404);
+ }
+ return call_user_func($func, $user, $meta);
+ }
+
+ /**
+ * Run hook on big query complete
+ *
+ * @param array $params Big query job info
+ * @param array $meta Optional parameters that will be passed to
+ * user function
+ * @return mixed
+ * @throws FunctionError
+ * @see self::onInsight
+ */
+ public static function runOnInsight($params, $meta=array()) {
+ $name = "__on_complete_bigquery_job";
+ $func = self::getFunc($name);
+ if (!$func) {
+ throw new FunctionError("Cloud hook `{$name}' not found.",
+ 404);
+ }
+ return call_user_func($func, $params, $meta);
+ }
+}
diff --git a/src/LeanCloud/Engine/FunctionError.php b/src/LeanCloud/Engine/FunctionError.php
new file mode 100644
index 0000000..f0c4673
--- /dev/null
+++ b/src/LeanCloud/Engine/FunctionError.php
@@ -0,0 +1,22 @@
+status = $status;
+ }
+
+ public function __toString() {
+ return __CLASS__ . ": [{$this->code}] {$this->message}\n";
+ }
+}
diff --git a/src/LeanCloud/Engine/LaravelEngine.php b/src/LeanCloud/Engine/LaravelEngine.php
new file mode 100644
index 0000000..22e7c45
--- /dev/null
+++ b/src/LeanCloud/Engine/LaravelEngine.php
@@ -0,0 +1,54 @@
+request->header($key);
+ }
+
+ /**
+ * Get request body string
+ *
+ * @return string
+ */
+ protected function getBody() {
+ return $this->request->getContent();
+ }
+
+ /**
+ * Laravel middleware entry point
+ *
+ * @param \Illuminate\Http\Reuqest $request Laravel request
+ * @param \Closure $next Laravel closure
+ * @return mixed
+ */
+ public function handle($request, $next) {
+ $this->request = $request;
+ $this->dispatch($request->method(),
+ $request->url());
+ return $next($this->request);
+ }
+}
+
diff --git a/src/LeanCloud/Engine/LeanEngine.php b/src/LeanCloud/Engine/LeanEngine.php
new file mode 100644
index 0000000..144c828
--- /dev/null
+++ b/src/LeanCloud/Engine/LeanEngine.php
@@ -0,0 +1,623 @@
+withHeader("Content-Type",
+ "application/json; charset=utf-8;")
+ ->send($out, $status);
+ }
+
+ /**
+ * Render error and end request
+ *
+ * @param string $message Error message
+ * @param string $code Error code
+ * @param string $status Http response status code
+ */
+ private function renderError($message, $code=1, $status=400) {
+ $data = json_encode(array(
+ "code" => $code,
+ "error" => $message
+ ));
+ $this->withHeader("Content-Type", "application; charset=utf-8;")
+ ->send($data, $status);
+ }
+
+ /**
+ * Retrieve header value with multiple version of keys
+ *
+ * @param array $keys Keys in order
+ * @return mixed
+ */
+ private function retrieveHeader($keys) {
+ $val = null;
+ forEach($keys as $k) {
+ $val = $this->getHeaderLine($k);
+ if (!empty($val)) {
+ return $val;
+ }
+ }
+ return $val;
+ }
+
+ /**
+ * Extract variant headers into env
+ *
+ * PHP prepends `HTTP_` to user-defined headers, so `X-MY-VAR`
+ * would be populated as `HTTP_X_MY_VAR`. But 3rd party frameworks
+ * (e.g. Laravel) may overwrite the behavior, and populate it as
+ * cleaner `X_MY_VAR`. So we try to retrieve header value from both
+ * versions.
+ *
+ */
+ private function parseHeaders() {
+ $this->env["ORIGIN"] = $this->retrieveHeader(array(
+ "ORIGIN",
+ "HTTP_ORIGIN"
+ ));
+ $this->env["CONTENT_TYPE"] = $this->retrieveHeader(array(
+ "CONTENT_TYPE",
+ "HTTP_CONTENT_TYPE"
+ ));
+ $this->env["REMOTE_ADDR"] = $this->retrieveHeader(array(
+ "X_REAL_IP",
+ "HTTP_X_REAL_IP",
+ "X_FORWARDED_FOR",
+ "HTTP_X_FORWARDED_FOR",
+ "REMOTE_ADDR"
+ ));
+
+ $this->env["LC_ID"] = $this->retrieveHeader(array(
+ "X_LC_ID",
+ "HTTP_X_LC_ID",
+ "X_AVOSCLOUD_APPLICATION_ID",
+ "HTTP_X_AVOSCLOUD_APPLICATION_ID",
+ "X_ULURU_APPLICATION_ID",
+ "HTTP_X_ULURU_APPLICATION_ID"
+ ));
+ $this->env["LC_KEY"] = $this->retrieveHeader(array(
+ "X_LC_KEY",
+ "HTTP_X_LC_KEY",
+ "X_AVOSCLOUD_APPLICATION_KEY",
+ "HTTP_X_AVOSCLOUD_APPLICATION_KEY",
+ "X_ULURU_APPLICATION_KEY",
+ "HTTP_X_ULURU_APPLICATION_KEY"
+ ));
+ $this->env["LC_MASTER_KEY"] = $this->retrieveHeader(array(
+ "X_AVOSCLOUD_MASTER_KEY",
+ "HTTP_X_AVOSCLOUD_MASTER_KEY",
+ "X_ULURU_MASTER_KEY",
+ "HTTP_X_ULURU_MASTER_KEY"
+ ));
+ $this->env["LC_SESSION"] = $this->retrieveHeader(array(
+ "X_LC_SESSION",
+ "HTTP_X_LC_SESSION",
+ "X_AVOSCLOUD_SESSION_TOKEN",
+ "HTTP_X_AVOSCLOUD_SESSION_TOKEN",
+ "X_ULURU_SESSION_TOKEN",
+ "HTTP_X_ULURU_SESSION_TOKEN"
+ ));
+ $this->env["LC_SIGN"] = $this->retrieveHeader(array(
+ "X_LC_SIGN",
+ "HTTP_X_LC_SIGN",
+ "X_AVOSCLOUD_REQUEST_SIGN",
+ "HTTP_X_AVOSCLOUD_REQUEST_SIGN"
+ ));
+ $prod = $this->retrieveHeader(array(
+ "X_LC_PROD",
+ "HTTP_X_LC_PROD",
+ "X_AVOSCLOUD_APPLICATION_PRODUCTION",
+ "HTTP_X_AVOSCLOUD_APPLICATION_PRODUCTION",
+ "X_ULURU_APPLICATION_PRODUCTION",
+ "HTTP_X_ULURU_APPLICATION_PRODUCTION"
+ ));
+ $this->env["useProd"] = true;
+ if ($prod === 0 || $prod === false) {
+ $this->env["useProd"] = false;
+ }
+ $this->env["useMaster"] = false;
+ }
+
+ /**
+ * Parse plain text body
+ *
+ * The CORS request might be sent as POST request with text/plain
+ * header, whence the app key info is attached in the body as
+ * JSON.
+ *
+ * @param string $body
+ * @return array Decoded body array
+ */
+ private function parsePlainBody($body) {
+ $data = json_decode($body, true);
+ if (!empty($data)) {
+ $this->env["LC_ID"] = isset($data["_ApplicationId"]) ?
+ $data["_ApplicationId"] : null;
+ $this->env["LC_KEY"] = isset($data["_ApplicationKey"]) ?
+ $data["_ApplicationKey"] : null;
+ $this->env["LC_MASTER_KEY"] = isset($data["_MasterKey"]) ?
+ $data["_MasterKey"] : null;
+ $this->env["LC_SESSION"] = isset($data["_SessionToken"]) ?
+ $data["_SessionToken"] : null;
+ $this->env["LC_SIGN"] = null;
+ $this->env["useProd"] = isset($data["_ApplicationProduction"]) ?
+ (true && $data["_ApplicationProduction"]) :
+ true;
+ $this->env["useMaster"] = false;
+ // remove internal fields set by API
+ // note we need to preserve `__type` field for object decoding
+ // see #61
+ forEach($data as $key) {
+ if ($key[0] === "_" && $key[1] !== "_") {
+ unset($data[$key]);
+ }
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * Authenticate request by app ID and key
+ */
+ private function authRequest() {
+ $appId = $this->env["LC_ID"];
+ $sign = $this->env["LC_SIGN"];
+ if ($sign && Client::verifySign($appId, $sign)) {
+ if (strpos($sign, "master") !== false) {
+ $this->env["useMaster"] = true;
+ }
+ return true;
+ }
+
+ $appKey = $this->env["LC_KEY"];
+ if ($appKey && Client::verifyKey($appId, $appKey)) {
+ if (strpos($appKey, "master") !== false) {
+ $this->env["useMaster"] = true;
+ }
+ return true;
+ }
+
+ $masterKey = $this->env["LC_MASTER_KEY"];
+ $key = "{$masterKey}, master";
+ if ($masterKey && Client::verifyKey($appId, $key)) {
+ $this->env["useMaster"] = true;
+ return true;
+ }
+
+ $this->renderError("Unauthorized", 401, 401);
+ }
+
+ private function verifyHookSign($hookName, $sign){
+ if (Client::verifyHookSign($hookName, $sign)) return true;
+ error_log("Invalid hook sign for {$hookName}");
+ $this->renderError("Unauthorized", 142, 401);
+ }
+
+ /**
+ * Set user session if sessionToken present
+ */
+ private function processSession() {
+ $token = $this->env["LC_SESSION"];
+ if ($token) {
+ User::become($token);
+ }
+ }
+
+ /**
+ * Dispatch request
+ *
+ * Following routes are processed and returned by LeanEngine:
+ *
+ * ```
+ * OPTIONS {1,1.1}/{functions,call}.*
+ * * __engine/1/ping
+ * * {1,1.1}/{functions,call}/_ops/metadatas
+ * * {1,1.1}/{functions,call}/onVerified/{sms,email}
+ * * {1,1.1}/{functions,call}/BigQuery/onComplete
+ * * {1,1.1}/{functions,call}/{className}/{hookName}
+ * * {1,1.1}/{functions,call}/{funcName}
+ * ```
+ *
+ * others may be added in future.
+ *
+ * @param string $method Request method
+ * @param string $url Request url
+ */
+ private function __dispatch($method, $url) {
+ if (static::$useHttpsRedirect) {
+ $this->httpsRedirect();
+ }
+ $path = parse_url($url, PHP_URL_PATH);
+ $path = rtrim($path, "/");
+ if (strpos($path, "/__engine/1/ping") === 0) {
+ $this->renderJSON(array(
+ "runtime" => "php-" . phpversion(),
+ "version" => Client::VERSION
+ ));
+ }
+
+ $this->parseHeaders();
+
+ $pathParts = array(); // matched path components
+ if (preg_match("/^\/(1|1\.1)\/(functions|call)(.*)/",
+ $path,
+ $pathParts) === 1) {
+ $pathParts["version"] = $pathParts[1]; // 1 or 1.1
+ $pathParts["endpoint"] = $pathParts[2]; // functions or call
+ $pathParts["extra"] = $pathParts[3]; // extra part after endpoint
+ $origin = $this->env["ORIGIN"];
+ $this->withHeader("Access-Control-Allow-Origin",
+ $origin ? $origin : "*");
+ if ($method == "OPTIONS") {
+ $this->withHeader("Access-Control-Max-Age", 86400)
+ ->withHeader("Access-Control-Allow-Methods",
+ "PUT, GET, POST, DELETE, OPTIONS")
+ ->withHeader("Access-Control-Allow-Headers",
+ implode(", ", self::$allowedHeaders))
+ ->withHeader("Content-Length", 0)
+ ->renderJSON();
+ }
+
+ $body = $this->getBody();
+ if (preg_match("/text\/plain/", $this->env["CONTENT_TYPE"])) {
+ // To work around with CORS restriction, some requests are
+ // submit as text/palin body, where headers are attached
+ // in the body.
+ $json = $this->parsePlainBody($body);
+ } else {
+ $json = json_decode($body, true);
+ }
+
+ $this->authRequest();
+ $this->processSession();
+ if (strpos($pathParts["extra"], "/_ops/metadatas") === 0) {
+ if ($this->env["useMaster"]) {
+ $this->renderJSON(array("result" => Cloud::getKeys()));
+ } else {
+ $this->renderError("Unauthorized.", 401, 401);
+ }
+ }
+
+ // extract func params from path:
+ // /1.1/call/{0}/{1}
+ $funcParams = explode("/", ltrim($pathParts["extra"], "/"));
+ if (count($funcParams) == 1) {
+ // {1,1.1}/functions/{funcName}
+ $this->dispatchFunc($funcParams[0], $json,
+ $pathParts["endpoint"] === "call");
+ } else {
+ if ($funcParams[0] == "onVerified") {
+ // {1,1.1}/functions/onVerified/sms
+ $this->dispatchOnVerified($funcParams[1], $json);
+ } else if ($funcParams[0] == "_User" &&
+ $funcParams[1] == "onLogin") {
+ // {1,1.1}/functions/_User/onLogin
+ $this->dispatchOnLogin($json);
+ } else if ($funcParams[0] == "BigQuery" ||
+ $funcParams[0] == "Insight") {
+ // {1,1.1}/functions/Insight/onComplete
+ $this->dispatchOnInsight($json);
+ } else if (count($funcParams) == 2) {
+ // {1,1.1}/functions/{className}/beforeSave
+ $this->dispatchHook($funcParams[0], $funcParams[1], $json);
+ }
+ }
+ }
+ }
+
+ /**
+ * Dispatch function and render result
+ *
+ * @param string $funcName Function name
+ * @param array $body JSON decoded body params
+ * @param bool $decodeObj
+ */
+ private function dispatchFunc($funcName, $body, $decodeObj=false) {
+ // verify hook sign for RTM hooks
+ if (in_array($funcName, array(
+ '_messageReceived', '_receiversOffline', '_messageSent', '_messageUpdate',
+ '_conversationStart', '_conversationStarted',
+ '_conversationAdd', '_conversationAdded', '_conversationRemove', '_conversationRemoved', '_conversationUpdate',
+ '_clientOnline', '_clientOffline', '_rtmClientSign'
+ ))) {
+ static::verifyHookSign($funcName, $body["__sign"]);
+ }
+
+ $params = $body;
+ if ($decodeObj) {
+ $params = Client::decode($body, null);
+ }
+
+ $meta["remoteAddress"] = $this->env["REMOTE_ADDR"];
+ $result = Cloud::run($funcName,
+ $params,
+ User::getCurrentUser(),
+ $meta);
+ if ($decodeObj) {
+ // Encode object to full, type-annotated JSON
+ $out = Client::encode($result, "toFullJSON");
+ } else {
+ // Encode object to type-less literal JSON
+ $out = Client::encode($result, "toJSON");
+ }
+ $this->renderJSON(array("result" => $out));
+ }
+
+ /**
+ * Dispatch class hook and render result
+ *
+ * @param string $className
+ * @param string $hookName
+ * @param array $body JSON decoded body params
+ */
+ private function dispatchHook($className, $hookName, $body) {
+ $verified = false;
+ if (strpos($hookName, "before") === 0) {
+ $this->verifyHookSign("__before_for_{$className}",
+ $body["object"]["__before"]);
+ } else {
+ $this->verifyHookSign("__after_for_{$className}",
+ $body["object"]["__after"]);
+ }
+
+ $json = $body["object"];
+ $json["__type"] = "Object";
+ $json["className"] = $className;
+ $obj = Client::decode($json, null);
+
+ // set hook marks to prevent infinite loop. For example if user
+ // invokes `$obj->save` in an afterSave hook, API will not again
+ // invoke afterSave if we set hook marks.
+ if (strpos($hookName, "before") === 0) {
+ if (isset($json["__before"])) {
+ $obj->set("__before", $json["__before"]);
+ } else {
+ $obj->disableBeforeHook();
+ }
+ } else {
+ if (isset($json["__after"])) {
+ $obj->set("__after", $json["__after"]);
+ } else {
+ $obj->disableAfterHook();
+ }
+ }
+
+ // in beforeUpdate hook, attach updatedKeys to object so user
+ // can detect changed keys in hook.
+ if (isset($json["_updatedKeys"])) {
+ $obj->updatedKeys = $json["_updatedKeys"];
+ }
+
+ $meta["remoteAddress"] = $this->env["REMOTE_ADDR"];
+ $result = Cloud::runHook($className,
+ $hookName,
+ $obj,
+ User::getCurrentUser(),
+ $meta);
+ if ($hookName == "beforeDelete") {
+ $this->renderJSON(array());
+ } else if (strpos($hookName, "after") === 0) {
+ $this->renderJSON(array("result" => "ok"));
+ } else {
+ // Encode result object to type-less literal JSON
+ $this->renderJSON($obj->toJSON());
+ }
+ }
+
+ /**
+ * Dispatch onVerified hook
+ *
+ * @param string $type Verify type: email or sms
+ * @param array $body JSON decoded body params
+ */
+ private function dispatchOnVerified($type, $body) {
+ $this->verifyHookSign("__on_verified_{$type}",
+ $body["object"]["__sign"]);
+
+ $userObj = Client::decode($body["object"], null);
+ User::saveCurrentUser($userObj);
+ $meta["remoteAddress"] = $this->env["REMOTE_ADDR"];
+ Cloud::runOnVerified($type, $userObj, $meta);
+ $this->renderJSON(array("result" => "ok"));
+ }
+
+ /**
+ * Dispatch onLogin hook
+ *
+ * @param array $body JSON decoded body params
+ */
+ private function dispatchOnLogin($body) {
+ $this->verifyHookSign("__on_login__User",
+ $body["object"]["__sign"]);
+
+ $userObj = Client::decode($body["object"], null);
+ $meta["remoteAddress"] = $this->env["REMOTE_ADDR"];
+ Cloud::runOnLogin($userObj, $meta);
+ $this->renderJSON(array("result" => "ok"));
+ }
+
+ /**
+ * Dispatch onInsight hook
+ *
+ * @param array $body JSON decoded body params
+ */
+ private function dispatchOnInsight($body) {
+ $this->verifyHookSign("__on_complete_bigquery_job",
+ $body["__sign"]);
+
+ $meta["remoteAddress"] = $this->env["REMOTE_ADDR"];
+ Cloud::runOnInsight($body, $meta);
+ $this->renderJSON(array("result" => "ok"));
+ }
+
+ /**
+ * Dispatch LeanEngine functions.
+ *
+ * @param string $method Request method
+ * @param string $url Request url
+ */
+ protected function dispatch($method, $url) {
+ try {
+ $this->__dispatch($method, $url);
+ } catch (FunctionError $ex) {
+ $status = (int) $ex->status;
+ if ( $status >= 500) {
+ error_log($ex);
+ error_log($ex->getTraceAsString());
+ }
+ $this->renderError("{$ex->getMessage()}", $ex->getCode(), $ex->status);
+ } catch (CloudException $ex) {
+ error_log($ex);
+ error_log($ex->getTraceAsString());
+ $this->renderError("{$ex->getMessage()}", $ex->getCode(), $ex->status);
+ } catch (\Exception $ex) {
+ error_log($ex);
+ error_log($ex->getTraceAsString());
+ $this->renderError($ex->getMessage(),
+ $ex->getCode() ? $ex->getCode() : 1,
+ // unhandled internal exception
+ 500);
+ }
+ }
+
+ /**
+ * Start engine and process request
+ */
+ public function start() {
+ $this->dispatch($_SERVER["REQUEST_METHOD"],
+ $_SERVER["REQUEST_URI"]);
+ }
+
+ /**
+ * Redirect to http request to https
+ */
+ private function httpsRedirect() {
+ $reqProto = $this->getHeaderLine("HTTP_X_FORWARDED_PROTO");
+ if ($reqProto === "http" &&
+ in_array(getenv("LEANCLOUD_APP_ENV"), array("production", "stage"))) {
+ $url = "https://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
+ $this->redirect($url);
+ }
+ }
+
+ /**
+ * Enable https redirect
+ */
+ public static function enableHttpsRedirect() {
+ static::$useHttpsRedirect = true;
+ }
+
+}
+
diff --git a/src/LeanCloud/Engine/SlimEngine.php b/src/LeanCloud/Engine/SlimEngine.php
new file mode 100644
index 0000000..a118d96
--- /dev/null
+++ b/src/LeanCloud/Engine/SlimEngine.php
@@ -0,0 +1,66 @@
+add(new SlimEngine());
+ * ```
+ *
+ * @link http://www.slimframework.com/docs/concepts/middleware.html
+ */
+class SlimEngine extends LeanEngine {
+
+ /**
+ * Get request header value
+ *
+ * @param string $key Header key
+ * @return string
+ */
+ protected function getHeaderLine($key) {
+ return $this->request->getHeaderLine($key);
+ }
+
+ /**
+ * Get request body string
+ *
+ * @return string
+ */
+ protected function getBody() {
+ return $this->request->getBody()->getContents();
+ }
+
+ /*
+ * Ideally we would like to write to Slim response and send
+ * the response to client. But we did not yet find a good way
+ * to end the request as Slime middleware. As a work around,
+ * we fallback to PHP native functions to do that. Pull request
+ * is welcome.
+ *
+ * @see LeanEngine::withHeader LeanEngine::send
+ */
+ // protected function withHeader($key, $val) {}
+ // protected function send($key, $val) {}
+
+ /**
+ * Slim middleware entry point
+ *
+ * @param \Psr\Http\Message\ServerRequestInterface $request PSR7 request
+ * @param \Psr\Http\Message\ResponseInterface $response PSR7 response
+ * @param callable $next Next middleware
+ * @return \Psr\Http\Message\ResponseInterface
+ */
+ public function __invoke($request, $response, $next) {
+ $this->request = $request;
+ $this->response = $response;
+ $this->dispatch($request->getMethod(),
+ $request->getUri());
+ return $next($this->request, $this->response);
+ }
+}
+
diff --git a/src/LeanCloud/LeanFile.php b/src/LeanCloud/File.php
similarity index 78%
rename from src/LeanCloud/LeanFile.php
rename to src/LeanCloud/File.php
index f9af878..1c8d4a4 100644
--- a/src/LeanCloud/LeanFile.php
+++ b/src/LeanCloud/File.php
@@ -1,16 +1,16 @@
_data["name"] = $name;
+ $this->_data["key"] = null;
$this->_source = $data;
if (!$mimeType) {
@@ -58,8 +59,8 @@ public function __construct($name, $data=null, $mimeType=null) {
$this->_data["mime_type"] = $mimeType;
$this->_metaData["owner"] = "unknown";
- if (LeanUser::$currentUser) {
- $this->_metaData["owner"] = LeanUser::$currentUser->getObjectId();
+ if (User::$currentUser) {
+ $this->_metaData["owner"] = User::$currentUser->getObjectId();
}
if ($this->_source) {
$this->_metaData["size"] = strlen($this->_source);
@@ -72,10 +73,10 @@ public function __construct($name, $data=null, $mimeType=null) {
* @param string $name File base name
* @param string $url Public URL
* @param string $mimeType (optional)
- * @return LeanFile
+ * @return File
*/
public static function createWithUrl($name, $url, $mimeType=null) {
- $file = new LeanFile($name, null, $mimeType);
+ $file = new File($name, null, $mimeType);
$file->_data["url"] = $url;
$file->_metaData["__source"] = "external";
return $file;
@@ -87,10 +88,10 @@ public static function createWithUrl($name, $url, $mimeType=null) {
* @param string $name File name
* @param string $data File content
* @param string $mimeType
- * @return LeanFile
+ * @return File
*/
public static function createWithData($name, $data, $mimeType=null) {
- $file = new LeanFile($name, $data, $mimeType);
+ $file = new File($name, $data, $mimeType);
return $file;
}
@@ -98,16 +99,20 @@ public static function createWithData($name, $data, $mimeType=null) {
* Create file from disk
*
* @param string $filepath Absolute file path
- * @param string $mimeType
- * @return LeanFile
+ * @param string $mimeType E.g. "image/png"
+ * @param string $name Name of file
+ * @return File
* @throws RuntimeException
*/
- public static function createWithLocalFile($filepath, $mimeType=null) {
+ public static function createWithLocalFile($filepath, $mimeType=null, $name=null) {
$content = file_get_contents($filepath);
if ($content === false) {
throw new \RuntimeException("Read file error at $filepath");
}
- return static::createWithData(basename($filepath), $content, $mimeType);
+ if (!$name) {
+ $name = basename($filepath);
+ }
+ return static::createWithData($name, $content, $mimeType);
}
/**
@@ -132,6 +137,24 @@ public function getName() {
return $this->get("name");
}
+ /**
+ * Get key of file
+ *
+ * @return string
+ */
+ public function getKey() {
+ return $this->get("key");
+ }
+ /**
+ * Set key of file
+ *
+ * @return self
+ */
+ public function setKey($val) {
+ $this->_data["key"] = $val;
+ return $this;
+ }
+
/**
* Get objectId of file
*
@@ -254,19 +277,6 @@ public function getMeta($key=null) {
return null;
}
- /**
- * Generate pseudo-uuid key for filename
- *
- * @return string
- */
- private static function genFileKey() {
- $octets = array_map(function() {
- $num = floor((1 + LeanClient::randomFloat()) * 0x10000);
- return substr(dechex($num), 1);
- }, range(0, 4));
- return implode("", $octets);
- }
-
/**
* Is the file exteranl
*
@@ -293,11 +303,11 @@ private function _mergeData($data, $meta=array()) {
}
forEach($data as $key => $val) {
- $this->_data[$key] = LeanClient::decode($val, $key);
+ $this->_data[$key] = Client::decode($val, $key);
}
forEach($meta as $key => $val) {
- $this->_metaData[$key] = LeanClient::decode($val, $key);
+ $this->_metaData[$key] = Client::decode($val, $key);
}
}
@@ -362,19 +372,46 @@ public function save() {
if ($this->isExternal()) {
$data["url"] = $this->getUrl();
- $resp = LeanClient::post("/files/{$this->getName()}", $data);
+ $resp = Client::post("/files", $data);
$this->mergeAfterSave($resp);
} else {
- $key = static::genFileKey();
- $key .= "." . pathinfo($this->getName(), PATHINFO_EXTENSION);
- $data["key"] = $key;
- $resp = LeanClient::post("/qiniu", $data);
- $token = $resp["token"];
- unset($resp["token"]);
- $this->mergeAfterSave($resp);
+ $key = $this->getKey();
+ if (isset($key)) {
+ $data["key"] = $key;
+ }
+ $data["__type"] = "File";
+ $resp = Client::post("/fileTokens", $data);
+ if (!isset($resp["token"])) {
+ // adapt for S3, when there is no token
+ $resp["token"] = null;
+ }
+ $key = $resp["key"];
+ $this->setKey($key);
+
+ $callbackParams = array("token" => $resp["token"]);
+ try {
+ $uploader = SimpleUploader::createUploader($resp["provider"]);
+ $uploader->initialize($resp["upload_url"], $resp["token"]);
+ $uploader->upload($this->_source, $this->getMimeType(), $key);
+ $callbackParams["result"] = true;
+ } catch (\Exception $ex) {
+ $callbackParams["result"] = false;
+ throw $ex;
+ } finally {
+ try {
+ Client::post("/fileCallback", $callbackParams);
+ } catch (\Exception $ex) {
+ error_log("Request /fileCallback failed.");
+ }
+ }
- LeanClient::uploadToQiniu($token, $this->_source, $key,
- $this->getMimeType());
+ forEach(array("upload_url", "token") as $k) {
+ if (isset($resp[$k])) {
+ unset($resp[$k]);
+ }
+ }
+
+ $this->mergeAfterSave($resp);
}
}
@@ -384,11 +421,11 @@ public function save() {
* Note it fetches descriptive data from LeanCloud, but not file content.
* The content should be fetched from file URL.
*
- * @return LeanFile
+ * @return File
*/
public static function fetch($objectId) {
- $file = new LeanFile("");
- $resp = LeanClient::get("/files/{$objectId}");
+ $file = new File("");
+ $resp = Client::get("/files/{$objectId}");
$file->mergeAfterFetch($resp);
return $file;
}
@@ -402,7 +439,7 @@ public function destroy() {
if (!$this->getObjectId()) {
return false;
}
- LeanClient::delete("/files/{$this->getObjectId()}");
+ Client::delete("/files/{$this->getObjectId()}");
}
/**
@@ -422,4 +459,3 @@ public function encode() {
);
}
}
-
diff --git a/src/LeanCloud/GeoPoint.php b/src/LeanCloud/GeoPoint.php
index 5a18fbc..78bae64 100644
--- a/src/LeanCloud/GeoPoint.php
+++ b/src/LeanCloud/GeoPoint.php
@@ -6,10 +6,10 @@
* GeoPoint type representation
*
* It represents a geographic point, and supports computing geo
- * distance from point to point. It can also be used in LeanQuery to
+ * distance from point to point. It can also be used in Query to
* build proximity-based queries.
*
- * @see LeanQuery
+ * @see Query
*/
class GeoPoint {
/**
diff --git a/src/LeanCloud/LeanObject.php b/src/LeanCloud/LeanObject.php
index 219d688..97ae73f 100644
--- a/src/LeanCloud/LeanObject.php
+++ b/src/LeanCloud/LeanObject.php
@@ -1,7 +1,7 @@
_className;
}
+ public function disableBeforeHook() {
+ $this->_set("__before",
+ Client::signHook("__before_for_{$this->getClassName()}",
+ round(microtime(true) * 1000)));
+ }
+
+ public function disableAfterHook() {
+ $this->_set("__after",
+ Client::signHook("__after_for_{$this->getClassName()}",
+ round(microtime(true) * 1000)));
+ }
+
/**
* Pointer representation of object
*
@@ -139,7 +167,7 @@ public function getClassName() {
*/
public function getPointer() {
if (!$this->getObjectId()) {
- throw new \RuntimeException("Object without ID cannot " .
+ throw new \RuntimeException("LeanObject without ID cannot " .
"be serialized.");
}
return array(
@@ -149,6 +177,42 @@ public function getPointer() {
);
}
+ /**
+ * Recursively encode object and its data to JSON
+ *
+ * Top level object are encoded to literal JSON, with __type and
+ * className stripped out.
+ *
+ * @return array
+ * @see self::toFullJSON
+ */
+ public function toJSON() {
+ $out = $this->toFullJSON();
+ unset($out["__type"]);
+ unset($out["className"]);
+ return $out;
+ }
+
+ /**
+ * Recursively encode object and its data to full JSON
+ *
+ * Recursively encode object and its (snapshot) data to full JSON, the
+ * `__type` and `className` will be included in the attributes.
+ *
+ * @param array $seen Objects that have been traversed
+ * @return array
+ * @see self::toJSON
+ */
+ public function toFullJSON($seen=array()) {
+ $out = array();
+ forEach($this->_data as $key => $val) {
+ $out[$key] = Client::encode($val, "toFullJSON", $seen);
+ }
+ $out["__type"] = "Object";
+ $out["className"] = $this->getClassName();
+ return $out;
+ }
+
/**
* Get objectId of object
*
@@ -174,6 +238,18 @@ public function getUpdatedAt() {
return $this->get("updatedAt");
}
+ private function _set($key, $val) {
+ if ($key === "ACL" &&
+ !($val instanceof ACL)) {
+ throw new RuntimeException("Invalid ACL.");
+ }
+ if (!($val instanceof IOperation)) {
+ $val = new SetOperation($key, $val);
+ }
+ $this->_applyOperation($val);
+ return $this;
+ }
+
/**
* Set field value by key
*
@@ -183,30 +259,26 @@ public function getUpdatedAt() {
* @throws RuntimeException
*/
public function set($key, $val) {
- if (in_array($key, array("objectId", "createdAt", "updatedAt"))) {
+ if (in_array($key, self::$PRESERVED_KEYS)) {
throw new \RuntimeException("Preserved field could not be set.");
}
- if (!($val instanceof IOperation)) {
- $val = new SetOperation($key, $val);
- }
- $this->_applyOperation($val);
- return $this;
+ return $this->_set($key, $val);
}
/**
* Set ACL for object
*
- * @param LeanACL $acl
+ * @param ACL $acl
* @return self
*/
- public function setACL(LeanACL $acl) {
- return $this->set("ACL", $acl);
+ public function setACL(ACL $acl) {
+ return $this->_set("ACL", $acl);
}
/**
* Get ACL for object
*
- * @return null|LeanACL
+ * @return null|ACL
*/
public function getACL() {
return $this->get("ACL");
@@ -234,7 +306,7 @@ public function get($key) {
return null;
}
$val = $this->_data[$key];
- if ($val instanceof LeanRelation) {
+ if ($val instanceof Relation) {
return $this->getRelation($key);
}
return $this->_data[$key];
@@ -328,6 +400,16 @@ public function removeIn($key, $val) {
return $this;
}
+ /**
+ * If object has data attributes.
+ *
+ * @return bool
+ */
+ public function hasData() {
+ $keys = array_keys($this->_data);
+ return $keys !== array("objectId");
+ }
+
/**
* If there are unsaved operations.
*
@@ -344,16 +426,20 @@ public function isDirty() {
* @return array
*/
private function getSaveData() {
- return LeanClient::encode($this->_operationSet);
+ return Client::encode($this->_operationSet);
}
/**
* Save object and its children objects and files
*
+ * @param SaveOption $option
* @throws CloudException
*/
- public function save() {
+ public function save($option=null) {
if (!$this->isDirty()) {return;}
+ if ($option) {
+ $this->_saveOption = $option;
+ }
try {
$result = self::saveAll(array($this));
} catch (BatchRequestError $batchRequestError) {
@@ -380,7 +466,7 @@ private function _mergeData($data) {
}
forEach($data as $key => $val) {
- $this->_data[$key] = LeanClient::decode($val, $key);
+ $this->_data[$key] = Client::decode($val, $key);
}
}
@@ -459,8 +545,8 @@ public function fetchAll($objects) {
$objects[] = $obj;
}
- $sessionToken = LeanUser::getCurrentSessionToken();
- $response = LeanClient::batch($requests, $sessionToken);
+ $sessionToken = User::getCurrentSessionToken();
+ $response = Client::batch($requests, $sessionToken);
$batchRequestError = new BatchRequestError();
forEach($objects as $i => $obj) {
@@ -502,30 +588,30 @@ public function destroy() {
/**
* Return query object based on the object class
*
- * @return LeanQuery
+ * @return Query
*/
public function getQuery() {
- return new LeanQuery($this->getClassName());
+ return new Query($this->getClassName());
}
/**
* Get (or build) relation on field
*
* @param string $key Field key
- * @return LeanRelation
+ * @return Relation
* @throws RuntimeException
*/
public function getRelation($key) {
$val = isset($this->_data[$key]) ? $this->_data[$key] : null;
if ($val) {
- if ($val instanceof LeanRelation) {
+ if ($val instanceof Relation) {
$val->setParentAndKey($this, $key);
return $val;
} else {
throw new \RuntimeException("Field {$key} is not relation.");
}
}
- return new LeanRelation($this, $key);
+ return new Relation($this, $key);
}
/**
@@ -571,7 +657,7 @@ public function findUnsavedChildren() {
static::traverse($this->_data, $seen,
function($val) use (&$unsavedChildren) {
if (($val instanceof LeanObject) ||
- ($val instanceof LeanFile)) {
+ ($val instanceof File)) {
if ($val->isDirty()) {
$unsavedChildren[] = $val;
}
@@ -598,7 +684,7 @@ public static function saveAll($objects) {
$children = array(); // Array of unsaved objects excluding files
forEach($unsavedChildren as $obj) {
- if ($obj instanceof LeanFile) {
+ if ($obj instanceof File) {
$obj->save();
} else if ($obj instanceof LeanObject) {
if (!in_array($obj, $children)) {
@@ -652,12 +738,15 @@ private static function batchSave($objects, $batchSize=20) {
$req["method"] = "POST";
$req["path"] = "{$path}/{$obj->getClassName()}";
}
+ if ($obj->_saveOption) {
+ $req["params"] = $obj->_saveOption->encode();
+ }
$requests[] = $req;
$objects[] = $obj;
}
- $sessionToken = LeanUser::getCurrentSessionToken();
- $response = LeanClient::batch($requests, $sessionToken);
+ $sessionToken = User::getCurrentSessionToken();
+ $response = Client::batch($requests, $sessionToken);
forEach($objects as $i => $obj) {
if (isset($response[$i]["success"])) {
@@ -672,7 +761,7 @@ private static function batchSave($objects, $batchSize=20) {
/**
* Delete objects in batch
*
- * @param array $objects Array of LeanObjects to destroy
+ * @param array $objects Array of Objects to destroy
*/
public static function destroyAll($objects) {
$batch = array();
@@ -696,8 +785,7 @@ public static function destroyAll($objects) {
$objects[] = $obj;
}
- $sessionToken = LeanUser::getCurrentSessionToken();
- $response = LeanClient::batch($requests, $sessionToken);
+ $sessionToken = User::getCurrentSessionToken();
+ $response = Client::batch($requests, $sessionToken);
}
}
-
diff --git a/src/LeanCloud/Object.php b/src/LeanCloud/Object.php
new file mode 100644
index 0000000..6fd931d
--- /dev/null
+++ b/src/LeanCloud/Object.php
@@ -0,0 +1,15 @@
+= 70200) {
+ throw new \RuntimeException("'Object` was reserved by PHP 7.2, use 'LeanObject' instead, see https://url.leanapp.cn/php72-object-deprecated");
+} else {
+ $filename = sys_get_temp_dir() . "/php72-object-deprecated";
+
+ if (!file_exists($filename)) {
+ touch($filename);
+ error_log("Warning: 'Object' was deprecated, use 'LeanObject' instead, see https://url.leanapp.cn/php72-object-deprecated");
+ }
+
+ class_alias('\LeanCloud\LeanObject', '\LeanCloud\Object');
+}
diff --git a/src/LeanCloud/Operation/ArrayOperation.php b/src/LeanCloud/Operation/ArrayOperation.php
index a81e2f2..02875c4 100644
--- a/src/LeanCloud/Operation/ArrayOperation.php
+++ b/src/LeanCloud/Operation/ArrayOperation.php
@@ -1,7 +1,8 @@
$this->getOpType(),
- "objects" => LeanClient::encode($this->value),
+ "objects" => Client::encode($this->value),
);
}
diff --git a/src/LeanCloud/Operation/RelationOperation.php b/src/LeanCloud/Operation/RelationOperation.php
index f50a3b6..b1bbdf8 100644
--- a/src/LeanCloud/Operation/RelationOperation.php
+++ b/src/LeanCloud/Operation/RelationOperation.php
@@ -1,12 +1,12 @@
getClassName() !== $this->targetClassName) {
- throw new \RuntimeException("Object type incompatible" .
+ throw new \RuntimeException("LeanObject type incompatible" .
" with relation.");
}
if (isset($this->objects_to_remove[$obj->getObjectID()])) {
@@ -129,7 +129,7 @@ private function add($objects) {
/**
* Remove object(s) from relation
*
- * @param array $objects Object(s) to remove
+ * @param array $objects LeanObject(s) to remove
*/
private function remove($objects) {
if (empty($objects)) { return; }
@@ -142,7 +142,7 @@ private function remove($objects) {
" from relation.");
}
if ($obj->getClassName() !== $this->targetClassName) {
- throw new \RuntimeException("Object type incompatible" .
+ throw new \RuntimeException("LeanObject type incompatible" .
" with relation.");
}
if (isset($this->objects_to_add[$obj->getObjectID()])) {
@@ -155,17 +155,17 @@ private function remove($objects) {
/**
* Apply the operation on previous relation
*
- * @param LeanRelation $relation Previous relation
+ * @param Relation $relation Previous relation
* @param LeanObject $object Parent of relation
- * @return LeanRelation
+ * @return Relation
* @throws RuntimeException
*/
public function applyOn($relation, $object=null) {
if (!$relation) {
- return new LeanRelation($object, $this->getKey(),
+ return new Relation($object, $this->getKey(),
$this->getTargetClassName());
}
- if (!($relation instanceof LeanRelation)) {
+ if (!($relation instanceof Relation)) {
throw new \RuntimeException("Operation incompatible with " .
"previous value.");
}
diff --git a/src/LeanCloud/Operation/SetOperation.php b/src/LeanCloud/Operation/SetOperation.php
index 51911d3..fa1aeb0 100644
--- a/src/LeanCloud/Operation/SetOperation.php
+++ b/src/LeanCloud/Operation/SetOperation.php
@@ -1,7 +1,7 @@
value);
+ return Client::encode($this->value);
}
/**
diff --git a/src/LeanCloud/LeanPush.php b/src/LeanCloud/Push.php
similarity index 77%
rename from src/LeanCloud/LeanPush.php
rename to src/LeanCloud/Push.php
index f97957e..fddf45d 100644
--- a/src/LeanCloud/LeanPush.php
+++ b/src/LeanCloud/Push.php
@@ -5,7 +5,7 @@
/**
* Send Push notification to mobile devices
*/
-class LeanPush {
+class Push {
/**
* Notification data
*
@@ -30,6 +30,7 @@ class LeanPush {
public function __construct($data=array(), $options=array()) {
$this->data = $data;
$this->options = $options;
+ $this->options["prod"] = Client::$isProduction ? "prod": "dev";
}
/**
@@ -86,11 +87,11 @@ public function setChannels($channels) {
*
* The query must be over _Installation table.
*
- * @param LeanQuery $query A query over _Installation
+ * @param Query $query A query over _Installation
* @return self
* @see self::setOption()
*/
- public function setWhere(LeanQuery $query) {
+ public function setWhere(Query $query) {
if ($query->getClassName() != "_Installation") {
throw new \RuntimeException("Query must be over " .
"_Installation table.");
@@ -137,6 +138,18 @@ public function setExpirationTime(\DateTime $time) {
return $this->setOption("expiration_time", $time);
}
+ /**
+ * Enable smooth push for message
+ *
+ * @param int $flowControl clients to push per second,
+ * a value <1000 is equivalent to 1000.
+ * @return self
+ * @see self::setOption()
+ */
+ public function setFlowControl($flowControl) {
+ return $this->setOption("flow_control", $flowControl);
+ }
+
/**
* Encode to JSON representation
*
@@ -145,6 +158,16 @@ public function setExpirationTime(\DateTime $time) {
public function encode() {
$out = $this->options;
$out["data"] = $this->data;
+ $expire = isset($this->options["expiration_time"]) ? $this->options["expiration_time"] : null;
+ if (($expire instanceof \DateTime) ||
+ ($expire instanceof \DateTimeImmutable)) {
+ $out["expiration_time"] = Client::formatDate($expire);
+ }
+ $pushTime = isset($this->options["push_time"]) ? $this->options["push_time"] : null;
+ if (($pushTime instanceof \DateTime) ||
+ ($pushTime instanceof \DateTimeImmutable)){
+ $out["push_time"] = Client::formatDate($pushTime);
+ }
if (isset($this->options["where"])) {
$query = $this->options["where"]->encode();
$out["where"] = json_decode($query["where"], true);
@@ -159,7 +182,7 @@ public function encode() {
*/
public function send() {
$out = $this->encode();
- $resp = LeanClient::post("/push", $out);
+ $resp = Client::post("/push", $out);
return $resp;
}
-}
\ No newline at end of file
+}
diff --git a/src/LeanCloud/LeanQuery.php b/src/LeanCloud/Query.php
similarity index 95%
rename from src/LeanCloud/LeanQuery.php
rename to src/LeanCloud/Query.php
index 337f3a7..e558dd8 100644
--- a/src/LeanCloud/LeanQuery.php
+++ b/src/LeanCloud/Query.php
@@ -1,13 +1,13 @@
where[$key][$op] = LeanClient::encode($val);
+ $this->where[$key][$op] = Client::encode($val);
}
/**
@@ -112,7 +112,7 @@ private function _addCondition($key, $op, $val) {
* @return self
*/
public function equalTo($key, $val) {
- $this->where[$key] = LeanClient::encode($val);
+ $this->where[$key] = Client::encode($val);
return $this;
}
@@ -323,7 +323,7 @@ public function matches($key, $regex, $modifiers="") {
* Matches result objects returned from a sub-query
*
* @param string $key
- * @param LeanQuery $query The sub-query
+ * @param Query $query The sub-query
* @return self
*/
public function matchesInQuery($key, $query) {
@@ -338,7 +338,7 @@ public function matchesInQuery($key, $query) {
* Not-match result objects returned from a sub-query
*
* @param string $key
- * @param LeanQuery $query The sub-query
+ * @param Query $query The sub-query
* @return self
*/
public function notMatchInQuery($key, $query) {
@@ -354,7 +354,7 @@ public function notMatchInQuery($key, $query) {
*
* @param string $key
* @param string $queryKey Target field key in sub-query
- * @param LeanQuery $query The sub-query
+ * @param Query $query The sub-query
* @return self
*/
public function matchesFieldInQuery($key, $queryKey, $query) {
@@ -373,7 +373,7 @@ public function matchesFieldInQuery($key, $queryKey, $query) {
*
* @param string $key
* @param string $queryKey Target field key in sub-query
- * @param LeanQuery $query The sub-query
+ * @param Query $query The sub-query
* @return self
*/
public function notMatchFieldInQuery($key, $queryKey, $query) {
@@ -598,8 +598,8 @@ public function addDescend($key) {
* Compose AND/OR query from queries
*
* @param string $op Operator string, either `$and` or `$or`
- * @param array $queries Array of LeanQuery
- * @return LeanQuery
+ * @param array $queries Array of Query
+ * @return Query
*/
private static function composeQuery($op, $queries) {
$className = $queries[0]->getClassName();
@@ -610,7 +610,7 @@ private static function composeQuery($op, $queries) {
}
$conds[] = $q->where;
}
- $query = new LeanQuery($className);
+ $query = new Query($className);
$query->where[$op] = $conds;
return $query;
}
@@ -622,7 +622,7 @@ private static function composeQuery($op, $queries) {
* LeanQueries.
*
* @param ...
- * @return LeanQuery
+ * @return Query
*/
public static function orQuery($queries) {
if (!is_array($queries)) {
@@ -638,7 +638,7 @@ public static function orQuery($queries) {
* LeanQueries.
*
* @param ...
- * @return LeanQuery
+ * @return Query
*/
public static function andQuery($queries) {
if (!is_array($queries)) {
@@ -712,10 +712,7 @@ public function get($objectId) {
*/
public function first() {
$objects = $this->find($this->skip, 1);
- if (empty($objects)) {
- throw new CloudException("Object not found.", 101);
- }
- return $objects[0];
+ return empty($objects) ? null : $objects[0];
}
/**
@@ -738,7 +735,7 @@ public function find($skip=-1, $limit=-1) {
$params["limit"] = $limit;
}
- $resp = LeanClient::get("/classes/{$this->getClassName()}", $params);
+ $resp = Client::get("/classes/{$this->getClassName()}", $params);
$objects = array();
forEach($resp["results"] as $props) {
$obj = LeanObject::create($this->getClassName());
@@ -757,7 +754,7 @@ public function count() {
$params = $this->encode();
$params["limit"] = 0;
$params["count"] = 1;
- $resp = LeanClient::get("/classes/{$this->getClassName()}", $params);
+ $resp = Client::get("/classes/{$this->getClassName()}", $params);
return $resp["count"];
}
@@ -778,9 +775,9 @@ public function count() {
public static function doCloudQuery($cql, $pvalues=array()) {
$data = array("cql" => $cql);
if (!empty($pvalues)) {
- $data["pvalues"] = json_encode(LeanClient::encode($pvalues));
+ $data["pvalues"] = json_encode(Client::encode($pvalues));
}
- $resp = LeanClient::get('/cloudQuery', $data);
+ $resp = Client::get('/cloudQuery', $data);
$objects = array();
forEach($resp["results"] as $val) {
$obj = LeanObject::create($resp["className"], $val["objectId"]);
diff --git a/src/LeanCloud/Region.php b/src/LeanCloud/Region.php
new file mode 100644
index 0000000..462056e
--- /dev/null
+++ b/src/LeanCloud/Region.php
@@ -0,0 +1,24 @@
+targetClassName) {
- $query = new LeanQuery($this->targetClassName);
+ $query = new Query($this->targetClassName);
} else {
- $query = new LeanQuery($this->parent->getClassName());
+ $query = new Query($this->parent->getClassName());
$query->addOption("redirectClassNameForKey", $this->key);
}
$query->relatedTo($this->key, $this->parent);
@@ -134,10 +134,10 @@ public function getQuery() {
* Query on the parent class where child is in the relation
*
* @param LeanObject $child Child object
- * @return LeanQuery
+ * @return Query
*/
public function getReverseQuery(LeanObject $child) {
- $query = new LeanQuery($this->parent->getClassName());
+ $query = new Query($this->parent->getClassName());
$query->equalTo($this->key, $child->getPointer());
return $query;
}
diff --git a/src/LeanCloud/LeanRole.php b/src/LeanCloud/Role.php
similarity index 86%
rename from src/LeanCloud/LeanRole.php
rename to src/LeanCloud/Role.php
index 780cecc..5dc8acd 100644
--- a/src/LeanCloud/LeanRole.php
+++ b/src/LeanCloud/Role.php
@@ -10,15 +10,15 @@
* write permission.
*
* All users of a role could be queried by `$role->getUsers()`, which
- * is an instance of LeanRelation, where users can be added or
+ * is an instance of Relation, where users can be added or
* removed.
*
* Roles can belong to role as well, which can be got by
* `$role->getRoles()`, where roles can be added or removed.
*
- * @see LeanACL, LeanRelation
+ * @see ACL, Relation
*/
-class LeanRole extends LeanObject {
+class Role extends LeanObject {
/**
* Table name on LeanCloud
* @var string
@@ -31,7 +31,7 @@ class LeanRole extends LeanObject {
* The name can contain only alphanumeric characters, _, -, and
* space. It cannot be changed after being saved.
*
- * @return LeanRole
+ * @return Role
*/
public function setName($name) {
$this->set("name", $name);
@@ -50,7 +50,7 @@ public function getName() {
/**
* Get a relation of users that belongs to this role
*
- * @return LeanRelation
+ * @return Relation
*/
public function getUsers() {
return $this->getRelation("users");
@@ -59,7 +59,7 @@ public function getUsers() {
/**
* Get a relation of roles that belongs to this role
*
- * @return LeanRelation
+ * @return Relation
*/
public function getRoles() {
return $this->getRelation("roles");
diff --git a/src/LeanCloud/SMS.php b/src/LeanCloud/SMS.php
new file mode 100644
index 0000000..3662e61
--- /dev/null
+++ b/src/LeanCloud/SMS.php
@@ -0,0 +1,53 @@
+ $v) {
+ if (!isset($options[$k])) {
+ unset($options[$k]);
+ }
+ }
+ $options["mobilePhoneNumber"] = $phoneNumber;
+ Client::post("/requestSmsCode", $options);
+ }
+
+ /**
+ * Verify SMS code
+ *
+ * @param string $phoneNumber
+ * @param string $smsCode
+ */
+ public static function verifySmsCode($phoneNumber, $smsCode) {
+ Client::post("/verifySmsCode/{$smsCode}?mobilePhoneNumber={$phoneNumber}",
+ null);
+ }
+
+}
diff --git a/src/LeanCloud/SaveOption.php b/src/LeanCloud/SaveOption.php
new file mode 100644
index 0000000..d9d22aa
--- /dev/null
+++ b/src/LeanCloud/SaveOption.php
@@ -0,0 +1,44 @@
+fetchWhenSave)) {
+ $params["fetchWhenSave"] = $this->fetchWhenSave ? true : false;
+ }
+ if (!is_null($this->where)) {
+ if ($this->where instanceof Query) {
+ $out = $this->where->encode();
+ $params["where"] = $out["where"];
+ } else {
+ throw new \RuntimeException("where of SaveOption must be Query object.");
+ }
+ }
+ return $params;
+ }
+}
diff --git a/src/LeanCloud/Storage/IStorage.php b/src/LeanCloud/Storage/IStorage.php
index cd783bc..5d8a2ad 100644
--- a/src/LeanCloud/Storage/IStorage.php
+++ b/src/LeanCloud/Storage/IStorage.php
@@ -6,8 +6,8 @@
* Storage Interface
*
* Simple key-value storage interface for persisting session related
- * data. At SDK level, it is attached to LeanClient, and used for
- * storing session token of a logged-in LeanUser.
+ * data. At SDK level, it is attached to Client, and used for
+ * storing session token of a logged-in User.
*
*/
interface IStorage {
diff --git a/src/LeanCloud/Uploader/QCloudUploader.php b/src/LeanCloud/Uploader/QCloudUploader.php
new file mode 100644
index 0000000..32c970f
--- /dev/null
+++ b/src/LeanCloud/Uploader/QCloudUploader.php
@@ -0,0 +1,69 @@
+multipartEncode(array(
+ "name" => $key,
+ "mimeType" => $mimeType,
+ "content" => $content,
+ ), array(
+ "op" => "upload",
+ "sha" => hash("sha1", $content)
+ ), $boundary);
+
+ $headers[] = "User-Agent: " . Client::getVersionString();
+ $headers[] = "Content-Type: multipart/form-data;" .
+ " boundary={$boundary}";
+ // $headers[] = "Content-Length: " . strlen($body);
+ $headers[] = "Authorization: {$this->getAuthToken()}";
+ $url = $this->getUploadUrl();
+ $ch = curl_init($url);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_POST, 1);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
+ $resp = curl_exec($ch);
+ $respCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ $respType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
+ $error = curl_error($ch);
+ $errno = curl_errno($ch);
+ curl_close($ch);
+
+ /** type of error:
+ * - curl error
+ * - http status error 4xx, 5xx
+ * - rest api error
+ */
+ if ($errno > 0) {
+ throw new \RuntimeException("CURL ($url) error: " .
+ "{$errno} {$error}",
+ $errno);
+ }
+
+ $data = json_decode($resp, true);
+ if ($data["code"] != 0) {
+ throw new \RuntimeException("Upload to Qcloud ({$url}) failed: ".
+ "{$data['code']} {$data['message']}",
+ $data["code"]);
+ }
+ return $data;
+ }
+
+}
diff --git a/src/LeanCloud/Uploader/QiniuUploader.php b/src/LeanCloud/Uploader/QiniuUploader.php
new file mode 100644
index 0000000..e440236
--- /dev/null
+++ b/src/LeanCloud/Uploader/QiniuUploader.php
@@ -0,0 +1,83 @@
+multipartEncode(array(
+ "name" => $key,
+ "mimeType" => $mimeType,
+ "content" => $content,
+ ), array(
+ "token" => $this->getAuthToken(),
+ "key" => $key,
+ "crc32" => $this->crc32Data($content)
+ ), $boundary);
+
+ $headers[] = "User-Agent: " . Client::getVersionString();
+ $headers[] = "Content-Type: multipart/form-data;" .
+ " boundary={$boundary}";
+ $headers[] = "Content-Length: " . strlen($body);
+
+ $url = $this->getUploadUrl();
+ $ch = curl_init($url);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_POST, 1);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
+ $resp = curl_exec($ch);
+ $respCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ $respType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
+ $error = curl_error($ch);
+ $errno = curl_errno($ch);
+ curl_close($ch);
+
+ /** type of error:
+ * - curl error
+ * - http status error 4xx, 5xx
+ * - rest api error
+ */
+ if ($errno > 0) {
+ throw new \RuntimeException("CURL ($url) error: " .
+ "{$errno} {$error}",
+ $errno);
+ }
+
+ $data = json_decode($resp, true);
+ if (isset($data["error"])) {
+ $code = isset($data["code"]) ? $data["code"] : 1;
+ throw new \RuntimeException("Upload to Qiniu ({$url}) failed: ".
+ "{$code} {$data['error']}", $code);
+ }
+ return $data;
+ }
+
+}
diff --git a/src/LeanCloud/Uploader/S3Uploader.php b/src/LeanCloud/Uploader/S3Uploader.php
new file mode 100644
index 0000000..770116e
--- /dev/null
+++ b/src/LeanCloud/Uploader/S3Uploader.php
@@ -0,0 +1,47 @@
+getUploadUrl()) {
+ throw new \RuntimeException("Please initialize with pre-signed url.");
+ }
+ $headers[] = "User-Agent: " . Client::getVersionString();
+ $headers[] = "Content-Type: $mimeType";
+ $url = $this->getUploadUrl();
+ $ch = curl_init($url);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $content);
+ $resp = curl_exec($ch);
+ $respCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ $respType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
+ $error = curl_error($ch);
+ $errno = curl_errno($ch);
+ curl_close($ch);
+
+ if ($errno > 0) {
+ throw new \RuntimeException("CURL ({$url}) error: " .
+ "{$errno} {$error}",
+ $errno);
+ }
+
+ if ($respCode >= "300") {
+ $S3Error = simplexml_load_string($resp);
+ throw new \RuntimeException("Upload to S3 ({$url}) failed: " .
+ "{$S3Error->Code} {$S3Error->Message}");
+ }
+ return true;
+ }
+}
diff --git a/src/LeanCloud/Uploader/SimpleUploader.php b/src/LeanCloud/Uploader/SimpleUploader.php
new file mode 100644
index 0000000..a6bf293
--- /dev/null
+++ b/src/LeanCloud/Uploader/SimpleUploader.php
@@ -0,0 +1,94 @@
+ $val) {
+ $body .= "--{$boundary}\r\n";
+ $body .= "Content-Disposition: form-data; name=\"{$key}\"\r\n\r\n";
+ $body .= "{$val}\r\n";
+ }
+
+ if (!empty($file)) {
+ $mimeType = "application/octet-stream";
+ if (isset($file["mimeType"])) {
+ $mimeType = $file["mimeType"];
+ }
+ $fieldname = static::getFileFieldName();
+ // escape quotes in file name
+ $filename = filter_var($file["name"],
+ FILTER_SANITIZE_MAGIC_QUOTES);
+
+ $body .= "--{$boundary}\r\n";
+ $body .= "Content-Disposition: form-data; name=\"{$fieldname}\"; filename=\"{$filename}\"\r\n";
+ $body .= "Content-Type: {$mimeType}\r\n\r\n";
+ $body .= "{$file['content']}\r\n";
+ }
+
+ // append end frontier
+ $body .= "--{$boundary}--\r\n";
+
+ return $body;
+ }
+
+ /**
+ * Initialize uploader with url and auth token
+ *
+ * @param string $uploadUrl File provider url
+ * @param string $authToken Auth token for file provider
+ */
+ public function initialize($uploadUrl, $authToken) {
+ $this->uploadUrl = $uploadUrl;
+ $this->authToken = $authToken;
+ }
+
+ public function getUploadUrl() {
+ return $this->uploadUrl;
+ }
+
+ public function getAuthToken() {
+ return $this->authToken;
+ }
+
+ abstract public function upload($content, $mimeType, $key);
+}
diff --git a/src/LeanCloud/LeanUser.php b/src/LeanCloud/User.php
similarity index 70%
rename from src/LeanCloud/LeanUser.php
rename to src/LeanCloud/User.php
index a258b59..039b988 100644
--- a/src/LeanCloud/LeanUser.php
+++ b/src/LeanCloud/User.php
@@ -1,7 +1,7 @@
getObjectId()) {
- parent::save();
+ parent::save($option);
} else {
throw new CloudException("Cannot save new user, please signUp ".
"first.");
@@ -134,7 +135,7 @@ public function save() {
public function updatePassword($old, $new) {
if ($this->getObjectId()) {
$path = "/users/{$this->getObjectId()}/updatePassword";
- $resp = LeanClient::put($path, array("old_password" => $old,
+ $resp = Client::put($path, array("old_password" => $old,
"new_password" => $new),
$this->getSessionToken());
$this->mergeAfterFetch($resp);
@@ -186,7 +187,7 @@ public function getSessionToken() {
* @param string $token Session token of logged-in user
*/
public static function setCurrentSessionToken($token) {
- LeanClient::getStorage()->set("LC_SessionToken", $token);
+ Client::getStorage()->set("LC_SessionToken", $token);
}
/**
@@ -195,16 +196,16 @@ public static function setCurrentSessionToken($token) {
* @return string
*/
public static function getCurrentSessionToken() {
- return LeanClient::getStorage()->get("LC_SessionToken");
+ return Client::getStorage()->get("LC_SessionToken");
}
/**
* Get currently logged-in user
*
- * @return LeanUser
+ * @return User
*/
public static function getCurrentUser() {
- if (self::$currentUser instanceof LeanUser) {
+ if (self::$currentUser instanceof User) {
return self::$currentUser;
}
$token = static::getCurrentSessionToken();
@@ -216,9 +217,9 @@ public static function getCurrentUser() {
/**
* Save logged-in user and session token
*
- * @param LeanUser
+ * @param User
*/
- private static function saveCurrentUser($user) {
+ public static function saveCurrentUser($user) {
self::$currentUser = $user;
self::setCurrentSessionToken($user->getSessionToken());
}
@@ -226,22 +227,69 @@ private static function saveCurrentUser($user) {
/**
* Clear logged-in user and session token.
*/
- private static function clearCurrentUser() {
+ public static function clearCurrentUser() {
self::$currentUser = null;
self::setCurrentSessionToken(null);
}
+ /**
+ * Refresh session token
+ */
+ public function refreshSessionToken() {
+ $resp = Client::put("/users/{$this->getObjectId()}/refreshSessionToken",
+ null);
+ $this->mergeAfterFetch($resp);
+ static::saveCurrentUser($this);
+ }
+
+ /**
+ * Test if user logged in and session token is valid.
+ *
+ * @return bool
+ */
+ public function isAuthenticated() {
+ $token = $this->getSessionToken();
+ if (!$token) {
+ return false;
+ }
+ try {
+ $resp = Client::get("/users/me",
+ array("session_token" => $token));
+ } catch(CloudException $ex) {
+ if ($ex->getCode() === 211) {
+ return false;
+ }
+ throw ex;
+ }
+ return true;
+ }
+
+ /**
+ * Get roles the user belongs to
+ *
+ * @return array Array of Role
+ */
+ public function getRoles() {
+ if (!$this->getObjectId()) {
+ return array();
+ }
+ $query = new Query("_Role");
+ $query->equalTo("users", $this);
+ $roles = $query->find();
+ return $roles;
+ }
+
/**
* Log-in user by session token
*
* And set current user.
*
* @param string $token Session token
- * @return LeanUser
+ * @return User
* @throws CloudException
*/
public static function become($token) {
- $resp = LeanClient::get("/users/me",
+ $resp = Client::get("/users/me",
array("session_token" => $token));
$user = new static();
$user->mergeAfterFetch($resp);
@@ -250,6 +298,14 @@ public static function become($token) {
return $user;
}
+
+ private static function _login($userData) {
+ $resp = Client::post("/login", $userData);
+ $user = new static();
+ $user->mergeAfterFetch($resp);
+ static::saveCurrentUser($user);
+ return $user;
+ }
/**
* Log-in user by username and password
*
@@ -257,18 +313,31 @@ public static function become($token) {
*
* @param string $username
* @param string $password
- * @return LeanUser
+ * @return User
* @throws CloudException
*/
public static function logIn($username, $password) {
- $resp = LeanClient::post("/login", array("username" => $username,
- "password" => $password));
- $user = new static();
- $user->mergeAfterFetch($resp);
- static::saveCurrentUser($user);
+ $user = static::_login(array("username" => $username,
+ "password" => $password));
+ return $user;
+ }
+ /**
+ * Log-in user by email and password
+ *
+ * And set current user.
+ *
+ * @param string $email
+ * @param string $password
+ * @return User
+ * @throws CloudException
+ */
+ public static function logInWithEmail($email, $password) {
+ $user = static::_login(array("email" => $email,
+ "password" => $password));
return $user;
}
+
/**
* Log-out current user
*/
@@ -276,7 +345,7 @@ public static function logOut() {
$user = static::getCurrentUser();
if ($user) {
try {
- LeanClient::post("/logout", null, $user->getSessionToken());
+ Client::post("/logout", null, $user->getSessionToken());
} catch (CloudException $exp) {
// skip
}
@@ -289,12 +358,12 @@ public static function logOut() {
*
* @param string $phoneNumber
* @param string $password
- * @return LeanUser
+ * @return User
*/
public static function logInWithMobilePhoneNumber($phoneNumber, $password) {
$params = array("mobilePhoneNumber" => $phoneNumber,
"password" => $password);
- $resp = LeanClient::post("/login", $params);
+ $resp = Client::post("/login", $params);
$user = new static();
$user->mergeAfterFetch($resp);
static::saveCurrentUser($user);
@@ -309,12 +378,12 @@ public static function logInWithMobilePhoneNumber($phoneNumber, $password) {
*
* @param string $phoneNumber Registered mobile phone number
* @param string $smsCode
- * @return LeanUser
+ * @return User
*/
public static function logInWithSmsCode($phoneNumber, $smsCode) {
$params = array("mobilePhoneNumber" => $phoneNumber,
"smsCode" => $smsCode);
- $resp = LeanClient::get("/login", $params);
+ $resp = Client::get("/login", $params);
$user = new static();
$user->mergeAfterFetch($resp);
static::saveCurrentUser($user);
@@ -330,7 +399,7 @@ public static function logInWithSmsCode($phoneNumber, $smsCode) {
* @param string $phoneNumber Register mobile phone number
*/
public static function requestLoginSmsCode($phoneNumber) {
- LeanClient::post("/requestLoginSmsCode",
+ Client::post("/requestLoginSmsCode",
array("mobilePhoneNumber" => $phoneNumber));
}
@@ -342,7 +411,7 @@ public static function requestLoginSmsCode($phoneNumber) {
* @param string $email
*/
public static function requestEmailVerify($email) {
- LeanClient::post("/requestEmailVerify", array("email" => $email));
+ Client::post("/requestEmailVerify", array("email" => $email));
}
/**
@@ -351,7 +420,7 @@ public static function requestEmailVerify($email) {
* @param string $email Registered email
*/
public static function requestPasswordReset($email) {
- LeanClient::post("/requestPasswordReset", array("email" => $email));
+ Client::post("/requestPasswordReset", array("email" => $email));
}
/**
@@ -362,7 +431,7 @@ public static function requestPasswordReset($email) {
* @param string $phoneNumber Registered mobile phone number
*/
public static function requestPasswordResetBySmsCode($phoneNumber) {
- LeanClient::post("/requestPasswordResetBySmsCode",
+ Client::post("/requestPasswordResetBySmsCode",
array("mobilePhoneNumber" => $phoneNumber));
}
@@ -372,9 +441,11 @@ public static function requestPasswordResetBySmsCode($phoneNumber) {
* @param string $smsCode
* @param string $newPassword
*/
- public static function resetPasswordBySmsCode($smsCode, $newPassword) {
- LeanClient::put("/resetPasswordBySmsCode/{$smsCode}",
- array("password" => $newPassword));
+ public static function resetPasswordBySmsCode($smsCode, $newPassword, $mobilePhoneNumber) {
+ Client::put("/resetPasswordBySmsCode/{$smsCode}", array(
+ "password" => $newPassword,
+ "mobilePhoneNumber" => $mobilePhoneNumber
+ ));
}
/**
@@ -385,7 +456,7 @@ public static function resetPasswordBySmsCode($smsCode, $newPassword) {
* @param string $phoneNumber
*/
public static function requestMobilePhoneVerify($phoneNumber) {
- LeanClient::post("/requestMobilePhoneVerify",
+ Client::post("/requestMobilePhoneVerify",
array("mobilePhoneNumber" => $phoneNumber));
}
@@ -394,11 +465,53 @@ public static function requestMobilePhoneVerify($phoneNumber) {
*
* @param string $smsCode
*/
- public static function verifyMobilePhone($smsCode) {
- LeanClient::post("/verifyMobilePhone/{$smsCode}", null);
+ public static function verifyMobilePhone($smsCode, $mobilePhoneNumber) {
+ Client::post("/verifyMobilePhone/{$smsCode}", array(
+ "mobilePhoneNumber" => $mobilePhoneNumber
+ ));
}
+ /**
+ * Request mobile phone verify before updating it.
+ *
+ * @param string $phoneNumber
+ */
+ public static function requestChangePhoneNumber($phoneNumber) {
+ Client::post("/requestChangePhoneNumber", array(
+ "mobilePhoneNumber" => $phoneNumber
+ ));
+ }
+ /**
+ * Update mobile phone number by SMS code.
+ *
+ * @param string $smsCode
+ * @param string $phoneNumber
+ */
+ public static function changePhoneNumber($smsCode, $phoneNumber) {
+ Client::post("/changePhoneNumber", array(
+ "mobilePhoneNumber" => $phoneNumber,
+ "code" => $smsCode
+ ));
+ }
+
+ /**
+ * Sign up user by mobile phone and SMS code
+ *
+ * @param string $phoneNumber
+ * @param string $smsCode
+ * @return User
+ */
+ public static function signUpOrLoginByMobilePhone($phoneNumber, $smsCode) {
+ $resp = Client::post("/usersByMobilePhone", array(
+ "mobilePhoneNumber" => $phoneNumber,
+ "smsCode" => $smsCode
+ ));
+ $user = new static();
+ $user->mergeAfterFetch($resp);
+ static::saveCurrentUser($user);
+ return $user;
+ }
/*
* Link and unlink with 3rd party auth provider
@@ -425,7 +538,7 @@ public static function verifyMobilePhone($smsCode) {
*
* @param string $provider Provider name
* @param array $authToken Auth token
- * @return LeanUser
+ * @return User
*/
public static function logInWith($provider, $authToken) {
$user = new static();
@@ -482,4 +595,3 @@ public function unlinkWith($provider) {
}
}
-
diff --git a/tests/LeanACLTest.php b/test/ACLTest.php
similarity index 72%
rename from tests/LeanACLTest.php
rename to test/ACLTest.php
index 630516c..fb1afd9 100644
--- a/tests/LeanACLTest.php
+++ b/test/ACLTest.php
@@ -1,22 +1,23 @@
encode();
$this->assertEquals(true, $out["id123"]["read"]);
$this->assertEquals(true, $out["id123"]["write"]);
@@ -28,27 +29,27 @@ public function testInitializeUserACL() {
* @link https://github.com/leancloud/php-sdk/issues/84
*/
public function testEmptyACL() {
- $acl = new LeanACL();
+ $acl = new ACL();
$out = $acl->encode();
$this->assertEquals("{}", json_encode($out));
}
public function testSetPublicAccess() {
- $acl = new LeanACL();
+ $acl = new ACL();
$acl->setPublicReadAccess(true);
$out = $acl->encode();
- $this->assertEquals(true, $out[LeanACL::PUBLIC_KEY]["read"]);
+ $this->assertEquals(true, $out[ACL::PUBLIC_KEY]["read"]);
$this->assertEquals(true, $acl->getPublicReadAccess());
$acl->setPublicWriteAccess(false);
$out = $acl->encode();
- $this->assertEquals(false, $out[LeanACL::PUBLIC_KEY]["write"]);
+ $this->assertEquals(false, $out[ACL::PUBLIC_KEY]["write"]);
$this->assertEquals(false, $acl->getPublicWriteAccess());
}
public function testSetUserAccess() {
- $user = new LeanUser(null, "id123");
- $acl = new LeanACL();
+ $user = new User(null, "id123");
+ $acl = new ACL();
$acl->setReadAccess($user, true);
$out = $acl->encode();
$this->assertEquals(true, $out[$user->getObjectId()]["read"]);
@@ -61,10 +62,10 @@ public function testSetUserAccess() {
}
public function testSetRoleAccess() {
- $role = new LeanRole();
+ $role = new Role();
$role->setName("admin");
- $role->setACL(new LeanACL());
- $acl = new LeanACL();
+ $role->setACL(new ACL());
+ $acl = new ACL();
$acl->setRoleReadAccess($role, true);
$out = $acl->encode();
$this->assertEquals(true, $out["role:admin"]["read"]);
@@ -77,7 +78,7 @@ public function testSetRoleAccess() {
}
public function testSetRoleAccessWithRoleName() {
- $acl = new LeanACL();
+ $acl = new ACL();
$acl->setRoleReadAccess("admin", true);
$out = $acl->encode();
$this->assertEquals(true, $out["role:admin"]["read"]);
diff --git a/tests/LeanAPITest.php b/test/APITest.php
similarity index 58%
rename from tests/LeanAPITest.php
rename to test/APITest.php
index cc42d3b..e7a2473 100644
--- a/tests/LeanAPITest.php
+++ b/test/APITest.php
@@ -1,31 +1,33 @@
"alice");
- $resp = LeanClient::post("/classes/TestObject", $obj);
+ $resp = Client::post("/classes/TestObject", $obj);
$this->setExpectedException("LeanCloud\CloudException",
- "111 Invalid value type for field", 111);
- $resp2 = LeanClient::put("/classes/TestObject/" . $resp["objectId"],
+ "Invalid value type for field", 111);
+ $resp2 = Client::put("/classes/TestObject/" . $resp["objectId"],
array("name" => array("__op" => "Increment",
"amount" => 1)));
- LeanClient::delete("/classes/TestObject/{$resp['objectId']}");
+ Client::delete("/classes/TestObject/{$resp['objectId']}");
}
/**
@@ -35,7 +37,7 @@ public function testIncrementOnNewObject() {
$obj = array("name" => "alice",
"score" => array("__op" => "Increment",
"amount" => 1));
- $resp = LeanClient::post("/classes/TestObject", $obj);
+ $resp = Client::post("/classes/TestObject", $obj);
$this->assertNotEmpty($resp["objectId"]);
}
@@ -43,56 +45,56 @@ public function testAddOnNewObject() {
$obj = array("name" => "alice",
"tags" => array("__op" => "Add",
"objects" => array("frontend")));
- $resp = LeanClient::post("/classes/TestObject", $obj);
+ $resp = Client::post("/classes/TestObject", $obj);
$this->assertNotEmpty($resp["objectId"]);
- LeanClient::delete("/classes/TestObject/{$resp['objectId']}");
+ Client::delete("/classes/TestObject/{$resp['objectId']}");
}
public function testAddUniqueOnAddField() {
$obj = array("name" => "alice",
"tags" => array("__op" => "Add",
"objects" => array("frontend", "frontend")));
- $resp = LeanClient::post("/classes/TestObject", $obj);
+ $resp = Client::post("/classes/TestObject", $obj);
$this->assertNotEmpty($resp["objectId"]);
- $resp2 = LeanClient::get("/classes/TestObject/{$resp["objectId"]}");
+ $resp2 = Client::get("/classes/TestObject/{$resp["objectId"]}");
$this->assertEquals(array("frontend", "frontend"), $resp2["tags"]);
- $resp3 = LeanClient::put("/classes/TestObject/{$resp["objectId"]}",
+ $resp3 = Client::put("/classes/TestObject/{$resp["objectId"]}",
array("tags" => array("__op" => "AddUnique",
"objects" => array("css"))));
// AddUnique will not remove exsiting duplicate items
- $resp4 = LeanClient::get("/classes/TestObject/{$resp["objectId"]}");
+ $resp4 = Client::get("/classes/TestObject/{$resp["objectId"]}");
$this->assertEquals(array("frontend", "frontend", "css"),
$resp4["tags"]);
- LeanClient::delete("/classes/TestObject/{$resp['objectId']}");
+ Client::delete("/classes/TestObject/{$resp['objectId']}");
}
public function testHeterogeneousObjectsInArray() {
$obj = array("name" => "alice",
"tags" => array("foo", 42, array("a", "b")));
- $resp = LeanClient::post("/classes/TestObject", $obj);
+ $resp = Client::post("/classes/TestObject", $obj);
$this->assertNotEmpty($resp["objectId"]);
- LeanClient::delete("/classes/TestObject/{$resp['objectId']}");
+ Client::delete("/classes/TestObject/{$resp['objectId']}");
}
public function testSetHashValue() {
$obj = array("name" => "alice",
"attr" => array("age" => 12,
"gender" => "female"));
- $resp = LeanClient::post("/classes/TestObject", $obj);
+ $resp = Client::post("/classes/TestObject", $obj);
$this->assertNotEmpty($resp["objectId"]);
// Add hash pair to hash field is not valid
$this->setExpectedException("LeanCloud\CloudException", null, 1);
- $resp2 = LeanClient::put("/classes/TestObject/{$resp["objectId"]}",
+ $resp2 = Client::put("/classes/TestObject/{$resp["objectId"]}",
array("attr" => array(
"__op" => "add",
"objects" => array("favColor" => "Orange"))));
- LeanClient::delete("/classes/TestObject/{$resp['objectId']}");
+ Client::delete("/classes/TestObject/{$resp['objectId']}");
}
public function testAddRelation() {
@@ -103,10 +105,10 @@ public function testAddRelation() {
"objectId" => "abc001")));
$obj = array("name" => "alice",
"likes" => $adds);
- $resp = LeanClient::post("/classes/TestObject", $obj);
+ $resp = Client::post("/classes/TestObject", $obj);
$this->assertNotEmpty($resp["objectId"]);
- LeanClient::delete("/classes/TestObject/{$resp['objectId']}");
+ Client::delete("/classes/TestObject/{$resp['objectId']}");
}
public function testRelationBatchOp() {
@@ -114,30 +116,32 @@ public function testRelationBatchOp() {
"objects" => array(
array("__type" => "Pointer",
"className" => "TestObject",
- "objectId" => "abc001")));
- $removes = array("__op" => "RemoveRelation",
- "objects" => array(
+ "objectId" => "abc001"),
array("__type" => "Pointer",
"className" => "TestObject",
"objectId" => "abc002")));
+ $addsMore = array("__op" => "AddRelation",
+ "objects" => array(
+ array("__type" => "Pointer",
+ "className" => "TestObject",
+ "objectId" => "abc003")));
$obj = array("name" => "alice",
"likes" => array("__op" => "Batch",
- "ops" => array($adds, $removes)));
- $this->setExpectedException("LeanCloud\CloudException", null, 301);
- $resp = LeanClient::post("/classes/TestObject", $obj);
- // $this->assertNotEmpty($resp["objectId"]);
- // LeanClient::delete("/classes/TestObject/{$resp['objectId']}");
+ "ops" => array($adds, $addsMore)));
+ $resp = Client::post("/classes/TestObject", $obj);
+ $this->assertNotEmpty($resp["objectId"]);
+ Client::delete("/classes/TestObject/{$resp['objectId']}");
}
+
/**
* Batch on array operation will result error:
*
- * 301 - Fails to insert new document, cannot update on ...
- * at the same time.
+ * 304 - Invalid array operation.
*/
public function testBatchOperationOnArray() {
$obj = array("name" => "Batch test", "tags" => array());
- $resp = LeanClient::post("/classes/TestObject", $obj);
+ $resp = Client::post("/classes/TestObject", $obj);
$this->assertNotEmpty($resp["objectId"]);
@@ -148,18 +152,17 @@ public function testBatchOperationOnArray() {
$obj = array("tags" => array("__op" => "Batch",
"ops" => array($adds, $removes)));
- $this->setExpectedException("LeanCloud\CloudException", null, 301);
- $resp = LeanClient::put("/classes/TestObject/{$resp['objectId']}",
+ $this->setExpectedException("LeanCloud\CloudException", null, 304);
+ Client::put("/classes/TestObject/{$resp['objectId']}",
$obj);
-
- LeanClient::delete("/classes/TestObject/{$obj['objectId']}");
+ Client::delete("/classes/TestObject/{$obj['objectId']}");
}
public function testBatchGet() {
$obj1 = array("name" => "alice 1");
$obj2 = array("name" => "alice 2");
- $resp1 = LeanClient::post("/classes/TestObject", $obj1);
- $resp2 = LeanClient::post("/classes/TestObject", $obj2);
+ $resp1 = Client::post("/classes/TestObject", $obj1);
+ $resp2 = Client::post("/classes/TestObject", $obj2);
$this->assertNotEmpty($resp1["objectId"]);
$this->assertNotEmpty($resp2["objectId"]);
@@ -167,7 +170,7 @@ public function testBatchGet() {
"method" => "GET");
$req[] = array("path" => "/1.1/classes/TestObject/{$resp2['objectId']}",
"method" => "GET");
- $resp = LeanClient::post("/batch", array("requests" => $req));
+ $resp = Client::post("/batch", array("requests" => $req));
$this->assertEquals(2, count($resp));
$this->assertEquals($resp1["objectId"], $resp[0]["success"]["objectId"]);
$this->assertEquals($resp2["objectId"], $resp[1]["success"]["objectId"]);
@@ -175,37 +178,63 @@ public function testBatchGet() {
public function testBatchGetNotFound() {
$obj = array("name" => "alice");
- $resp = LeanClient::post("/classes/TestObject", $obj);
+ $resp = Client::post("/classes/TestObject", $obj);
$this->assertNotEmpty($resp["objectId"]);
$req[] = array("path" => "/1.1/classes/TestObject/{$resp['objectId']}",
"method" => "GET");
$req[] = array("path" => "/1.1/classes/TestObject/nonexistent_id",
"method" => "GET");
- $resp2 = LeanClient::batch($req);
+ $resp2 = Client::batch($req);
$this->assertNotEmpty($resp2[0]["success"]);
$this->assertEmpty($resp2[1]["success"]); // empty when not found
- LeanClient::delete("/classes/TestObject/{$resp['objectId']}");
+ Client::delete("/classes/TestObject/{$resp['objectId']}");
}
public function testUserLogin() {
$data = array("username" => "testuser",
"password" => "5akf#a?^G",
"phone" => "18612340000");
- $resp = LeanClient::post("/users", $data);
+ $resp = Client::post("/users", $data);
$this->assertNotEmpty($resp["objectId"]);
$this->assertNotEmpty($resp["sessionToken"]);
$id = $resp["objectId"];
- $resp = LeanClient::get("/users/me",
+ $resp = Client::get("/users/me",
array("session_token" => $resp["sessionToken"]));
$this->assertNotEmpty($resp["objectId"]);
- LeanClient::delete("/users/{$id}", $resp["sessionToken"]);
+ Client::delete("/users/{$id}", $resp["sessionToken"]);
// Raise 211: Could not find user.
$this->setExpectedException("LeanCloud\CloudException", null, 211);
- $resp = LeanClient::get("/users/me",
+ $resp = Client::get("/users/me",
array("session_token" => "non-existent-token"));
}
+ public function testGzipCompatibility() {
+ // Test that enable server-side gzip shall not break client decoding.
+ // minimum "Content-Length: 512" to trigger server gzip
+ $obj = array(
+ "name" => "alice131",
+ "text" => "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
+ );
+ $resp = Client::post("/classes/TestObject", $obj);
+ $this->assertNotEmpty($resp["objectId"]);
+
+ $resp2 = Client::get("/classes/TestObject", array("where" => json_encode(array("name" => "alice131"))));
+ $this->assertNotEmpty($resp2["results"]);
+ $this->assertEquals($resp2["results"][0]["objectId"], $resp["objectId"]);
+
+ Client::delete("/classes/TestObject/{$resp['objectId']}");
+ }
+
+ public function testRequestTimeout() {
+ $getTimeout = function() { return static::$apiTimeout; };
+ $getApiTimeout = $getTimeout->bindTo(null, Client::class);
+ $this->assertEquals(15, $getApiTimeout());
+ Client::setApiTimeout(3);
+ $this->assertEquals(3, $getApiTimeout());
+ Client::setApiTimeout(15); // revert to default
+ }
+
}
diff --git a/test/AppRouterTest.php b/test/AppRouterTest.php
new file mode 100644
index 0000000..0f73018
--- /dev/null
+++ b/test/AppRouterTest.php
@@ -0,0 +1,53 @@
+genId(12));
+ $router->setRegion("CN_E1");
+ $router->setRegion("US");
+ $router->setRegion(Region::CN_E1);
+ $router->setRegion(Region::US);
+ }
+
+ public function testGetRoute() {
+ $this->markTestSkipped("app-router no longer available");
+ $appid = getenv("LEANCLOUD_APP_ID");
+ $router = AppRouter::getInstance($appid);
+ $host = $router->getRoute(AppRouter::API_SERVER_KEY);
+ $domain = getenv("LEANCLOUD_WILDCARD_DOMAIN");
+ $this->assertEquals("{$this->getShortAppId($appid)}.api.{$domain}", $host);
+
+ $host = $router->getRoute(AppRouter::ENGINE_SERVER_KEY);
+ $domain = getenv("LEANCLOUD_WILDCARD_DOMAIN");
+ $this->assertEquals("{$this->getShortAppId($appid)}.engine.{$domain}", $host);
+ }
+
+ public function testGetRouteWhenAppRouterNotAvailable() {
+ $appid = $this->genId(18);
+ $router = AppRouter::getInstance($appid);
+ $router_url = getenv("LEANCLOUD_APP_ROUTER");
+ putenv("LEANCLOUD_APP_ROUTER=http://localhost:4000/route?appId=");
+ $this->assertEquals($router->getRegionDefaultRoute(AppRouter::ENGINE_SERVER_KEY),
+ $router->getRoute(AppRouter::ENGINE_SERVER_KEY));
+
+ putenv("LEANCLOUD_APP_ROUTER={$router_url}");
+ $host = $router->getRoute(AppRouter::API_SERVER_KEY);
+ $this->assertEquals($router->getRegionDefaultRoute(AppRouter::API_SERVER_KEY),
+ $host);
+ }
+
+}
diff --git a/tests/ArrayOperationTest.php b/test/ArrayOperationTest.php
similarity index 99%
rename from tests/ArrayOperationTest.php
rename to test/ArrayOperationTest.php
index 41bfb90..ad60fad 100644
--- a/tests/ArrayOperationTest.php
+++ b/test/ArrayOperationTest.php
@@ -1,9 +1,11 @@
setExpectedException("InvalidArgumentException",
"Operation on array not supported: Set.");
diff --git a/tests/LeanBytesTest.php b/test/BytesTest.php
similarity index 65%
rename from tests/LeanBytesTest.php
rename to test/BytesTest.php
index be06f1e..68785f2 100644
--- a/tests/LeanBytesTest.php
+++ b/test/BytesTest.php
@@ -1,38 +1,39 @@
encode();
$this->assertEquals("Bytes", $out["__type"]);
$this->assertEquals("", $out["base64"]);
}
public function testEncodeArray() {
- $bytes = LeanBytes::createFromByteArray(array(72, 101, 108, 108, 111));
+ $bytes = Bytes::createFromByteArray(array(72, 101, 108, 108, 111));
$out = $bytes->encode();
$this->assertEquals("Bytes", $out["__type"]);
$this->assertEquals(base64_encode("Hello"), $out["base64"]);
}
public function testCreateFromEmptyString() {
- $bytes = LeanBytes::createFromBase64Data(base64_encode(""));
+ $bytes = Bytes::createFromBase64Data(base64_encode(""));
$this->assertEmpty($bytes->getByteArray());
$this->assertEquals("", $bytes->asString());
}
public function testCreateFromBase64() {
- $bytes = LeanBytes::createFromByteArray(array(72, 101, 108, 108, 111));
- $bytes1 = LeanBytes::createFromBase64Data(base64_encode("Hello"));
+ $bytes = Bytes::createFromByteArray(array(72, 101, 108, 108, 111));
+ $bytes1 = Bytes::createFromBase64Data(base64_encode("Hello"));
$this->assertEquals($bytes->getByteArray(), $bytes1->getByteArray());
$this->assertEquals("Hello", $bytes->asString());
$this->assertEquals("Hello", $bytes1->asString());
}
public function testEncodeCreateFromBase64() {
- $bytes = LeanBytes::createFromBase64Data(base64_encode("Hello"));
+ $bytes = Bytes::createFromBase64Data(base64_encode("Hello"));
$out = $bytes->encode();
$this->assertEquals(base64_encode("Hello"), $out["base64"]);
}
diff --git a/tests/LeanClientTest.php b/test/ClientTest.php
similarity index 50%
rename from tests/LeanClientTest.php
rename to test/ClientTest.php
index ac2addd..b6797fe 100644
--- a/tests/LeanClientTest.php
+++ b/test/ClientTest.php
@@ -1,87 +1,112 @@
assertEquals("{$url}/1.1", Client::getAPIEndPoint());
+
+ Client::setServerURL("https://hello.api.lncld.net");
+ $this->assertEquals("https://hello.api.lncld.net/1.1", Client::getAPIEndPoint());
+ Client::setServerURL(null);
+
+ $this->assertEquals("{$url}/1.1", Client::getAPIEndPoint());
}
- public function testGetAPIEndpoint() {
- LeanClient::useRegion("CN");
- $this->assertEquals(LeanClient::getAPIEndpoint(),
- "https://api.leancloud.cn/1.1");
+ public function testVerifyKey() {
+ $result = Client::verifyKey(
+ getenv("LEANCLOUD_APP_ID"),
+ getenv("LEANCLOUD_APP_KEY")
+ );
+ $this->assertTrue($result);
}
- public function testUseInvalidRegion() {
- $this->setExpectedException("RuntimeException", "Invalid API region");
- LeanClient::useRegion("cn-bla");
+ # public function testVerifyKeyMaster() {
+ # $result = Client::verifyKey(
+ # getenv("LEANCLOUD_APP_ID"),
+ # getenv("LEANCLOUD_APP_MASTER_KEY") . ",master"
+ # );
+ # $this->assertTrue($result);
+ # }
+
+ public function testVerifySign() {
+ $time = time();
+ $sign = md5($time . getenv("LEANCLOUD_APP_KEY")) . ",{$time}";
+ $result = Client::verifySign(getenv("LEANCLOUD_APP_ID"), $sign);
+ $this->assertTrue($result);
}
- public function testUseRegion() {
- LeanClient::useRegion("US");
- $this->assertEquals(LeanClient::getAPIEndpoint(),
- "https://us-api.leancloud.cn/1.1");
+ public function testVerifySignMaster() {
+ $time = time();
+ $sign = md5($time . getenv("LEANCLOUD_APP_MASTER_KEY")) . ",{$time},master";
+ $result = Client::verifySign(getenv("LEANCLOUD_APP_ID"), $sign);
+ $this->assertTrue($result);
}
public function testUseMasterKeyByDefault() {
- LeanClient::useMasterKey(true);
- $headers = LeanClient::buildHeaders("token", null);
+ Client::useMasterKey(true);
+ $headers = Client::buildHeaders("token", null);
$this->assertContains("master", $headers["X-LC-Sign"]);
- $headers = LeanClient::buildHeaders("token", true);
+ $headers = Client::buildHeaders("token", true);
$this->assertContains("master", $headers["X-LC-Sign"]);
- $headers = LeanClient::buildHeaders("token", false);
+ $headers = Client::buildHeaders("token", false);
$this->assertNotContains("master", $headers["X-LC-Sign"]);
}
public function testNotUseMasterKeyByDefault() {
- LeanClient::useMasterKey(false);
- $headers = LeanClient::buildHeaders("token", null);
+ Client::useMasterKey(false);
+ $headers = Client::buildHeaders("token", null);
$this->assertNotContains("master", $headers["X-LC-Sign"]);
- $headers = LeanClient::buildHeaders("token", false);
+ $headers = Client::buildHeaders("token", false);
$this->assertNotContains("master", $headers["X-LC-Sign"]);
- $headers = LeanClient::buildHeaders("token", true);
+ $headers = Client::buildHeaders("token", true);
$this->assertContains("master", $headers["X-LC-Sign"]);
}
public function testRequestServerDate() {
- $data = LeanClient::request("GET", "/date", null);
+ $data = Client::request("GET", "/date", null);
$this->assertEquals($data["__type"], "Date");
}
public function testRequestUnauthorized() {
- LeanClient::initialize(getenv("LC_APP_ID"),
+ Client::initialize(getenv("LEANCLOUD_APP_ID"),
"invalid key",
"invalid master key");
$this->setExpectedException("LeanCloud\CloudException", "Unauthorized");
- $data = LeanClient::request("POST",
+ $data = Client::request("POST",
"/classes/TestObject",
array("name" => "alice",
"story" => "in wonderland"));
- LeanClient::delete("/classes/TestObject/{$data['objectId']}");
+ Client::delete("/classes/TestObject/{$data['objectId']}");
}
public function testRequestTestObject() {
- $data = LeanClient::request("POST",
+ $data = Client::request("POST",
"/classes/TestObject",
array(
"name" => "alice",
@@ -89,69 +114,71 @@ public function testRequestTestObject() {
$this->assertArrayHasKey("objectId", $data);
$id = $data["objectId"];
- $data = LeanClient::request("GET",
+ $data = Client::request("GET",
"/classes/TestObject/" . $id,
null);
$this->assertEquals($data["name"], "alice");
- LeanClient::delete("/classes/TestObject/{$data['objectId']}");
+ Client::delete("/classes/TestObject/{$data['objectId']}");
}
public function testPostCreateTestObject() {
- $data = LeanClient::post("/classes/TestObject",
+ $data = Client::post("/classes/TestObject",
array("name" => "alice",
"story" => "in wonderland"));
$this->assertArrayHasKey("objectId", $data);
- LeanClient::delete("/classes/TestObject/{$data['objectId']}");
+ Client::delete("/classes/TestObject/{$data['objectId']}");
}
public function testGetTestObject() {
- $data = LeanClient::post("/classes/TestObject",
+ $data = Client::post("/classes/TestObject",
array("name" => "alice",
"story" => "in wonderland"));
$this->assertArrayHasKey("objectId", $data);
- $obj = LeanClient::get("/classes/TestObject/{$data['objectId']}");
+ $obj = Client::get("/classes/TestObject/{$data['objectId']}");
$this->assertEquals($obj["name"], "alice");
$this->assertEquals($obj["story"], "in wonderland");
- LeanClient::delete("/classes/TestObject/{$obj['objectId']}");
+ Client::delete("/classes/TestObject/{$obj['objectId']}");
}
public function testUpdateTestObject() {
- $data = LeanClient::post("/classes/TestObject",
+ $data = Client::post("/classes/TestObject",
array("name" => "alice",
"story" => "in wonderland"));
$this->assertArrayHasKey("objectId", $data);
- LeanClient::put("/classes/TestObject/{$data['objectId']}",
+ Client::put("/classes/TestObject/{$data['objectId']}",
array("name" => "Hiccup",
"story" => "How to train your dragon"));
- $obj = LeanClient::get("/classes/TestObject/{$data['objectId']}");
+ $obj = Client::get("/classes/TestObject/{$data['objectId']}");
$this->assertEquals($obj["name"], "Hiccup");
$this->assertEquals($obj["story"], "How to train your dragon");
- LeanClient::delete("/classes/TestObject/{$obj['objectId']}");
+ Client::delete("/classes/TestObject/{$obj['objectId']}");
}
public function testDeleteTestObject() {
- $data = LeanClient::post("/classes/TestObject",
+ $data = Client::post("/classes/TestObject",
array("name" => "alice",
"story" => "in wonderland"));
$this->assertArrayHasKey("objectId", $data);
- LeanClient::delete("/classes/TestObject/{$data['objectId']}");
+ Client::delete("/classes/TestObject/{$data['objectId']}");
- $obj = LeanClient::get("/classes/TestObject/{$data['objectId']}");
+ $obj = Client::get("/classes/TestObject/{$data['objectId']}");
$this->assertEmpty($obj);
}
public function testDecodeDate() {
$date = new DateTime();
$type = array("__type" => "Date",
- "iso" => LeanClient::formatDate($date));
- $this->assertEquals($date, LeanClient::decode($type, null));
+ "iso" => Client::formatDate($date));
+ $date2 = Client::decode($type, null);
+ $this->assertEquals($date->getTimestamp(),
+ $date2->getTimestamp());
}
public function testDecodeDateWithTimeZone() {
@@ -160,16 +187,18 @@ public function testDecodeDateWithTimeZone() {
forEach($zones as $zone) {
$date = new DateTime("now", new DateTimeZone($zone));
$type = array("__type" => "Date",
- "iso" => LeanClient::formatDate($date));
- $this->assertEquals($date, LeanClient::decode($type, null));
+ "iso" => Client::formatDate($date));
+ $date2 = Client::decode($type, null);
+ $this->assertEquals($date->getTimestamp(),
+ $date2->getTimestamp());
}
}
public function testDecodeRelation() {
$type = array("__type" => "Relation",
"className" => "TestObject");
- $val = LeanClient::decode($type, null);
- $this->assertTrue($val instanceof LeanRelation);
+ $val = Client::decode($type, null);
+ $this->assertTrue($val instanceof Relation);
$this->assertEquals("TestObject", $val->getTargetClassName());
}
@@ -177,7 +206,7 @@ public function testDecodePointer() {
$type = array("__type" => "Pointer",
"className" => "TestObject",
"objectId" => "abc101");
- $val = LeanClient::decode($type, null);
+ $val = Client::decode($type, null);
$this->assertTrue($val instanceof LeanObject);
$this->assertEquals("TestObject", $val->getClassName());
@@ -189,7 +218,7 @@ public function testDecodeObject() {
"objectId" => "abc101",
"name" => "alice",
"tags" => array("fiction", "bar"));
- $val = LeanClient::decode($type, null);
+ $val = Client::decode($type, null);
$this->assertTrue($val instanceof LeanObject);
$this->assertEquals("TestObject", $val->getClassName());
@@ -200,8 +229,8 @@ public function testDecodeObject() {
public function testDecodeBytes() {
$type = array("__type" => "Bytes",
"base64" => base64_encode("Hello"));
- $val = LeanClient::decode($type, null);
- $this->assertTrue($val instanceof LeanBytes);
+ $val = Client::decode($type, null);
+ $this->assertTrue($val instanceof Bytes);
$this->assertEquals(array(72, 101, 108, 108, 111),
$val->getByteArray());
}
@@ -212,9 +241,9 @@ public function testDecodeUserObject() {
"objectId" => "abc101",
"username" => "alice",
"email" => "alice@example.com");
- $val = LeanClient::decode($type, null);
+ $val = Client::decode($type, null);
- $this->assertTrue($val instanceof LeanUser);
+ $this->assertTrue($val instanceof User);
$this->assertEquals($type["objectId"], $val->getObjectId());
$this->assertEquals($type["username"], $val->getUsername());
$this->assertEquals($type["email"], $val->getEmail());
@@ -224,9 +253,9 @@ public function testDecodeUserPointer() {
$type = array("__type" => "Pointer",
"className" => "_User",
"objectId" => "abc101");
- $val = LeanClient::decode($type, null);
+ $val = Client::decode($type, null);
- $this->assertTrue($val instanceof LeanUser);
+ $this->assertTrue($val instanceof User);
$this->assertEquals($type["objectId"], $val->getObjectId());
}
@@ -235,9 +264,9 @@ public function testDecodeFile() {
"objectId" => "abc101",
"name" => "favicon.ico",
"url" => "https://leancloud.cn/favicon.ico");
- $val = LeanClient::decode($type, null);
+ $val = Client::decode($type, null);
- $this->assertTrue($val instanceof LeanFile);
+ $this->assertTrue($val instanceof File);
$this->assertEquals($type["objectId"], $val->getObjectId());
$this->assertEquals($type["name"], $val->getName());
$this->assertEquals($type["url"], $val->getUrl());
@@ -249,8 +278,8 @@ public function testDecodeACL() {
"user123" => array("write" => true),
"role:admin" => array("write" => true)
);
- $val = LeanClient::decode($type, 'ACL');
- $this->assertTrue($val instanceof LeanACL);
+ $val = Client::decode($type, 'ACL');
+ $this->assertTrue($val instanceof ACL);
$this->assertTrue($val->getPublicReadAccess());
$this->assertFalse($val->getPublicWriteAccess());
$this->assertTrue($val->getRoleWriteAccess("admin"));
@@ -277,15 +306,15 @@ public function testDecodeRecursiveObjectWithACL() {
'ACL' => $acl
)
);
- $val = LeanClient::decode($type, null);
+ $val = Client::decode($type, null);
$this->assertTrue($val instanceof LeanObject);
$this->assertEquals('alice', $val->get('name'));
- $this->assertTrue($val->getACL() instanceof LeanACL);
+ $this->assertTrue($val->getACL() instanceof ACL);
$parent = $val->get("parent");
$this->assertTrue($parent instanceof LeanObject);
$this->assertEquals('jill', $parent->get('name'));
- $this->assertTrue($parent->getACL() instanceof LeanACL);
+ $this->assertTrue($parent->getACL() instanceof ACL);
}
/*
@@ -296,7 +325,7 @@ public function testDecodeRecursiveObjectWithACL() {
* @link bug #43: https://github.com/leancloud/php-sdk/issues/43
*/
public function testDecodeIndexedArrayValue() {
- $val = LeanClient::decode(array(
+ $val = Client::decode(array(
'__type' => 'Pointer',
'className' => 'TestObject',
'objectId' => '5682bd'
@@ -310,11 +339,100 @@ public function testDecodeGeoPoint() {
'latitude' => 39.9,
'longitude' => 116.4
);
- $val = LeanClient::decode($type, null);
+ $val = Client::decode($type, null);
$this->assertTrue($val instanceof GeoPoint);
$this->assertEquals(39.9, $val->getLatitude());
$this->assertEquals(116.4, $val->getLongitude());
}
+
+ public function testEncodeRelation() {
+ $a = new LeanObject("TestObject", "id001");
+ $rel = $a->getRelation("likes");
+ $out = Client::encode($rel);
+ $this->assertEquals("Relation",
+ $out["__type"]);
+ }
+
+ public function testEncodeObjectToJSON() {
+ $a = new LeanObject("TestObject", "id001");
+ $b = new LeanObject("TestObject", "id002");
+ $a->set("name", "A");
+ $b->set("name", "B");
+ $a->addIn("likes", $b);
+ $jsonA = Client::encode($a, "toJSON");
+ $jsonB = $jsonA["likes"][0];
+ // top level object A will be encoded as literal json
+ $this->assertEquals("A", $jsonA["name"]);
+ $this->assertEquals("id001", $jsonA["objectId"]);
+ $this->assertEquals("B", $jsonB["name"]);
+ $this->assertEquals("id002", $jsonB["objectId"]);
+ $this->assertEquals("Object", $jsonB["__type"]);
+ $this->assertEquals("TestObject", $jsonB["className"]);
+
+ $this->assertArrayNotHasKey("__type", $jsonA);
+ $this->assertArrayNotHasKey("className", $jsonA);
+ }
+
+ public function testEncodeObjectToFullJSON() {
+ $a = new LeanObject("TestObject", "id001");
+ $b = new LeanObject("TestObject", "id002");
+ $a->set("name", "A");
+ $b->set("name", "B");
+ $a->addIn("likes", $b);
+ $jsonA = Client::encode($a, "toFullJSON");
+ $jsonB = $jsonA["likes"][0];
+ $this->assertEquals("A", $jsonA["name"]);
+ $this->assertEquals("id001", $jsonA["objectId"]);
+ $this->assertEquals("Object", $jsonA["__type"]);
+ $this->assertEquals("TestObject", $jsonA["className"]);
+ $this->assertEquals("B", $jsonB["name"]);
+ $this->assertEquals("id002", $jsonB["objectId"]);
+ $this->assertEquals("Object", $jsonB["__type"]);
+ $this->assertEquals("TestObject", $jsonB["className"]);
+ }
+
+ public function testEncodeCircularObjectAsPointer() {
+ $a = new LeanObject("TestObject", "id001");
+ $b = new LeanObject("TestObject", "id002");
+ $c = new LeanObject("TestObject", "id003");
+ $a->set("name", "A");
+ $b->set("name", "B");
+ $c->set("name", "C");
+ $a->addIn("likes", $b);
+ $b->addIn("likes", $c);
+ $c->addIn("likes", $a);
+ $jsonA = Client::encode($a, "toFullJSON");
+ $jsonB = $jsonA["likes"][0];
+ $jsonC = $jsonB["likes"][0];
+
+ $this->assertEquals("Object", $jsonA["__type"]);
+ $this->assertEquals("Object", $jsonB["__type"]);
+ $this->assertEquals("Object", $jsonC["__type"]);
+ $this->assertEquals("Pointer", $jsonC["likes"][0]["__type"]);
+ }
+
+ public function testEncodePointerObject() {
+ $json = array(
+ "__type" => "Object",
+ "objectId" => "id001",
+ "className" => "TestObject",
+ "name" => "A",
+ "likes" => array(
+ "__type" => "Pointer",
+ "objectId" => "id002",
+ "className" => "TestObject"
+ )
+ );
+ $a = Client::decode($json, null);
+ $this->assertTrue($a instanceof LeanObject);
+ $this->assertTrue($a->get("likes") instanceof LeanObject);
+
+ $out = $a->toFullJSON();
+ $this->assertEquals("A", $out["name"]);
+ $this->assertEquals("Pointer", $out["likes"]["__type"]);
+ $this->assertEquals("TestObject", $out["likes"]["className"]);
+ }
+
}
diff --git a/test/CloudTest.php b/test/CloudTest.php
new file mode 100644
index 0000000..48dfd8c
--- /dev/null
+++ b/test/CloudTest.php
@@ -0,0 +1,154 @@
+setUsername("alice");
+ $user->setPassword("blabla");
+ $user->setEmail("alice@example.com");
+ try {
+ $user->signUp();
+ } catch (CloudException $ex) {
+ // skip
+ }
+ }
+
+ public static function tearDownAfterClass() {
+ // destroy default user if present
+ try {
+ $user = User::logIn("alice", "blabla");
+ $user->destroy();
+ } catch (CloudException $ex) {
+ // skip
+ }
+ }
+
+
+
+ public function testGetKeys() {
+ $name = uniqid();
+ Cloud::define($name, function($params, $user) {
+ return "hello";
+ });
+ $this->assertContains($name, Cloud::getKeys());
+ }
+
+ public function testDefineFunctionWithoutArg() {
+ // user function are free to accept positional arguments,
+ // this one should not error out.
+ Cloud::define("hello", function() {
+ return "hello";
+ });
+ $result = Cloud::run("hello", array("name" => "alice"), null);
+ $this->assertEquals("hello", $result);
+ }
+
+ public function testFunctionWithoutArg() {
+ Cloud::define("hello", function($params, $user) {
+ return "hello";
+ });
+
+ $result = Cloud::run("hello", array(), null);
+ $this->assertEquals("hello", $result);
+ }
+
+ public function testFunctionWithArg() {
+ Cloud::define("sayHello", function($params, $user) {
+ return "hello {$params['name']}";
+ });
+
+ $result = Cloud::run("sayHello", array("name" => "alice"), null);
+ $this->assertEquals("hello alice", $result);
+ }
+
+ public function testFunctionAcceptMeta() {
+ Cloud::define("getMeta", function($params, $user, $meta) {
+ return $meta['remoteAddress'];
+ });
+
+ $result = Cloud::run("getMeta",
+ array("name" => "alice"),
+ null,
+ array("remoteAddress" => "10.0.0.1")
+ );
+ $this->assertEquals("10.0.0.1", $result);
+ }
+
+ public function testRemoteFunction() {
+ // Assumes [LeanFunction] is deployed at this application's LeanEngine.
+ // [LeanFunction]: https://github.com/leancloud/LeanFunction
+ $response = Cloud::runRemote("hello", []);
+ $result = $response["result"];
+ $this->assertEquals("Hello world!", $result);
+ }
+
+ public function testRemoteFunctionWithSession() {
+ // See testRemoteFunction for dependencies.
+ try {
+ User::logIn("alice", "blabla");
+ } catch (\LeanCloud\CloudException $e) {
+ // skip
+ }
+ $token = User::getCurrentSessionToken();
+ $response = Cloud::runRemote("echo-session-token", [], $token);
+ $result = $response["result"];
+ $this->assertEquals($token, $result);
+ }
+
+ public function testClassHook() {
+ forEach(array("beforeSave", "afterSave",
+ "beforeUpdate", "afterUpdate",
+ "beforeDelete", "afterDelete") as $hookName) {
+ $count = 42;
+ call_user_func(
+ array("LeanCloud\Engine\Cloud", $hookName),
+ "TestObject",
+ function($obj, $user) use (&$count) {
+ $count += 1;
+ }
+ );
+ Cloud::runHook("TestObject", $hookName, null, null);
+ $this->assertEquals(43, $count);
+ }
+ }
+
+ public function testOnVerifiedHook() {
+ // use a closure to ensure hook being executed
+ $count = 42;
+ Cloud::onVerified("sms", function($user) use (&$count) {
+ $count += 1;
+ });
+ Cloud::runOnVerified("sms", null);
+ $this->assertEquals(43, $count);
+ }
+
+ public function testOnLogin() {
+ $count = 42;
+ Cloud::onLogin(function($user) use (&$count) {
+ $count += 1;
+ });
+ Cloud::runOnLogin(null);
+ $this->assertEquals(43, $count);
+ }
+
+ public function testOnInsight() {
+ $count = 42;
+ Cloud::onInsight(function($job) use (&$count) {
+ $count += 1;
+ });
+ Cloud::runOnInsight(null);
+ $this->assertEquals(43, $count);
+ }
+
+}
+
diff --git a/tests/DeleteOperationTest.php b/test/DeleteOperationTest.php
similarity index 94%
rename from tests/DeleteOperationTest.php
rename to test/DeleteOperationTest.php
index f33473f..bc4f928 100644
--- a/tests/DeleteOperationTest.php
+++ b/test/DeleteOperationTest.php
@@ -4,8 +4,9 @@
use LeanCloud\Operation\SetOperation;
use LeanCloud\Operation\IncrementOperation;
use LeanCloud\Operation\ArrayOperation;
+use PHPUnit\Framework\TestCase;
-class DeleteOperationTest extends PHPUnit_Framework_TestCase {
+class DeleteOperationTest extends TestCase {
public function testOperationEncode() {
$op = new DeleteOperation("tags");
$out = $op->encode();
diff --git a/tests/LeanFileTest.php b/test/FileTest.php
similarity index 50%
rename from tests/LeanFileTest.php
rename to test/FileTest.php
index 7abdd7b..014f62a 100644
--- a/tests/LeanFileTest.php
+++ b/test/FileTest.php
@@ -1,42 +1,49 @@
assertEquals("", $file->getName());
}
public function testInitializeMimeType() {
- $file = new LeanFile("test.txt");
+ $file = new File("test.txt");
$this->assertEquals("text/plain", $file->getMimeType());
- $file = new LeanFile("test.txt", null, "image/png");
+ $file = new File("test.txt", null, "image/png");
$this->assertEquals("image/png", $file->getMimeType());
}
public function testCreateWithURL() {
- $file = LeanFile::createWithUrl("blabla.png", "https://leancloud.cn/favicon.png");
+ $file = File::createWithUrl("blabla.png", "https://leancloud.cn/favicon.png");
$this->assertEquals("blabla.png", $file->getName());
$this->assertEquals("https://leancloud.cn/favicon.png", $file->getUrl());
$this->assertEquals("image/png", $file->getMimeType());
}
+ public function testCreateWithLocalFile() {
+ $file = File::createWithLocalFile(__FILE__);
+ $this->assertEquals("FileTest.php", $file->getName());
+ }
+
public function testSaveTextFile() {
- $file = LeanFile::createWithData("test.txt", "Hello World!");
+ $file = File::createWithData("test.txt", "Hello World!");
+ $this->assertNull($file->getKey());
$file->save();
$this->assertNotEmpty($file->getObjectId());
+ $this->assertNotEmpty($file->getKey());
$this->assertNotEmpty($file->getUrl());
$this->assertNotEmpty($file->getName());
$this->assertEquals("text/plain", $file->getMimeType());
@@ -47,7 +54,7 @@ public function testSaveTextFile() {
}
public function testSaveUTF8TextFile() {
- $file = LeanFile::createWithData("test.txt", "你好,中国!");
+ $file = File::createWithData("testChinese.txt", "你好,中国!");
$file->save();
$this->assertNotEmpty($file->getUrl());
$this->assertEquals("text/plain", $file->getMimeType());
@@ -56,12 +63,43 @@ public function testSaveUTF8TextFile() {
$file->destroy();
}
+
+ public function testSaveLocalFileWithMimeTypeAndName() {
+ $file = File::createWithLocalFile(__FILE__, "application/x-php", "FileTest.php");
+ $file->save();
+ $this->assertNotEmpty($file->getUrl());
+ $this->assertEquals("application/x-php", $file->getMimeType());
+
+ $file->destroy();
+ }
+
+ public function testSaveWithSpecifiedKeyWithoutMasterKey() {
+ $file = File::createWithData("test.txt", "Hello World!");
+ $file->setKey("abc");
+ $this->assertEquals("abc", $file->getKey());
+ $unsupportedKeyError = "Unsupported file key. Please use masterKey to set file key.";
+ $this->setExpectedException("LeanCloud\CloudException", $unsupportedKeyError, 1);
+ $file->save();
+ $this->assertEmpty($file->getObjectId());
+ }
+
+ public function testSaveExternalFile() {
+ $file = File::createWithUrl("blabla.png", "https://leancloud.cn/favicon.png");
+ $file->save();
+ $this->assertNotEmpty($file->getObjectId());
+ $this->assertEquals("blabla.png", $file->getName());
+ $this->assertEquals("https://leancloud.cn/favicon.png", $file->getUrl());
+ $this->assertEquals("image/png", $file->getMimeType());
+
+ $file->destroy();
+ }
public function testFetchFile() {
- $file = LeanFile::createWithData("test.txt", "你好,中国!");
+ $file = File::createWithData("testFetch.txt", "你好,中国!");
$file->save();
- $file2 = LeanFile::fetch($file->getObjectId());
- $this->assertEquals($file->getUrl(), $file2->getUrl());
+ $file2 = File::fetch($file->getObjectId());
+ // `uploadResult.getUrl() != fetchResult.getUrl()` is a feature
+ // $this->assertEquals($file->getUrl(), $file2->getUrl());
$this->assertEquals($file->getName(), $file2->getName());
$this->assertEquals($file->getSize(), $file2->getSize());
@@ -69,7 +107,7 @@ public function testFetchFile() {
}
public function testGetCreatedAtAndUpdatedAt() {
- $file = LeanFile::createWithData("test.txt", "你好,中国!");
+ $file = File::createWithData("testTimestamp.txt", "你好,中国!");
$file->save();
$this->assertNotEmpty($file->getUrl());
$this->assertNotEmpty($file->getCreatedAt());
@@ -79,12 +117,12 @@ public function testGetCreatedAtAndUpdatedAt() {
}
public function testMetaData() {
- $file = LeanFile::createWithData("test.txt", "你好,中国!");
+ $file = File::createWithData("testMetadata.txt", "你好,中国!");
$file->setMeta("language", "zh-CN");
$file->setMeta("bool", false);
$file->setMeta("downloads", 100);
$file->save();
- $file2 = LeanFile::fetch($file->getObjectId());
+ $file2 = File::fetch($file->getObjectId());
$this->assertEquals("zh-CN", $file2->getMeta("language"));
$this->assertEquals(false, $file2->getMeta("bool"));
$this->assertEquals(100, $file2->getMeta("downloads"));
@@ -99,7 +137,7 @@ public function testSaveObjectWithFile() {
$obj = new LeanObject("TestObject");
$obj->set("name", "alice");
- $file = LeanFile::createWithData("test.txt", "你好,中国!");
+ $file = File::createWithData("test.txt", "你好,中国!");
$obj->addIn("files", $file);
$obj->save();
diff --git a/tests/GeoPointTest.php b/test/GeoPointTest.php
similarity index 95%
rename from tests/GeoPointTest.php
rename to test/GeoPointTest.php
index 7454cbf..f004b30 100644
--- a/tests/GeoPointTest.php
+++ b/test/GeoPointTest.php
@@ -1,8 +1,9 @@
assertEquals(39.9 * M_PI / 180.0, $rad, '', 0.0000001);
}
-}
\ No newline at end of file
+}
diff --git a/tests/IncrementOperationTest.php b/test/IncrementOperationTest.php
similarity index 97%
rename from tests/IncrementOperationTest.php
rename to test/IncrementOperationTest.php
index 0e036f3..d7b4c73 100644
--- a/tests/IncrementOperationTest.php
+++ b/test/IncrementOperationTest.php
@@ -2,8 +2,9 @@
use LeanCloud\Operation\SetOperation;
use LeanCloud\Operation\IncrementOperation;
use LeanCloud\Operation\DeleteOperation;
+use PHPUnit\Framework\TestCase;
-class IncrementOperationTest extends PHPUnit_Framework_TestCase {
+class IncrementOperationTest extends TestCase {
public function testGetKey() {
$op = new IncrementOperation("score", 1);
$this->assertEquals($op->getKey(), "score");
diff --git a/tests/LeanObjectTest.php b/test/LeanObjectTest.php
similarity index 78%
rename from tests/LeanObjectTest.php
rename to test/LeanObjectTest.php
index e5580de..369ea25 100644
--- a/tests/LeanObjectTest.php
+++ b/test/LeanObjectTest.php
@@ -1,24 +1,34 @@
set("title", $title);
+ }
+
+ public function getTitle() {
+ return $this->get("title");
+ }
}
Movie::registerClass();
-class LeanObjectTest extends PHPUnit_Framework_TestCase {
+class LeanObjectTest extends TestCase {
public static function setUpBeforeClass() {
- LeanClient::initialize(
- getenv("LC_APP_ID"),
- getenv("LC_APP_KEY"),
- getenv("LC_APP_MASTER_KEY"));
- LeanClient::useRegion(getenv("LC_API_REGION"));
- LeanClient::setStorage(new SessionStorage());
+ Client::initialize(
+ getenv("LEANCLOUD_APP_ID"),
+ getenv("LEANCLOUD_APP_KEY"),
+ getenv("LEANCLOUD_APP_MASTER_KEY"));
+
+ Client::setStorage(new SessionStorage());
}
public function testInitializePlainObjectWithoutName() {
@@ -64,6 +74,13 @@ public function testeSetPreservedField() {
}
}
+ public function testCreateSubObject() {
+ $movie = LeanObject::create("Movie", "objid");
+ $this->assertTrue($movie instanceof Movie);
+ $movie->setTitle("Alice in wonderland");
+ $this->assertEquals("Alice in wonderland", $movie->getTitle());
+ }
+
public function testIncrement() {
$movie = new Movie();
$movie->set("score", 60);
@@ -122,6 +139,52 @@ public function testSaveExistingObject() {
$obj->destroy();
}
+ public function testSaveOptionEncode() {
+ $option = new SaveOption();
+ $this->assertEquals(array(), $option->encode());
+ $option->fetchWhenSave = true;
+ $this->assertEquals(array("fetchWhenSave" => true), $option->encode());
+ }
+
+ public function testFetchWhenSave() {
+ $obj = new LeanObject("TestObject");
+ $obj->set("score", 1);
+ $obj->save();
+ $this->assertNotEmpty($obj->getObjectId());
+ $obj2 = new LeanObject("TestObject", $obj->getObjectId());
+ $obj2->increment("score");
+
+ $option = new SaveOption();
+ $option->fetchWhenSave = true;
+ $obj2->save($option);
+ $this->assertEquals(2, $obj2->get("score"));
+
+ $obj->set("name", "Alice in wonderland");
+ $obj->increment("score");
+ $obj->save($option);
+ $this->assertEquals(3, $obj->get("score"));
+ }
+
+ public function testSaveWhenWhere() {
+ $obj = new LeanObject("TestObject");
+ $obj->set("score", 6);
+ $obj->save();
+ $this->assertNotEmpty($obj->getObjectId());
+ $obj->set("level", "good");
+ $query = new Query("TestObject");
+ $query->greaterThanOrEqualTo("score",8);
+ $option = new SaveOption();
+ $option->where = $query;
+ $this->setExpectedException("LeanCloud\CloudException");
+ $obj->save($option);
+
+ $query->greaterThanOrEqualTo("score",6);
+ $option->where = $query;
+ $obj->increment("score");
+ $obj->save($option);
+ $this->assertEquals(7, $obj->get("score"));
+ }
+
public function testGetCreatedAtAndUpdatedAt() {
$obj = new LeanObject("TestObject");
$obj->set("foo", "bar");
@@ -163,7 +226,8 @@ public function testGetDateShouldReturnDateTime() {
$obj2 = new LeanObject("TestObject", $obj->getObjectId());
$obj2->fetch();
$this->assertTrue($obj2->get("release") instanceof DateTime);
- $this->assertEquals($obj->get("release"), $obj2->get("release"));
+ $this->assertEquals($obj->get("release")->getTimestamp(),
+ $obj2->get("release")->getTimestamp());
$obj2->destroy();
}
@@ -182,7 +246,7 @@ public function testRelationDecode() {
$a2 = new LeanObject("TestObject", $a->getObjectId());
$a2->fetch();
$val = $a2->get("likes_relation");
- $this->assertTrue($val instanceof LeanRelation);
+ $this->assertTrue($val instanceof Relation);
$this->assertEquals("TestObject", $val->getTargetClassName());
LeanObject::destroyAll(array($a, $b));
@@ -254,8 +318,6 @@ public function testDestroyObject() {
$this->assertNotEmpty($obj->getObjectId());
$obj->destroy();
- $this->setExpectedException("LeanCloud\CloudException");
- $obj->fetch();
}
/**
@@ -271,7 +333,7 @@ public function testAddRelation() {
$this->assertEquals("TestAuthor", $out["className"]);
$val = $obj->get("authors");
- $this->assertTrue($val instanceof LeanRelation);
+ $this->assertTrue($val instanceof Relation);
$out = $val->encode();
$this->assertEquals("Relation", $out["__type"]);
$this->assertEquals("TestAuthor", $out["className"]);
@@ -357,7 +419,7 @@ public function testSaveWithNewGrandChildren() {
$b->set("likes", array($c, 42));
$this->setExpectedException("RuntimeException",
- "Object without ID cannot be serialized.");
+ "LeanObject without ID cannot be serialized.");
$a->save();
}
@@ -374,5 +436,26 @@ public function testSetGeoPoint() {
$this->assertEquals(116.4, $loc->getLongitude());
}
-}
+ public function testGeoPointLocation() {
+ $point = new GeoPoint(25.269876, 110.333061);
+
+ $location = new LeanObject("Location");
+ $location->set("location", $point);
+ $location->save();
+
+ $location->destroy();
+ }
+ public function testPointerObjectHasNoData() {
+ $json = array(
+ "__type" => "Pointer",
+ "className" => "TestObject",
+ "objectId" => "id001"
+ );
+ $obj = Client::decode($json, null);
+ $this->assertTrue($obj instanceof LeanObject);
+ $this->assertEquals("id001", $obj->getObjectId());
+
+ $this->assertFalse($obj->hasData());
+ }
+}
diff --git a/test/MasterTest.php b/test/MasterTest.php
new file mode 100644
index 0000000..6aaa07e
--- /dev/null
+++ b/test/MasterTest.php
@@ -0,0 +1,37 @@
+setKey("abc");
+ $this->assertEquals("abc", $file->getKey());
+ $file->save();
+ $this->assertNotEmpty($file->getObjectId());
+ $this->assertNotEmpty($file->getName());
+
+ $this->assertStringEndsWith("abc", $file->getKey());
+ $url = $file->getUrl();
+ $parsedUrl = parse_url($url);
+ $path = $parsedUrl["path"];
+ $this->assertStringEndsWith("abc", $path);
+
+ $this->assertEquals("text/plain", $file->getMimeType());
+ $content = file_get_contents($url);
+ $this->assertEquals("Hello World!", $content);
+
+ $file->destroy();
+ }
+}
diff --git a/test/Php72ObjectDeprecated.php b/test/Php72ObjectDeprecated.php
new file mode 100644
index 0000000..47ff9a9
--- /dev/null
+++ b/test/Php72ObjectDeprecated.php
@@ -0,0 +1,34 @@
+set("name", "Alice in wonderland");
+ $obj->set("score", 81);
+ $obj->save();
+
+ $this->assertTrue($obj instanceof Object);
+ $this->assertTrue($obj instanceof LeanObject);
+ $this->assertNotEmpty($obj->getObjectId());
+
+ LeanObject::destroyAll([$obj]);
+ }
+}
diff --git a/tests/LeanPushTest.php b/test/PushTest.php
similarity index 60%
rename from tests/LeanPushTest.php
rename to test/PushTest.php
index 8cb135e..e74de80 100644
--- a/tests/LeanPushTest.php
+++ b/test/PushTest.php
@@ -1,23 +1,34 @@
"Hello world!",
"badge" => 20,
"sound" => "APP/media/sound.mp3"
);
- $push = new LeanPush($data);
+ $push = new Push($data);
$out = $push->encode();
$this->assertEquals($data, $out["data"]);
}
public function testSetData() {
- $push = new LeanPush(array(
+ $push = new Push(array(
"alert" => "Hello world!"
));
$push->setData("badge", 20);
@@ -48,13 +59,21 @@ public function testSetPushForMultiplatform() {
"wp-param" => "/chat.xaml?NavigatedFrom=Toast Notification"
)
);
- $push = new LeanPush($data);
+ $push = new Push($data);
$out = $push->encode();
$this->assertEquals($data, $out["data"]);
}
+ public function testDefaultProd() {
+ $push = new Push(array(
+ "alert" => "Hello world!"
+ ));
+ $out = $push->encode();
+ $this->assertEquals(Client::$isProduction, $out["prod"] == "prod");
+ }
+
public function testSetProd() {
- $push = new LeanPush(array(
+ $push = new Push(array(
"alert" => "Hello world!"
));
$push->setOption("prod", "dev");
@@ -63,7 +82,7 @@ public function testSetProd() {
}
public function testSetChannels() {
- $push = new LeanPush(array(
+ $push = new Push(array(
"alert" => "Hello world!"
));
$channels = array("vip", "premium");
@@ -73,17 +92,18 @@ public function testSetChannels() {
}
public function testSetPushTime() {
- $push = new LeanPush(array(
+ $push = new Push(array(
"alert" => "Hello world!"
));
$time = new DateTime();
$push->setPushTime($time);
$out = $push->encode();
- $this->assertEquals($time, $out["push_time"]);
+ $time2 = new DateTime($out["push_time"]);
+ $this->assertEquals($time->getTimestamp(), $time2->getTimestamp());
}
public function testSetExpirationInterval() {
- $push = new LeanPush(array(
+ $push = new Push(array(
"alert" => "Hello world!"
));
$push->setExpirationInterval(86400);
@@ -92,28 +112,53 @@ public function testSetExpirationInterval() {
}
public function testSetExpirationTime() {
- $push = new LeanPush(array(
+ $push = new Push(array(
"alert" => "Hello world!"
));
$date = new DateTime();
$push->setExpirationTime($date);
$out = $push->encode();
- $this->assertEquals($date, $out["expiration_time"]);
+ $date2 = new DateTime($out["expiration_time"]);
+ $this->assertEquals($date->getTimestamp(), $date2->getTimestamp());
}
public function testSetWhere() {
- $push = new LeanPush(array(
+ $push = new Push(array(
"alert" => "Hello world!"
));
- $query = new LeanQuery("_Installation");
+ $query = new Query("_Installation");
$date = new DateTime();
$query->lessThan("updatedAt", $date);
$push->setWhere($query);
$out = $push->encode();
$this->assertEquals(array(
"updatedAt" => array(
- '$lt' => LeanClient::encode($date)
+ '$lt' => Client::encode($date)
)
), $out["where"]);
}
-}
\ No newline at end of file
+
+ public function testSetFlowControl() {
+ $push = new Push(array(
+ "alert" => "Hello world!"
+ ));
+ $push->setFlowControl(3000);
+ $out = $push->encode();
+ $this->assertEquals(3000, $out["flow_control"]);
+ }
+
+ public function testSendPush() {
+ $push = new Push(array(
+ "alert" => "Hello world!"
+ ));
+ $query = new Query("_Installation");
+ $query->equalTo("deviceType", "Android");
+ $push->setWhere($query);
+
+ $at = new DateTime();
+ $at->add(new DateInterval("P1D"));
+ $push->setPushTime($at);
+
+ // $push->send();
+ }
+}
diff --git a/tests/LeanQueryTest.php b/test/QueryTest.php
similarity index 85%
rename from tests/LeanQueryTest.php
rename to test/QueryTest.php
index 242f594..dabede0 100644
--- a/tests/LeanQueryTest.php
+++ b/test/QueryTest.php
@@ -1,33 +1,34 @@
assertEquals("TestObject", $query->getClassName());
}
public function testEmptyQuery() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$out = $query->encode();
$this->assertEmpty($out);
}
public function testCount() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$cnt = $query->count();
$this->assertGreaterThanOrEqual(0, $cnt);
@@ -49,7 +50,7 @@ public function testGetById() {
$obj->set("testid", $id);
$obj->save();
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$obj2 = $query->get($obj->getObjectId());
$this->assertEquals($obj->get("testid"),
$obj2->get("testid"));
@@ -63,7 +64,7 @@ public function testFind() {
$obj->set("testid", $id);
$obj->save();
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->equalTo("testid", $id);
$objects = $query->find();
$this->assertEquals(1, count($objects));
@@ -73,7 +74,7 @@ public function testFind() {
}
public function testAddExtraOption() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->equalTo("testid", microtime());
$query->addOption("redirectClassNameForKey", "relationKey");
$out = $query->encode();
@@ -81,7 +82,7 @@ public function testAddExtraOption() {
}
public function testAddExtraOptionCannotOverwitePreservedOption() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->skip(100);
$query->addOption("skip", 50);
$out = $query->encode();
@@ -89,7 +90,7 @@ public function testAddExtraOptionCannotOverwitePreservedOption() {
}
public function testEqualTo() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->equalTo("age", 24);
$out = $query->encode();
$this->assertEquals(json_encode(array("age" => 24)), $out["where"]);
@@ -100,7 +101,7 @@ public function testEqualTo() {
}
public function testNotEqualTo() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->notEqualTo("age", 24);
$out = $query->encode();
$expect = json_encode(array("age" => array('$ne' => 24)));
@@ -110,7 +111,7 @@ public function testNotEqualTo() {
// Only the last will survive when repeatedly applying not-equal-to
// on same field.
public function testRepeatNotEqualTo() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->notEqualTo("age", 24);
$query->notEqualTo("age", 20);
$query->notEqualTo("age", 22);
@@ -121,7 +122,7 @@ public function testRepeatNotEqualTo() {
}
public function testLessThan() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->lessThan("age", 24);
$out = $query->encode();
$expect = json_encode(array("age" => array('$lt' => 24)));
@@ -129,7 +130,7 @@ public function testLessThan() {
}
public function testLessThanOrEqualTo() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->lessThanOrEqualTo("age", 24);
$out = $query->encode();
$expect = json_encode(array("age" => array('$lte' => 24)));
@@ -137,7 +138,7 @@ public function testLessThanOrEqualTo() {
}
public function testGreaterThan() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->greaterThan("age", 24);
$out = $query->encode();
$expect = json_encode(array("age" => array('$gt' => 24)));
@@ -145,7 +146,7 @@ public function testGreaterThan() {
}
public function testGreaterThanOrEqualTo() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->greaterThanOrEqualTo("age", 24);
$out = $query->encode();
$expect = json_encode(array("age" => array('$gte' => 24)));
@@ -153,7 +154,7 @@ public function testGreaterThanOrEqualTo() {
}
public function testContainedIn() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->containedIn("category", array("foo", "bar"));
$out = $query->encode();
$expect = json_encode(array("category" =>
@@ -162,7 +163,7 @@ public function testContainedIn() {
}
public function testNotContainedIn() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->notContainedIn("category", array("foo", "bar"));
$out = $query->encode();
$expect = json_encode(array("category" =>
@@ -171,7 +172,7 @@ public function testNotContainedIn() {
}
public function testContainsAll() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->containsAll("tags", array("foo", "bar"));
$out = $query->encode();
$expect = json_encode(array("tags" =>
@@ -180,7 +181,7 @@ public function testContainsAll() {
}
public function testSizeEqualTo() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->sizeEqualTo("tags", 2);
$out = $query->encode();
$expect = json_encode(array("tags" => array('$size' => 2)));
@@ -188,7 +189,7 @@ public function testSizeEqualTo() {
}
public function testFieldExists() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->exists("tags");
$out = $query->encode();
$expect = json_encode(array("tags" => array('$exists' => true)));
@@ -196,7 +197,7 @@ public function testFieldExists() {
}
public function testFieldNotExists() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->notExists("tags");
$out = $query->encode();
$expect = json_encode(array("tags" => array('$exists' => false)));
@@ -204,7 +205,7 @@ public function testFieldNotExists() {
}
public function testFieldContains() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->contains("title", "clojure");
$out = $query->encode();
$expect = json_encode(array("title" =>
@@ -213,7 +214,7 @@ public function testFieldContains() {
}
public function testStartsWith() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->startsWith("title", "clojure");
$out = $query->encode();
$expect = json_encode(array("title" =>
@@ -222,7 +223,7 @@ public function testStartsWith() {
}
public function testEndsWith() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->endsWith("title", "clojure");
$out = $query->encode();
$expect = json_encode(array("title" =>
@@ -231,7 +232,7 @@ public function testEndsWith() {
}
public function testRegexMatches() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->matches("title", '(cl.?jre)[0-9]', "im");
$out = $query->encode();
$expect = json_encode(array("title" =>
@@ -242,13 +243,13 @@ public function testRegexMatches() {
public function testMatchesInQuery() {
- $q1 = new LeanQuery("Post");
+ $q1 = new Query("Post");
$q1->exists("image");
$out1 = $q1->encode();
$where1 = array("image" => array('$exists' => true));
$this->assertEquals(json_encode($where1), $out1["where"]);
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->matchesInQuery("post", $q1);
$out = $query->encode();
$where = array("post" => array('$inQuery' => array(
@@ -257,7 +258,7 @@ public function testMatchesInQuery() {
)));
$this->assertEquals(json_encode($where), $out["where"]);
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->notMatchInQuery("post", $q1);
$out = $query->encode();
$where = array("post" => array('$notInQuery' => array(
@@ -268,13 +269,13 @@ public function testMatchesInQuery() {
}
public function testMatchesFieldInQuery() {
- $q1 = new LeanQuery("Post");
+ $q1 = new Query("Post");
$q1->contains("title", "clojure");
$out1 = $q1->encode();
$where1 = array("title" => array('$regex' => "clojure"));
$this->assertEquals(json_encode($where1), $out1["where"]);
- $query = new LeanQuery("Comment");
+ $query = new Query("Comment");
$query->matchesFieldInQuery("author", "author", $q1);
$out = $query->encode();
$where = array("author" => array('$select' => array(
@@ -286,7 +287,7 @@ public function testMatchesFieldInQuery() {
)));
$this->assertEquals(json_encode($where), $out["where"]);
- $query = new LeanQuery("Comment");
+ $query = new Query("Comment");
$query->notMatchFieldInQuery("author", "author", $q1);
$out = $query->encode();
$where = array("author" => array('$dontSelect' => array(
@@ -301,7 +302,7 @@ public function testMatchesFieldInQuery() {
public function testRelatedTo() {
$obj = new LeanObject("TestObject", "id123");
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->relatedTo("relField", $obj);
$out = $query->encode();
@@ -312,7 +313,7 @@ public function testRelatedTo() {
}
public function testNearGeoPoint() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->near('location', new GeoPoint(39.9, 116.4));
$out = $query->encode();
$expect = json_encode(array(
@@ -328,7 +329,7 @@ public function testNearGeoPoint() {
}
public function testWithinRadians() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->withinRadians('location', new GeoPoint(39.9, 116.4), 0.5);
$out = $query->encode();
$expect = json_encode(array(
@@ -345,7 +346,7 @@ public function testWithinRadians() {
}
public function testWithinKilometers() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->withinKilometers('location', new GeoPoint(39.9, 116.4), 0.5);
$out = $query->encode();
$expect = json_encode(array(
@@ -362,7 +363,7 @@ public function testWithinKilometers() {
}
public function testWithinMiles() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->withinMiles('location', new GeoPoint(39.9, 116.4), 0.5);
$out = $query->encode();
$expect = json_encode(array(
@@ -379,7 +380,7 @@ public function testWithinMiles() {
}
public function testWithinBox() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->withinBox('location',
new GeoPoint(39.9, 116.4),
new GeoPoint(40.0, 118.0));
@@ -406,13 +407,13 @@ public function testWithinBox() {
}
public function testSelectFields() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->select("name", "color", "foo", "bar");
$out = $query->encode();
$this->assertEquals("name,color,foo,bar", $out["keys"]);
// it accepts variable number of keys
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->select("name");
$query->select("color");
$query->select("foo", "bar");
@@ -420,7 +421,7 @@ public function testSelectFields() {
$this->assertEquals("name,color,foo,bar", $out["keys"]);
// it also accepts an array of keys
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->select(array("name", "color", "foo", "bar"));
$out = $query->encode();
$this->assertEquals("name,color,foo,bar", $out["keys"]);
@@ -433,21 +434,21 @@ public function testSelectFields() {
public function testIncludeNestObjects() {
// it accepts nested objects
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->_include("creator");
$query->_include("object.creator");
$out = $query->encode();
$this->assertEquals("creator,object.creator", $out["include"]);
// it accepts variable number of keys
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->_include("creator");
$query->_include("object.creator", "foo");
$out = $query->encode();
$this->assertEquals("creator,object.creator,foo", $out["include"]);
// it accepts array of fields
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->_include("creator");
$query->_include(array("object.creator", "foo"));
$out = $query->encode();
@@ -455,7 +456,7 @@ public function testIncludeNestObjects() {
}
public function testSkipAndLimit() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->limit(100);
$out = $query->encode();
$this->assertEquals(100, $out["limit"]);
@@ -476,7 +477,7 @@ public function testSkipAndLimit() {
}
public function testOrdering() {
- $query = new LeanQuery("TestObject");
+ $query = new Query("TestObject");
$query->addAscend("number");
$out = $query->encode();
$this->assertEquals("number", $out["order"]);
@@ -495,16 +496,16 @@ public function testOrdering() {
}
public function testComposeSimpleAndQuery() {
- $q1 = new LeanQuery("TestObject");
+ $q1 = new Query("TestObject");
$q1->lessThan("number", 42);
- $q2 = new LeanQuery("TestObject");
+ $q2 = new Query("TestObject");
$q2->greaterThanOrEqualTo("number", 24);
- $q3 = new LeanQuery("TestObject");
+ $q3 = new Query("TestObject");
$q3->contains("title", "clojure");
- $q = LeanQuery::andQuery($q1, $q2);
+ $q = Query::andQuery($q1, $q2);
$out = $q->encode();
$where = array(
'$and' => array(
@@ -514,7 +515,7 @@ public function testComposeSimpleAndQuery() {
);
$this->assertEquals(json_encode($where), $out["where"]);
- $q = LeanQuery::andQuery($q1, $q2, $q3);
+ $q = Query::andQuery($q1, $q2, $q3);
$out = $q->encode();
$where = array(
'$and' => array(
@@ -528,16 +529,16 @@ public function testComposeSimpleAndQuery() {
}
public function testComposeSimpleOrQuery() {
- $q1 = new LeanQuery("TestObject");
+ $q1 = new Query("TestObject");
$q1->greaterThanOrEqualTo("number", 42);
- $q2 = new LeanQuery("TestObject");
+ $q2 = new Query("TestObject");
$q2->lessThan("number", 24);
- $q3 = new LeanQuery("TestObject");
+ $q3 = new Query("TestObject");
$q3->contains("title", "clojure");
- $q = LeanQuery::orQuery($q1, $q2);
+ $q = Query::orQuery($q1, $q2);
$out = $q->encode();
$where = array(
'$or' => array(
@@ -547,7 +548,7 @@ public function testComposeSimpleOrQuery() {
);
$this->assertEquals(json_encode($where), $out["where"]);
- $q = LeanQuery::orQuery($q1, $q2, $q3);
+ $q = Query::orQuery($q1, $q2, $q3);
$out = $q->encode();
$where = array(
'$or' => array(
@@ -560,16 +561,16 @@ public function testComposeSimpleOrQuery() {
}
public function testComposeCompexLogicalQuery() {
- $q1 = new LeanQuery("TestObject");
+ $q1 = new Query("TestObject");
$q1->greaterThanOrEqualTo("number", 42);
- $q2 = new LeanQuery("TestObject");
+ $q2 = new Query("TestObject");
$q2->lessThan("number", 24);
- $q3 = new LeanQuery("TestObject");
+ $q3 = new Query("TestObject");
$q3->contains("title", "clojure");
- $q = LeanQuery::orQuery($q1, $q2);
+ $q = Query::orQuery($q1, $q2);
$out = $q->encode();
$where = array(
'$or' => array(
@@ -579,7 +580,7 @@ public function testComposeCompexLogicalQuery() {
);
$this->assertEquals(json_encode($where), $out["where"]);
- $q = LeanQuery::andQuery($q, $q3);
+ $q = Query::andQuery($q, $q3);
$out = $q->encode();
$where = array(
'$and' => array(
@@ -601,7 +602,7 @@ public function testDoCloudQueryCount() {
$obj = new LeanObject("TestObject");
$obj->set("name", "alice");
$obj->save();
- $resp = LeanQuery::doCloudQuery("SELECT count(*) FROM TestObject");
+ $resp = Query::doCloudQuery("SELECT count(*) FROM TestObject");
$this->assertTrue(is_int($resp["count"]));
$this->assertEquals("TestObject", $resp["className"]);
$obj->destroy();
@@ -611,7 +612,7 @@ public function testDoCloudQueryWithPvalues() {
$obj = new LeanObject("TestObject");
$obj->set("name", "alice");
$obj->save();
- $resp = LeanQuery::doCloudQuery("SELECT * FROM TestObject ".
+ $resp = Query::doCloudQuery("SELECT * FROM TestObject ".
"WHERE name = ? LIMIT ?",
array("alice", 1));
$this->assertGreaterThan(0, count($resp["results"]));
@@ -624,7 +625,7 @@ public function testDoCloudQueryWithDate() {
$obj->set("name", "alice");
$obj->save();
$date = $obj->getCreatedAt();
- $resp = LeanQuery::doCloudQuery("SELECT * FROM TestObject ".
+ $resp = Query::doCloudQuery("SELECT * FROM TestObject ".
"WHERE createdAt = ?",
array($date));
$this->assertGreaterThan(0, count($resp["results"]));
@@ -637,7 +638,7 @@ public function testDoCloudQueryGeoPoint() {
$obj->set("name", "alice");
$obj->set("location", $point);
$obj->save();
- $resp = LeanQuery::doCloudQuery("SELECT * FROM TestObject " .
+ $resp = Query::doCloudQuery("SELECT * FROM TestObject " .
"WHERE location NEAR ?",
array($point));
$this->assertEquals("TestObject", $resp["className"]);
diff --git a/tests/RelationOperationTest.php b/test/RelationOperationTest.php
similarity index 92%
rename from tests/RelationOperationTest.php
rename to test/RelationOperationTest.php
index a047a39..6bdbe1f 100644
--- a/tests/RelationOperationTest.php
+++ b/test/RelationOperationTest.php
@@ -1,17 +1,18 @@
setExpectedException("RuntimeException",
- "Object type incompatible with " .
+ "LeanObject type incompatible with " .
"relation.");
$op = new RelationOperation("foo",
array($child1),
@@ -108,7 +109,7 @@ public function testApplyOperation() {
$op = new RelationOperation("foo", array($child1), null);
$parent = new LeanObject("Test2Object");
$val = $op->applyOn(null, $parent);
- $this->assertTrue($val instanceof LeanRelation);
+ $this->assertTrue($val instanceof Relation);
$out = $val->encode();
$this->assertEquals("TestObject", $out["className"]);
}
diff --git a/tests/LeanRelationTest.php b/test/RelationTest.php
similarity index 86%
rename from tests/LeanRelationTest.php
rename to test/RelationTest.php
index 1009dc7..e71a4dc 100644
--- a/tests/LeanRelationTest.php
+++ b/test/RelationTest.php
@@ -1,16 +1,17 @@
getRelation("likes");
@@ -32,14 +33,14 @@ public function testRelationClassEncode() {
public function testGetRelationOnTargetClass() {
$obj = new LeanObject("TestObject", "id123");
- $rel = new LeanRelation($obj, "likes", "User");
+ $rel = new Relation($obj, "likes", "User");
$query = $rel->getQuery();
$this->assertEquals("User", $query->getClassName());
}
public function testGetRelationQueryWithoutTargetClass() {
$obj = new LeanObject("TestObject", "id123");
- $rel = new LeanRelation($obj, "likes");
+ $rel = new Relation($obj, "likes");
$query = $rel->getQuery();
// the query should be made against the parent class, with
@@ -52,7 +53,7 @@ public function testGetRelationQueryWithoutTargetClass() {
public function getReverseQueryOnChildObject() {
$obj = new LeanObject("TestObject", "id123");
- $rel = new LeanRelation($obj, "likes", "User");
+ $rel = new Relation($obj, "likes", "User");
$child = new LeanObject("User", "id124");
$query = $rel->getReverseQuery($child);
$this->assertEquals("TestObject", $query->getClassName());
diff --git a/test/RoleTest.php b/test/RoleTest.php
new file mode 100644
index 0000000..99cc67a
--- /dev/null
+++ b/test/RoleTest.php
@@ -0,0 +1,51 @@
+assertEquals("id123", $role->getObjectId());
+ }
+
+ public function testGetChildrenAsRelation() {
+ $role = new Role();
+ $this->assertTrue($role->getUsers() instanceof Relation);
+ $this->assertTrue($role->getRoles() instanceof Relation);
+ }
+
+ public function testSaveRole() {
+ $role = new Role();
+ $role->setName("admin");
+
+ $acl = new ACL();
+ $acl->setPublicWriteAccess(true); // so it can be destroyed
+ $role->setACL($acl);
+
+ $role->save();
+ $this->assertNotEmpty($role->getObjectId());
+ $this->assertTrue($role->getUsers() instanceof Relation);
+ $this->assertTrue($role->getRoles() instanceof Relation);
+
+ $role->destroy();
+ }
+
+}
+
diff --git a/tests/SetOperationTest.php b/test/SetOperationTest.php
similarity index 92%
rename from tests/SetOperationTest.php
rename to test/SetOperationTest.php
index fae5f61..29892f1 100644
--- a/tests/SetOperationTest.php
+++ b/test/SetOperationTest.php
@@ -4,9 +4,10 @@
use LeanCloud\Operation\ArrayOperation;
use LeanCloud\Operation\DeleteOperation;
use LeanCloud\Operation\IncrementOperation;
-use LeanCloud\LeanClient;
+use LeanCloud\Client;
+use PHPUnit\Framework\TestCase;
-class SetOperationTest extends PHPUnit_Framework_TestCase {
+class SetOperationTest extends TestCase {
public function testGetKey() {
$op = new SetOperation("name", "alice");
$this->assertEquals($op->getKey(), "name");
@@ -35,7 +36,7 @@ public function testOperationEncode() {
$out = $op->encode();
$this->assertEquals($out['__type'], "Date");
$this->assertEquals($out['iso'],
- LeanClient::formatDate($date));
+ Client::formatDate($date));
}
public function testMergeWithAnyOp() {
diff --git a/tests/StorageTest.php b/test/StorageTest.php
similarity index 91%
rename from tests/StorageTest.php
rename to test/StorageTest.php
index 2fc8bbe..957723c 100644
--- a/tests/StorageTest.php
+++ b/test/StorageTest.php
@@ -1,8 +1,9 @@
setUsername("alice");
$user->setPassword("blabla");
+ $user->setEmail("alice@example.com");
try {
$user->signUp();
} catch (CloudException $ex) {
@@ -30,7 +34,7 @@ public static function setUpBeforeClass() {
public static function tearDownAfterClass() {
// destroy default user if present
try {
- $user = LeanUser::logIn("alice", "blabla");
+ $user = User::logIn("alice", "blabla");
$user->destroy();
} catch (CloudException $ex) {
// skip
@@ -39,7 +43,7 @@ public static function tearDownAfterClass() {
public function setUp() {
// logout current user if any
- LeanUser::logOut();
+ User::logOut();
$this->openToken = array();
$this->openToken["openid"] = "0395BA18A";
$this->openToken["expires_in"] = "36000";
@@ -47,7 +51,7 @@ public function setUp() {
}
public function testSetGetFields() {
- $user = new LeanUser();
+ $user = new User();
$user->setUsername("alice");
$user->setEmail("alice@example.com");
$user->setMobilePhoneNumber("18612340000");
@@ -62,7 +66,7 @@ public function testSetGetFields() {
}
public function testSaveNewUser() {
- $user = new LeanUser();
+ $user = new User();
$user->setUsername("alice");
$user->setPassword("blabla");
$this->setExpectedException("LeanCloud\CloudException",
@@ -71,7 +75,7 @@ public function testSaveNewUser() {
}
public function testUserSignUp() {
- $user = new LeanUser();
+ $user = new User();
$user->setUsername("alice2");
$user->setPassword("blabla");
@@ -83,54 +87,74 @@ public function testUserSignUp() {
}
public function testUserUpdate() {
- $user = LeanUser::logIn("alice", "blabla");
+ $user = User::logIn("alice", "blabla");
$user->setEmail("alice@example.com");
$user->set("age", 24);
$user->save();
$this->assertNotEmpty($user->getUpdatedAt());
- $user2 = LeanUser::become($user->getSessionToken());
+ $user2 = User::become($user->getSessionToken());
$this->assertEquals("alice@example.com", $user2->getEmail());
$this->assertEquals(24, $user2->get("age"));
}
public function testUserLogIn() {
- $user = LeanUser::logIn("alice", "blabla");
+ $user = User::logIn("alice", "blabla");
+
+ $this->assertNotEmpty($user->getObjectId());
+ $this->assertEquals($user, User::getCurrentUser());
+ }
+
+ public function testUserLogInWithEmail() {
+ $user = User::logInWithEmail("alice@example.com", "blabla");
$this->assertNotEmpty($user->getObjectId());
- $this->assertEquals($user, LeanUser::getCurrentUser());
+ $this->assertEquals($user, User::getCurrentUser());
}
public function testLoginWithMobilePhoneNumber() {
- $user = LeanUser::logIn("alice", "blabla");
+ $user = User::logIn("alice", "blabla");
$user->setMobilePhoneNumber("18612340000");
$user->save();
$user->logOut();
- $this->assertNull(LeanUser::getCurrentUser());
+ $this->assertNull(User::getCurrentUser());
- LeanUser::logInWithMobilePhoneNumber("18612340000", "blabla");
- $user2 = LeanUser::getCurrentUser();
+ User::logInWithMobilePhoneNumber("18612340000", "blabla");
+ $user2 = User::getCurrentUser();
$this->assertEquals("alice", $user2->getUsername());
}
public function testBecome() {
- $user = LeanUser::logIn("alice", "blabla");
+ $user = User::logIn("alice", "blabla");
- $user2 = LeanUser::become($user->getSessionToken());
+ $user2 = User::become($user->getSessionToken());
$this->assertNotEmpty($user2->getObjectId());
- $this->assertEquals($user2, LeanUser::getCurrentUser());
+ $this->assertEquals($user2, User::getCurrentUser());
+ }
+
+ public function testRefreshSessionToken() {
+ $user = new User();
+ $user->setUsername("alice4");
+ $user->setPassword("blabla");
+ $user->signUp();
+
+ $token = $user->getSessionToken();
+ $user->refreshSessionToken();
+ $this->assertNotEmpty($user->getSessionToken());
+ $this->assertNotEquals($token, $user->getSessionToken());
+ $user->destroy();
}
public function testLogOut() {
- $user = LeanUser::logIn("alice", "blabla");
- $this->assertEquals($user, LeanUser::getCurrentUser());
- LeanUser::logOut();
- $this->assertNull(LeanUser::getCurrentUser());
+ $user = User::logIn("alice", "blabla");
+ $this->assertEquals($user, User::getCurrentUser());
+ User::logOut();
+ $this->assertNull(User::getCurrentUser());
}
public function testUpdatePassword() {
- $user = new LeanUser();
+ $user = new User();
$user->setUsername("alice3");
$user->setPassword("blabla");
$user->signUp();
@@ -148,17 +172,39 @@ public function testUpdatePassword() {
public function testVerifyMobilePhone() {
// Ensure the post format is correct
$this->setExpectedException("LeanCloud\CloudException", null, 603);
- LeanUser::verifyMobilePhone("000000");
+ User::verifyMobilePhone("000000", "18612340000");
+ }
+
+ public function testSignUpOrLoginByMobilePhone() {
+ // Ensure the post format is correct
+ $this->setExpectedException("LeanCloud\CloudException", null, 603);
+ User::signUpOrLoginByMobilePhone("18612340000", "000000");
+ }
+
+ public function testRequestChangePhoneNumber() {
+ // Remember to create this user before hand.
+ User::logIn("php_test_change_phone_number", "blabla");
+ // Uncomment the follow lines when manually running the test.
+ // phone number is from https://www.yinsiduanxin.com
+ // User::requestChangePhoneNumber("+8616533875941");
+ }
+
+ public function testChangePhoneNumber() {
+ $this->setExpectedException("LeanCloud\CloudException", null, 603);
+ User::changePhoneNumber("992989", "+8616533875941");
+ // Uncomment the follow lines when manually running the test.
+ // $user = User::logIn("php_test_change_phone_number", "blabla");
+ // $this->assertEquals("+8616533875941", $user->getMobilePhoneNumber());
}
public function testLogInWithLinkedService() {
- $user = LeanUser::logIn("alice", "blabla");
+ $user = User::logIn("alice", "blabla");
$user->linkWith("weixin", $this->openToken);
$auth = $user->get("authData");
$this->assertEquals($this->openToken, $auth["weixin"]);
- $user2 = LeanUser::logInWith("weixin", $this->openToken);
+ $user2 = User::logInWith("weixin", $this->openToken);
$this->assertEquals($user->getUsername(),
$user2->getUsername());
$this->assertEquals($user->getSessionToken(),
@@ -168,29 +214,65 @@ public function testLogInWithLinkedService() {
}
public function testSignUpWithLinkedService() {
- $user = LeanUser::logInWith("weixin", $this->openToken);
+ $user = User::logInWith("weixin", $this->openToken);
$this->assertNotEmpty($user->getSessionToken());
$this->assertNotEmpty($user->getObjectId());
- $this->assertEquals($user, LeanUser::getCurrentUser());
+ $this->assertEquals($user, User::getCurrentUser());
$user->destroy();
}
public function testUnlinkService() {
- $user = LeanUser::logInWith("weixin", $this->openToken);
+ $user = User::logInWith("weixin", $this->openToken);
$token = $user->getSessionToken();
$authData = $user->get("authData");
$this->assertEquals($this->openToken, $authData["weixin"]);
$user->unlinkWith("weixin");
// re-login with user session token
- $user2 = LeanUser::become($token);
+ $user2 = User::become($token);
$authData = $user2->get("authData");
$this->assertTrue(!isset($authData["weixin"]));
$user2->destroy();
}
+ public function testGetRoles() {
+ $user = new User();
+ $user->setUsername("alice3");
+ $user->setPassword("blabla");
+ $user->signUp();
+
+ $role = new Role();
+ $role->setName("test_role");
+ $acl = new ACL();
+ $acl->setPublicWriteAccess(true);
+ $acl->setPublicReadAccess(true);
+
+ $role->setACL($acl);
+ $rel = $role->getUsers();
+ $rel->add($user);
+ $role->save();
+ $this->assertNotEmpty($role->getObjectId());
+
+ $roles = $user->getRoles();
+ $this->assertEquals("test_role", $roles[0]->getName());
+
+ $user->destroy();
+ $role->destroy();
+ }
+
+ public function testIsAuthenticated() {
+ $user = User::logIn("alice", "blabla");
+ $this->assertTrue($user->isAuthenticated());
+
+ $user->mergeAfterFetch(array("sessionToken" => "invalid-token"));
+ $this->assertFalse($user->isAuthenticated());
+
+ $user = new User();
+ $this->assertFalse($user->isAuthenticated());
+ }
+
/*
* Get current user with file attribute shall not
* circularly invoke getCurrentUser.
@@ -200,15 +282,15 @@ public function testUnlinkService() {
public function testCircularGetCurrentUser() {
// ensure getCurrentUser neither run indefinetely, nor throw maximum
// function call error
- $avatar = LeanFile::createWithUrl("alice.png", "https://leancloud.cn/favicon.png");
- $user = LeanUser::logIn("alice", "blabla");
+ $avatar = File::createWithUrl("alice.png", "https://leancloud.cn/favicon.png");
+ $user = User::logIn("alice", "blabla");
$user->set("avatar", $avatar);
$user->save();
- $token = LeanUser::getCurrentSessionToken();
+ $token = User::getCurrentSessionToken();
$user->logOut();
- LeanUser::setCurrentSessionToken($token);
+ User::setCurrentSessionToken($token);
- $user2 = LeanUser::getCurrentUser();
+ $user2 = User::getCurrentUser();
$this->assertEquals($user2->getUsername(), "alice");
}
@@ -219,11 +301,10 @@ public function testCircularGetCurrentUser() {
* @link https://github.com/leancloud/php-sdk/issues/62
*/
public function testFindUserWithSession() {
- $user = LeanUser::logIn("alice", "blabla");
- $query = new LeanQuery("_User");
+ $user = User::logIn("alice", "blabla");
+ $query = new Query("_User");
// it should not raise: 1 Forbidden to find by class permission.
$query->first();
}
}
-
diff --git a/test/engine/LeanEngineTest.php b/test/engine/LeanEngineTest.php
new file mode 100644
index 0000000..35a1cda
--- /dev/null
+++ b/test/engine/LeanEngineTest.php
@@ -0,0 +1,226 @@
+ 0) {
+ throw new \RuntimeException("CURL connection error $errno: $url");
+ }
+ $data = json_decode($resp, true);
+ if (isset($data["error"])) {
+ $code = isset($data["code"]) ? $data["code"] : -1;
+ throw new CloudException("{$data['error']}", $code, $respCode,
+ $method, $url);
+ }
+ return $data;
+ }
+
+ private function signHook($hookName, $msec=null) {
+ if (!$msec) {
+ $msec = round(microtime(true) * 1000);
+ }
+ $hash = hash_hmac("sha1",
+ "{$hookName}:{$msec}",
+ getenv("LEANCLOUD_APP_MASTER_KEY"));
+ return "{$msec},{$hash}";
+ }
+
+ public function testPingEngine() {
+ $resp = $this->request("/__engine/1/ping", "GET");
+ $this->assertArrayHasKey("runtime", $resp);
+ $this->assertArrayHasKey("version", $resp);
+ }
+
+ public function testGetFuncitonMetadata() {
+ $resp = $this->request("/1/functions/_ops/metadatas", "GET");
+ $this->assertContains("hello", $resp["result"]);
+ }
+
+ public function testCloudFunctionHello() {
+ $resp = $this->request("/1/functions/hello", "POST", array());
+ $this->assertEquals("hello", $resp["result"]);
+ }
+
+ public function testCallFunctionWithObject() {
+ $obj = array(
+ "__type" => "Object",
+ "className" => "TestObject",
+ "objectId" => "id001",
+ "name" => "alice"
+ );
+ $resp = $this->request("/1/call/updateObject", "POST", array(
+ "object" => $obj
+ ));
+ $this->assertEquals($obj["className"], $resp["result"]["className"]);
+ $this->assertEquals($obj["objectId"], $resp["result"]["objectId"]);
+ $this->assertEquals(42, $resp["result"]["__testKey"]);
+ }
+
+ public function testFunctionWithParam() {
+ $resp = $this->request("/1/functions/sayHello", "POST", array(
+ "name" => "alice"
+ ));
+ $this->assertEquals("hello alice", $resp["result"]);
+ }
+
+ public function testMetaParamsShouldHaveRemoteAddress() {
+ $resp = $this->request("/1/functions/getMeta", "POST", array(
+ "name" => "alice"
+ ));
+ $this->assertNotEmpty($resp["result"]["remoteAddress"]);
+ }
+
+ public function testOnInsight() {
+ $resp = $this->request("/1/functions/BigQuery/onComplete", "POST", array(
+ "id" => "id001",
+ "status" => "OK",
+ "message" => "Big query completed successfully.",
+ "__sign" => $this->signHook("__on_complete_bigquery_job")
+ ));
+ $this->assertEquals("ok", $resp["result"]);
+ }
+
+ public function testOnLogin() {
+ $resp = $this->request("/1/functions/_User/onLogin", "POST", array(
+ "object" => array(
+ "__type" => "Object",
+ "className" => "_User",
+ "objectId" => "id002",
+ "username" => "alice",
+ "__sign" => $this->signHook("__on_login__User")
+ )
+ ));
+ $this->assertEquals("ok", $resp["result"]);
+ }
+
+ public function testOnVerifiedSms() {
+ $resp = $this->request("/1/functions/onVerified/sms", "POST", array(
+ "object" => array(
+ "__type" => "Object",
+ "className" => "_User",
+ "objectId" => "id002",
+ "username" => "alice",
+ "__sign" => $this->signHook("__on_verified_sms")
+ )
+ ));
+ $this->assertEquals("ok", $resp["result"]);
+ }
+
+ public function testBeforeSave() {
+ $obj = array(
+ "name" => "alice",
+ "likes" => array(
+ "__type" => "Pointer",
+ "className" => "TestObject",
+ "objectId" => "id002"
+ ),
+ "__before" => $this->signHook("__before_for_TestObject")
+ );
+ $resp = $this->request("/1/functions/TestObject/beforeSave", "POST",
+ array("object" => $obj));
+ $obj2 = $resp;
+ $this->assertEquals($obj["name"], $obj2["name"]);
+ $this->assertEquals(42, $obj2["__testKey"]);
+ $this->assertEquals("Pointer", $obj2["likes"]["__type"]);
+ $this->assertEquals("id002", $obj2["likes"]["objectId"]);
+ }
+
+ public function testAfterSave() {
+ $obj = array(
+ "__type" => "Object",
+ "className" => "TestObject",
+ "objectId" => "id002",
+ "name" => "alice",
+ "__after" => $this->signHook("__after_for_TestObject")
+ );
+ $resp = $this->request("/1/functions/TestObject/afterSave", "POST",
+ array("object" => $obj));
+ $this->assertEquals("ok", $resp["result"]);
+ }
+
+ public function testBeforeDelete() {
+ $obj = array(
+ "__type" => "Object",
+ "className" => "TestObject",
+ "objectId" => "id002",
+ "name" => "alice",
+ "__before" => $this->signHook("__before_for_TestObject")
+ );
+ $resp = $this->request("/1.1/functions/TestObject/beforeDelete", "POST",
+ array("object" => $obj));
+ $this->assertEmpty($resp);
+ }
+
+ public function test_messageReceived() {
+ $resp = $this->request("/1.1/functions/_messageReceived", "POST", array(
+ "convId" => '5789a33a1b8694ad267d8040',
+ "fromPeer" => "Tom",
+ "receipt" => false,
+ "toPeers" => array("Jerry"),
+ "content" => '{"_lctext":"耗子,起床!","_lctype":-1}',
+ "__sign" => $this->signHook("_messageReceived")
+ ));
+ $this->assertEquals(false, $resp["result"]["drop"]);
+ }
+
+ public function testFunctionError() {
+ try {
+ $this->request("/1.1/functions/customError", "POST", array());
+ } catch (CloudException $ex) {
+ $this->assertEquals("My custom error.", $ex->getMessage());
+ $this->assertEquals(1, $ex->getCode());
+ $this->assertEquals(500, $ex->status);
+ }
+ }
+
+}
+
diff --git a/test/engine/index.php b/test/engine/index.php
new file mode 100644
index 0000000..bbd9cf6
--- /dev/null
+++ b/test/engine/index.php
@@ -0,0 +1,78 @@
+ false);
+ } else {
+ return array("drop" => true);
+ }
+});
+
+Cloud::define("getMeta", function($params, $user, $meta) {
+ return array("remoteAddress" => $meta["remoteAddress"]);
+});
+
+Cloud::define("updateObject", function($params, $user) {
+ $obj = $params["object"];
+ $obj->set("__testKey", 42);
+ return $obj;
+});
+
+Cloud::onLogin(function($user) {
+ error_log("Logging a user");
+ return;
+});
+
+Cloud::onInsight(function($job) {
+ return;
+});
+
+Cloud::onVerified("sms", function($user){
+ return;
+});
+
+Cloud::beforeSave("TestObject", function($obj, $user) {
+ $obj->set("__testKey", 42);
+});
+
+Cloud::afterSave("TestObject", function($obj, $user) {
+ return;
+});
+
+Cloud::beforeDelete("TestObject", function($obj, $user) {
+ return;
+});
+
+$engine = new LeanEngine();
+$engine->start();
+
diff --git a/tests/LeanRoleTest.php b/tests/LeanRoleTest.php
deleted file mode 100644
index 8b77829..0000000
--- a/tests/LeanRoleTest.php
+++ /dev/null
@@ -1,50 +0,0 @@
-assertEquals("id123", $role->getObjectId());
- }
-
- public function testGetChildrenAsRelation() {
- $role = new LeanRole();
- $this->assertTrue($role->getUsers() instanceof LeanRelation);
- $this->assertTrue($role->getRoles() instanceof LeanRelation);
- }
-
- public function testSaveRole() {
- $role = new LeanRole();
- $role->setName("admin");
-
- $acl = new LeanACL();
- $acl->setPublicWriteAccess(true); // so it can be destroyed
- $role->setACL($acl);
-
- $role->save();
- $this->assertNotEmpty($role->getObjectId());
- $this->assertTrue($role->getUsers() instanceof LeanRelation);
- $this->assertTrue($role->getRoles() instanceof LeanRelation);
-
- $role->destroy();
- }
-
-}
-