psr12 fixes for new PHP_CodeSniffer (#4795)
[openemr.git] / src / Services / PractitionerService.php
blob3e33b95a8fb6483aa9ed39e38a3295568fdcd104
1 <?php
3 /**
4 * PractitionerService
6 * @package OpenEMR
7 * @link http://www.open-emr.org
8 * @author Matthew Vita <matthewvita48@gmail.com>
9 * @author Yash Bothra <yashrajbothra786gmail.com>
10 * @copyright Copyright (c) 2018 Matthew Vita <matthewvita48@gmail.com>
11 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
14 namespace OpenEMR\Services;
16 use OpenEMR\Common\Logging\SystemLogger;
17 use OpenEMR\Common\Uuid\UuidRegistry;
18 use OpenEMR\Services\Search\ISearchField;
19 use OpenEMR\Services\Search\SearchModifier;
20 use OpenEMR\Services\Search\StringSearchField;
21 use OpenEMR\Services\Search\TokenSearchField;
22 use OpenEMR\Services\Search\TokenSearchValue;
23 use OpenEMR\Validators\PractitionerValidator;
24 use OpenEMR\Validators\ProcessingResult;
26 class PractitionerService extends BaseService
28 private const PRACTITIONER_TABLE = "users";
29 /**
30 * @var PractitionerValidator
32 private $practitionerValidator;
34 /**
35 * Default constructor.
37 public function __construct()
39 parent::__construct('users');
40 UuidRegistry::createMissingUuidsForTables([self::PRACTITIONER_TABLE]);
41 $this->practitionerValidator = new PractitionerValidator();
44 public function getSelectJoinTables(): array
46 return
48 ['table' => 'list_options', 'alias' => 'abook', 'type' => 'LEFT JOIN', 'column' => 'abook_type', 'join_column' => 'option_id']
49 ,['table' => 'list_options', 'alias' => 'physician', 'type' => 'LEFT JOIN', 'column' => 'physician_type', 'join_column' => 'option_id']
53 public function getSelectFields(): array
55 // since we are joining a bunch of fields we need to make sure we normalize our regular field array by adding
56 // the table name for our own table values.
57 $fields = $this->getFields();
58 $normalizedFields = [];
59 // processing is cheap
60 foreach ($fields as $field) {
61 $normalizedFields[] = '`' . $this->getTable() . '`.`' . $field . '`';
64 return array_merge($normalizedFields, ['abook.title as abook_title', 'physician.title as physician_title', 'physician.codes as physician_code']);
67 public function getUuidFields(): array
69 return ['uuid'];
72 public function isValidPractitionerUuid($uuid)
74 $result = $this->getOne($uuid);
75 return !empty($result->getData());
78 public function search($search, $isAndCondition = true)
80 // we only retrieve from our database when our practitioners are not null
81 if (!empty($search['npi'])) {
82 if (!$search['npi'] instanceof ISearchField) {
83 throw new SearchFieldException("npi", "field must be instance of " . ISearchField::class);
85 if ($search['npi']->getModifier() === SearchModifier::MISSING) {
86 // force our value to be false as the only thing that differentiates users as practitioners is our npi number
87 $search['npi'] = new TokenSearchField('npi', [new TokenSearchValue(false)]);
89 } else {
90 $search['npi'] = new TokenSearchField('npi', [new TokenSearchValue(false)]);
91 $search['npi']->setModifier(SearchModifier::MISSING);
93 // TODO: @adunsulag check with @brady.miller or @sjpadgett and find out if all practitioners will have usernames.
94 // I noticed that in the test database that adding entries to the addressbook appears to create users... which
95 // seems bizarre and that those users don't have usernames. I noticed that all of the users displayed in the OpenEMR
96 // user selector will exclude anything w/o a username so I add the same logic here.
97 if (!empty($search['username'])) {
98 if (!$search['username'] instanceof ISearchField) {
99 throw new SearchFieldException("username", "field must be instance of " . ISearchField::class);
101 if ($search['username']->getModifier() === SearchModifier::MISSING) {
102 // force our value to be false as we don't count users as practitioners if there is no username
103 $search['username'] = new TokenSearchField('username', [new TokenSearchValue(false)]);
105 } else {
106 $search['username'] = new TokenSearchField('username', [new TokenSearchValue(false)]);
107 $search['username']->setModifier(SearchModifier::MISSING);
109 return parent::search($search, $isAndCondition);
113 * Returns a list of practitioners matching optional search criteria.
114 * Search criteria is conveyed by array where key = field/column name, value = ISearchField|primitive value
116 * If a primitive value is provided it will do an exact match on that field. If an ISearchField is provided it will
117 * use whatever modifiers, comparators, and composite search settings that are specified in the search field.
119 * If no search criteria is provided, all records are returned.
121 * @param $search search array parameters
122 * @param $isAndCondition specifies if AND condition is used for multiple criteria. Defaults to true.
123 * @return ProcessingResult which contains validation messages, internal error messages, and the data
124 * payload.
126 public function getAll($search = array(), $isAndCondition = true)
128 if (!empty($search)) {
129 $fields = $this->getFields();
130 $validKeys = array_combine($fields, $fields);
132 // We need to be backwards compatible with all other uses of the service so we are going to make this a
133 // exact match string param on everything, but only if they are not sending in any Search Field options
134 foreach ($search as $fieldName => $fieldValue) {
135 if (isset($validKeys[$fieldName]) && !($fieldValue instanceof ISearchField)) {
136 $search[$fieldName] = new StringSearchField($fieldName, $fieldValue, SearchModifier::EXACT, $isAndCondition);
140 return $this->search($search, $isAndCondition);
143 public function getAllWithIds(array $ids)
145 $idField = new TokenSearchField('id', $ids);
146 return $this->search(['id' => $idField]);
150 * Returns a single practitioner record by id.
151 * @param $uuid - The practitioner uuid identifier in string format.
152 * @return ProcessingResult which contains validation messages, internal error messages, and the data
153 * payload.
155 public function getOne($uuid)
157 $processingResult = new ProcessingResult();
159 $isValid = $this->practitionerValidator->validateId("uuid", "users", $uuid, true);
161 if ($isValid !== true) {
162 $validationMessages = [
163 'uuid' => ["invalid or nonexisting value" => " value " . $uuid]
165 $processingResult->setValidationMessages($validationMessages);
166 return $processingResult;
169 // there should not be a single duplicate id so we will grab that
170 $search = ['uuid' => new TokenSearchField('uuid', new TokenSearchValue($uuid, null, true))];
171 $results = $this->search($search);
172 $data = $results->getData();
173 if (count($data) > 1) {
174 // we will log this error and return just the single value
175 $results->setData([$data[0]]);
176 (new SystemLogger())->error("PractionerService->getOne() Duplicate records found for uuid", ['uuid' => $uuid]);
178 return $results;
183 * Inserts a new practitioner record.
185 * @param $data The practitioner fields (array) to insert.
186 * @return ProcessingResult which contains validation messages, internal error messages, and the data
187 * payload.
189 public function insert($data)
191 $processingResult = $this->practitionerValidator->validate(
192 $data,
193 PractitionerValidator::DATABASE_INSERT_CONTEXT
196 if (!$processingResult->isValid()) {
197 return $processingResult;
200 $data['uuid'] = UuidRegistry::getRegistryForTable(self::PRACTITIONER_TABLE)->createUuid();
202 $query = $this->buildInsertColumns($data);
203 $sql = " INSERT INTO users SET ";
204 $sql .= $query['set'];
206 $results = sqlInsert(
207 $sql,
208 $query['bind']
211 if ($results) {
212 $processingResult->addData(array(
213 'id' => $results,
214 'uuid' => UuidRegistry::uuidToString($data['uuid'])
216 } else {
217 $processingResult->addInternalError("error processing SQL Insert");
220 return $processingResult;
225 * Updates an existing practitioner record.
227 * @param $uuid - The practitioner uuid identifier in string format used for update.
228 * @param $data - The updated practitioner data fields
229 * @return ProcessingResult which contains validation messages, internal error messages, and the data
230 * payload.
232 public function update($uuid, $data)
234 if (empty($data)) {
235 $processingResult = new ProcessingResult();
236 $processingResult->setValidationMessages("Invalid Data");
237 return $processingResult;
239 $data["uuid"] = $uuid;
240 $processingResult = $this->practitionerValidator->validate(
241 $data,
242 PractitionerValidator::DATABASE_UPDATE_CONTEXT
244 if (!$processingResult->isValid()) {
245 return $processingResult;
248 $query = $this->buildUpdateColumns($data);
250 $sql = " UPDATE users SET ";
251 $sql .= $query['set'];
252 $sql .= " WHERE `uuid` = ?";
255 $uuidBinary = UuidRegistry::uuidToBytes($uuid);
256 array_push($query['bind'], $uuidBinary);
258 $sqlResult = sqlStatement($sql, $query['bind']);
260 if (!$sqlResult) {
261 $processingResult->addErrorMessage("error processing SQL Update");
262 } else {
263 $processingResult = $this->getOne($uuid);
265 return $processingResult;