Entity.php 6.5 KB

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