Completed
Push — master ( 4c707a...1e14b3 )
by Zhmayev
01:23
created

Entities::getNumericID()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 4
nop 2
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
* Vtiger Web Services PHP Client Library
4
*
5
* The MIT License (MIT)
6
*
7
* Copyright (c) 2015, Zhmayev Yaroslav <[email protected]>
8
*
9
* Permission is hereby granted, free of charge, to any person obtaining a copy
10
* of this software and associated documentation files (the "Software"), to deal
11
* in the Software without restriction, including without limitation the rights
12
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
* copies of the Software, and to permit persons to whom the Software is
14
* furnished to do so, subject to the following conditions:
15
*
16
* The above copyright notice and this permission notice shall be included in
17
* all copies or substantial portions of the Software.
18
*
19
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
* THE SOFTWARE.
26
*
27
* @author    Zhmayev Yaroslav <[email protected]>
28
* @copyright 2015-2016 Zhmayev Yaroslav
29
* @license   The MIT License (MIT)
30
*/
31
32
namespace Salaros\Vtiger\VTWSCLib;
33
34
use Salaros\Vtiger\VTWSCLib\WSClient;
35
use Salaros\Vtiger\VTWSCLib\EntityUtils;
36
37
/**
38
* Vtiger Web Services PHP Client Session class
39
*
40
* Class Entities
41
* @package Salaros\Vtiger\VTWSCLib
42
*/
43
class Entities
44
{
45
    private $wsClient = null;
46
47
    /**
48
     * Class constructor
49
     * @param string $wsClient  Parent WSClient instance
50
     */
51
    public function __construct($wsClient)
52
    {
53
        $this->wsClient = $wsClient;
54
    }
55
56
    /**
57
     * Retrieves an entity by ID
58
     * @param  string  $moduleName The name of the module / entity type
59
     * @param  string  $entityID The ID of the entity to retrieve
60
     * @return array   $select  The list of fields to select (defaults to SQL-like '*' - all the fields)
61
     * @return boolean  Entity data
62
     */
63
    public function findOneByID($moduleName, $entityID, array $select = [])
64
    {
65
        return $this->findOne($moduleName, [ 'id' => $entityID ], $select);
66
    }
67
68
    /**
69
     * Retrieve the entity matching a list of constraints
70
     * @param  string  $moduleName   The name of the module / entity type
71
     * @param  array   $params  Data used to find a matching entry
72
     * @return array   $select  The list of fields to select (defaults to SQL-like '*' - all the fields)
73
     * @return integer  The matching record
74
     */
75
    public function findOne($moduleName, array $params, array $select = [])
76
    {
77
        $records = $this->findMany($moduleName, $params, $select, 1);
78
        if (false === $records || !isset($records[0])) {
79
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Salaros\Vtiger\VTWSCLib\Entities::findOne of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
80
        }
81
        return $records[0];
82
    }
83
84
    /**
85
     * Retrieves the ID of the entity matching a list of constraints + prepends '<module_id>x' string to it
86
     * @param  string $moduleName   The name of the module / entity type
87
     * @param  array   $params  Data used to find a matching entry
88
     * @return integer  Type ID (a numeric ID + '<module_id>x')
89
     */
90
    public function getID($moduleName, array $params)
91
    {
92
        $record = $this->findOne($moduleName, $params, ['id']);
93
        if (false === $record || !isset($record['id']) || empty($record['id'])) {
94
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Salaros\Vtiger\VTWSCLib\Entities::getID of type integer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
95
        }
96
        return $record['id'];
97
    }
98
99
    /**
100
     * Retrieve a numeric ID of the entity matching a list of constraints
101
     * @param  string  $moduleName   The name of the module / entity type
102
     * @param  array   $params  Data used to find a matching entry
103
     * @return integer  Numeric ID
104
     */
105
    public function getNumericID($moduleName, array $params)
106
    {
107
        $entityID = $this->getID($moduleName, $params);
108
        $entityIDParts = explode('x', $entityID, 2);
109
        return (is_array($entityIDParts) && count($entityIDParts) === 2)
110
            ? intval($entityIDParts[1])
111
            : -1;
112
    }
113
114
    /**
115
     * Creates an entity for the giving module
116
     * @param  string  $moduleName   Name of the module / entity type for which the entry has to be created
117
     * @param  array  $params Entity data
118
     * @return array  Entity creation results
119
     */
120
    public function createOne($moduleName, array $params)
121
    {
122
        if (!self::checkParams($params, 'be able to create an entity')) {
123
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Salaros\Vtiger\VTWSCLib\Entities::createOne of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
124
        }
125
126
        // Assign record to logged in user if not specified
127
        if (!isset($params['assigned_user_id'])) {
128
            $params['assigned_user_id'] = $this->userID;
0 ignored issues
show
Bug introduced by
The property userID does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
129
        }
130
131
        $requestData = [
132
            'elementType' => $moduleName,
133
            'element'     => json_encode($params)
134
        ];
135
136
        return $this->wsClient->invokeOperation('create', $requestData);
0 ignored issues
show
Bug introduced by
The method invokeOperation cannot be called on $this->wsClient (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
137
    }
138
139
    /**
140
     * Updates an entity
141
     * @param  string $moduleName   The name of the module / entity type
142
     * @param  array $params Entity data
143
     * @return array  Entity update result
144
     */
145
    public function updateOne($moduleName, $entityID, array $params)
146
    {
147
        if (!self::checkParams($params, 'be able to update the entity(ies)')) {
148
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Salaros\Vtiger\VTWSCLib\Entities::updateOne of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
149
        }
150
151
        // Fail if no ID was supplied
152
        if (empty($entityID)) {
153
            throw new WSException("The list of contraints must contain a valid ID");
154
        }
155
156
        // Preprend so-called moduleid if needed
157
        $entityID = $this->wsClient->modules->getTypedID($moduleName, $entityID);
158
159
        // Check if the entity exists + retrieve its data so it can be used below
160
        $entityData = $this->findOneByID($moduleName, $entityID);
161
        if ($entityData === false && !is_array($entityData)) {
162
            throw new WSException("Such entity doesn't exist, so it cannot be updated");
163
        }
164
165
        // The new data overrides the existing one needed to provide
166
        // mandatory field values to WS 'update' operation
167
        $params = array_merge(
168
            $entityData,
169
            $params
170
        );
171
172
        $requestData = [
173
            'elementType' => $moduleName,
174
            'element'     => json_encode($params)
175
        ];
176
177
        return $this->wsClient->invokeOperation('update', $requestData);
0 ignored issues
show
Bug introduced by
The method invokeOperation cannot be called on $this->wsClient (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
178
    }
179
180
    /**
181
     * Provides entity removal functionality
182
     * @param  string $moduleName   The name of the module / entity type
183
     * @param  string $entityID The ID of the entity to delete
184
     * @return array  Removal status object
185
     */
186
    public function deleteOne($moduleName, $entityID)
187
    {
188
        // Preprend so-called moduleid if needed
189
        $entityID = $this->wsClient->modules->getTypedID($moduleName, $entityID);
190
        return $this->wsClient->invokeOperation('delete', [ 'id' => $entityID ]);
0 ignored issues
show
Bug introduced by
The method invokeOperation cannot be called on $this->wsClient (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
191
    }
192
193
    /**
194
     * Retrieves multiple records using module name and a set of constraints
195
     * @param  string   $moduleName  The name of the module / entity type
196
     * @param  array    $params  Data used to find matching entries
197
     * @return array    $select  The list of fields to select (defaults to SQL-like '*' - all the fields)
198
     * @return integer      $limit  limit the list of entries to N records (acts like LIMIT in SQL)
199
     * @return bool|array  The array containing matching entries or false if nothing was found
200
     */
201
    public function findMany($moduleName, array $params, array $select = [], $limit = 0)
202
    {
203
        if (!self::checkParams($params, 'be able to retrieve entity(ies)')) {
204
            return false;
205
        }
206
207
        // Builds the query
208
        $query = self::getQueryString($moduleName, $params, $select, $limit);
209
210
        // Run the query
211
        $records = $this->wsClient->runQuery($query);
0 ignored issues
show
Bug introduced by
The method runQuery cannot be called on $this->wsClient (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
212
        if (false === $records || !is_array($records) || (count($records) <= 0)) {
213
            return false;
214
        }
215
216
        return $records;
217
    }
218
219
    /**
220
     * Sync will return a sync result object containing details of changes after modifiedTime
221
     * @param  integer [$modifiedTime = null]    The date of the first change
222
     * @param  string [$moduleName = null]   The name of the module / entity type
223
     * @return array  Sync result object
224
     */
225
    public function sync($modifiedTime = null, $moduleName = null)
226
    {
227
        $modifiedTime = (empty($modifiedTime))
228
            ? strtotime('today midnight')
229
            : intval($modifiedTime);
230
231
        $requestData = [
232
            'modifiedTime' => $modifiedTime
233
        ];
234
235
        if (!empty($moduleName)) {
236
            $requestData['elementType'] = $moduleName;
237
        }
238
239
        return $this->wsClient->invokeOperation('sync', $requestData, true);
0 ignored issues
show
Bug introduced by
The method invokeOperation cannot be called on $this->wsClient (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
240
    }
241
242
        /**
243
     * Checks if if params holds valid entity data/search constraints, otherwise returns false
244
     * @access public
245
     * @static
246
     * @param  array    $params  Array holding entity data/search constraints
247
     * @return boolean  Returns true if params holds valid entity data/search constraints, otherwise returns false
248
     */
249
    private static function checkParams(array $params, $paramsPurpose)
250
    {
251 View Code Duplication
        if (empty($params) || !is_array($params) || !is_assoc_array($params)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
252
            throw new WSException(sprintf(
253
                "You have to specify at least one search parameter (prop => value) in order to %s",
254
                $paramsPurpose
255
            ));
256
        }
257
        return true;
258
    }
259
260
    /**
261
     * Builds the query using the supplied parameters
262
     * @access public
263
     * @static
264
     * @param  string   $moduleName  The name of the module / entity type
265
     * @param  array    $params  Data used to find matching entries
266
     * @return array    $select  The list of fields to select (defaults to SQL-like '*' - all the fields)
267
     * @return int      $limit  limit the list of entries to N records (acts like LIMIT in SQL)
268
     * @return string   The query build out of the supplied parameters
269
     */
270
    public static function getQueryString($moduleName, array $params, array $select = [], $limit = 0)
271
    {
272
        $criteria = array();
273
        $select=(empty($select)) ? '*' : implode(',', $select);
274
        $query=sprintf("SELECT %s FROM $moduleName WHERE ", $select);
275
        foreach ($params as $param => $value) {
276
            $criteria[] = "{$param} LIKE '{$value}'";
277
        }
278
279
        $query.=implode(" AND ", $criteria);
280
        if (intval($limit) > 0) {
281
            $query.=sprintf(" LIMIT %s", intval($limit));
282
        }
283
        return $query;
284
    }
285
}
286