summaryrefslogtreecommitdiff
path: root/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicGetAction.php
blob: 23d47a13c5321f296c03c470b6ecdda37a3e3ed6 (plain)
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
<?php

namespace Civi\Api4\Generic;

use Civi\API\Exception\NotImplementedException;

/**
 * Retrieve items based on criteria specified in the 'where' param.
 *
 * Use the 'select' param to determine which fields are returned, defaults to *.
 */
class BasicGetAction extends AbstractGetAction {
  use Traits\ArrayQueryActionTrait;

  /**
   * @var callable
   *
   * Function(BasicGetAction $thisAction) => array<array>
   */
  private $getter;

  /**
   * Basic Get constructor.
   *
   * @param string $entityName
   * @param string $actionName
   * @param callable $getter
   */
  public function __construct($entityName, $actionName, $getter = NULL) {
    parent::__construct($entityName, $actionName);
    $this->getter = $getter;
  }

  /**
   * Fetch results from the getter then apply filter/sort/select/limit.
   *
   * @param \Civi\Api4\Generic\Result $result
   */
  public function _run(Result $result) {
    $values = $this->getRecords();
    $result->exchangeArray($this->queryArray($values));
  }

  /**
   * This Basic Get class is a general-purpose api for non-DAO-based entities.
   *
   * Useful for fetching records from files or other places.
   * You can specify any php function to retrieve the records, and this class will
   * automatically filter, sort, select & limit the raw data from your callback.
   *
   * You can implement this action in one of two ways:
   * 1. Use this class directly by passing a callable ($getter) to the constructor.
   * 2. Extend this class and override this function.
   *
   * Either way, this function should return an array of arrays, each representing one retrieved object.
   *
   * The simplest thing for your getter function to do is return every full record
   * and allow this class to automatically do the sorting and filtering.
   *
   * Sometimes however that may not be practical for performance reasons.
   * To optimize your getter, it can use the following helpers from $this:
   *
   * Use this->_itemsToGet() to match records to field values in the WHERE clause.
   * Note the WHERE clause can potentially be very complex and it is not recommended
   * to parse $this->where yourself.
   *
   * Use $this->_isFieldSelected() to check if a field value is called for - useful
   * if loading the field involves expensive calculations.
   *
   * Be careful not to make assumptions, e.g. if LIMIT 100 is specified and your getter "helpfully" truncates the list
   * at 100 without accounting for WHERE, ORDER BY and LIMIT clauses, the final filtered result may be very incorrect.
   *
   * @return array
   * @throws \Civi\API\Exception\NotImplementedException
   */
  protected function getRecords() {
    if (is_callable($this->getter)) {
      return call_user_func($this->getter, $this);
    }
    throw new NotImplementedException('Getter function not found for api4 ' . $this->getEntityName() . '::' . $this->getActionName());
  }

  /**
   * Helper to parse the WHERE param for getRecords to perform simple pre-filtering.
   *
   * This is intended to optimize some common use-cases e.g. calling the api to get
   * one or more records by name or id.
   *
   * Ex: If getRecords fetches a long list of items each with a unique name,
   * but the user has specified a single record to retrieve, you can optimize the call
   * by checking $this->_itemsToGet('name') and only fetching the item(s) with that name.
   *
   * @param string $field
   * @return array|null
   */
  public function _itemsToGet($field) {
    foreach ($this->where as $clause) {
      if ($clause[0] == $field && in_array($clause[1], ['=', 'IN'])) {
        return (array) $clause[2];
      }
    }
    return NULL;
  }

  /**
   * Helper to see if a field should be selected by the getRecords function.
   *
   * Checks the SELECT, WHERE and ORDER BY params to see what fields are needed.
   *
   * Note that if no SELECT clause has been set then all fields should be selected
   * and this function will always return TRUE.
   *
   * @param string $field
   * @return bool
   */
  public function _isFieldSelected($field) {
    if (!$this->select || in_array($field, $this->select) || isset($this->orderBy[$field])) {
      return TRUE;
    }
    return $this->_whereContains($field, $this->where);
  }

  /**
   * Walk through the where clause and check if a field is in use.
   *
   * @param string $field
   * @param array $clauses
   * @return bool
   */
  private function _whereContains($field, $clauses) {
    foreach ($clauses as $clause) {
      if (is_array($clause) && is_string($clause[0])) {
        if ($clause[0] == $field) {
          return TRUE;
        }
        elseif (is_array($clause[1])) {
          return $this->_whereContains($field, $clause[1]);
        }
      }
    }
    return FALSE;
  }

}