Entity.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Bernhard Posselt <dev@bernhard-posselt.com>
  6. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  7. * @author Daniel Kesselberg <mail@danielkesselberg.de>
  8. * @author Joas Schilling <coding@schilljs.com>
  9. * @author Morris Jobke <hey@morrisjobke.de>
  10. *
  11. * @license AGPL-3.0
  12. *
  13. * This code is free software: you can redistribute it and/or modify
  14. * it under the terms of the GNU Affero General Public License, version 3,
  15. * as published by the Free Software Foundation.
  16. *
  17. * This program is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU Affero General Public License for more details.
  21. *
  22. * You should have received a copy of the GNU Affero General Public License, version 3,
  23. * along with this program. If not, see <http://www.gnu.org/licenses/>
  24. *
  25. */
  26. namespace OCP\AppFramework\Db;
  27. use function lcfirst;
  28. use function substr;
  29. /**
  30. * @method int getId()
  31. * @method void setId(int $id)
  32. * @since 7.0.0
  33. * @psalm-consistent-constructor
  34. */
  35. abstract class Entity {
  36. /**
  37. * @var int
  38. */
  39. public $id;
  40. private array $_updatedFields = [];
  41. private array $_fieldTypes = ['id' => 'integer'];
  42. /**
  43. * Simple alternative constructor for building entities from a request
  44. * @param array $params the array which was obtained via $this->params('key')
  45. * in the controller
  46. * @since 7.0.0
  47. */
  48. public static function fromParams(array $params): static {
  49. $instance = new static();
  50. foreach ($params as $key => $value) {
  51. $method = 'set' . ucfirst($key);
  52. $instance->$method($value);
  53. }
  54. return $instance;
  55. }
  56. /**
  57. * Maps the keys of the row array to the attributes
  58. * @param array $row the row to map onto the entity
  59. * @since 7.0.0
  60. */
  61. public static function fromRow(array $row): static {
  62. $instance = new static();
  63. foreach ($row as $key => $value) {
  64. $prop = $instance->columnToProperty($key);
  65. $instance->setter($prop, [$value]);
  66. }
  67. $instance->resetUpdatedFields();
  68. return $instance;
  69. }
  70. /**
  71. * @return array with attribute and type
  72. * @since 7.0.0
  73. */
  74. public function getFieldTypes() {
  75. return $this->_fieldTypes;
  76. }
  77. /**
  78. * Marks the entity as clean needed for setting the id after the insertion
  79. * @since 7.0.0
  80. */
  81. public function resetUpdatedFields() {
  82. $this->_updatedFields = [];
  83. }
  84. /**
  85. * Generic setter for properties
  86. * @since 7.0.0
  87. */
  88. protected function setter(string $name, array $args): void {
  89. // setters should only work for existing attributes
  90. if (property_exists($this, $name)) {
  91. if ($this->$name === $args[0]) {
  92. return;
  93. }
  94. $this->markFieldUpdated($name);
  95. // if type definition exists, cast to correct type
  96. if ($args[0] !== null && array_key_exists($name, $this->_fieldTypes)) {
  97. $type = $this->_fieldTypes[$name];
  98. if ($type === 'blob') {
  99. // (B)LOB is treated as string when we read from the DB
  100. if (is_resource($args[0])) {
  101. $args[0] = stream_get_contents($args[0]);
  102. }
  103. $type = 'string';
  104. }
  105. if ($type === 'datetime') {
  106. if (!$args[0] instanceof \DateTime) {
  107. $args[0] = new \DateTime($args[0]);
  108. }
  109. } elseif ($type === 'json') {
  110. if (!is_array($args[0])) {
  111. $args[0] = json_decode($args[0], true);
  112. }
  113. } else {
  114. settype($args[0], $type);
  115. }
  116. }
  117. $this->$name = $args[0];
  118. } else {
  119. throw new \BadFunctionCallException($name .
  120. ' is not a valid attribute');
  121. }
  122. }
  123. /**
  124. * Generic getter for properties
  125. * @since 7.0.0
  126. */
  127. protected function getter(string $name): mixed {
  128. // getters should only work for existing attributes
  129. if (property_exists($this, $name)) {
  130. return $this->$name;
  131. } else {
  132. throw new \BadFunctionCallException($name .
  133. ' is not a valid attribute');
  134. }
  135. }
  136. /**
  137. * Each time a setter is called, push the part after set
  138. * into an array: for instance setId will save Id in the
  139. * updated fields array so it can be easily used to create the
  140. * getter method
  141. * @since 7.0.0
  142. */
  143. public function __call(string $methodName, array $args) {
  144. if (str_starts_with($methodName, 'set')) {
  145. $this->setter(lcfirst(substr($methodName, 3)), $args);
  146. } elseif (str_starts_with($methodName, 'get')) {
  147. return $this->getter(lcfirst(substr($methodName, 3)));
  148. } elseif ($this->isGetterForBoolProperty($methodName)) {
  149. return $this->getter(lcfirst(substr($methodName, 2)));
  150. } else {
  151. throw new \BadFunctionCallException($methodName .
  152. ' does not exist');
  153. }
  154. }
  155. /**
  156. * @param string $methodName
  157. * @return bool
  158. * @since 18.0.0
  159. */
  160. protected function isGetterForBoolProperty(string $methodName): bool {
  161. if (str_starts_with($methodName, 'is')) {
  162. $fieldName = lcfirst(substr($methodName, 2));
  163. return isset($this->_fieldTypes[$fieldName]) && str_starts_with($this->_fieldTypes[$fieldName], 'bool');
  164. }
  165. return false;
  166. }
  167. /**
  168. * Mark am attribute as updated
  169. * @param string $attribute the name of the attribute
  170. * @since 7.0.0
  171. */
  172. protected function markFieldUpdated(string $attribute): void {
  173. $this->_updatedFields[$attribute] = true;
  174. }
  175. /**
  176. * Transform a database columnname to a property
  177. * @param string $columnName the name of the column
  178. * @return string the property name
  179. * @since 7.0.0
  180. */
  181. public function columnToProperty($columnName) {
  182. $parts = explode('_', $columnName);
  183. $property = null;
  184. foreach ($parts as $part) {
  185. if ($property === null) {
  186. $property = $part;
  187. } else {
  188. $property .= ucfirst($part);
  189. }
  190. }
  191. return $property;
  192. }
  193. /**
  194. * Transform a property to a database column name
  195. * @param string $property the name of the property
  196. * @return string the column name
  197. * @since 7.0.0
  198. */
  199. public function propertyToColumn($property) {
  200. $parts = preg_split('/(?=[A-Z])/', $property);
  201. $column = null;
  202. foreach ($parts as $part) {
  203. if ($column === null) {
  204. $column = $part;
  205. } else {
  206. $column .= '_' . lcfirst($part);
  207. }
  208. }
  209. return $column;
  210. }
  211. /**
  212. * @return array array of updated fields for update query
  213. * @since 7.0.0
  214. */
  215. public function getUpdatedFields() {
  216. return $this->_updatedFields;
  217. }
  218. /**
  219. * Adds type information for a field so that its automatically casted to
  220. * that value once its being returned from the database
  221. * @param string $fieldName the name of the attribute
  222. * @param string $type the type which will be used to call settype()
  223. * @since 7.0.0
  224. */
  225. protected function addType($fieldName, $type) {
  226. $this->_fieldTypes[$fieldName] = $type;
  227. }
  228. /**
  229. * Slugify the value of a given attribute
  230. * Warning: This doesn't result in a unique value
  231. * @param string $attributeName the name of the attribute, which value should be slugified
  232. * @return string slugified value
  233. * @since 7.0.0
  234. * @deprecated 24.0.0
  235. */
  236. public function slugify($attributeName) {
  237. // toSlug should only work for existing attributes
  238. if (property_exists($this, $attributeName)) {
  239. $value = $this->$attributeName;
  240. // replace everything except alphanumeric with a single '-'
  241. $value = preg_replace('/[^A-Za-z0-9]+/', '-', $value);
  242. $value = strtolower($value);
  243. // trim '-'
  244. return trim($value, '-');
  245. } else {
  246. throw new \BadFunctionCallException($attributeName .
  247. ' is not a valid attribute');
  248. }
  249. }
  250. }