Entity.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  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 = ucfirst($instance->columnToProperty($key));
  65. $setter = 'set' . $prop;
  66. $instance->$setter($value);
  67. }
  68. $instance->resetUpdatedFields();
  69. return $instance;
  70. }
  71. /**
  72. * @return array with attribute and type
  73. * @since 7.0.0
  74. */
  75. public function getFieldTypes() {
  76. return $this->_fieldTypes;
  77. }
  78. /**
  79. * Marks the entity as clean needed for setting the id after the insertion
  80. * @since 7.0.0
  81. */
  82. public function resetUpdatedFields() {
  83. $this->_updatedFields = [];
  84. }
  85. /**
  86. * Generic setter for properties
  87. * @since 7.0.0
  88. */
  89. protected function setter(string $name, array $args): void {
  90. // setters should only work for existing attributes
  91. if (property_exists($this, $name)) {
  92. if ($this->$name === $args[0]) {
  93. return;
  94. }
  95. $this->markFieldUpdated($name);
  96. // if type definition exists, cast to correct type
  97. if ($args[0] !== null && array_key_exists($name, $this->_fieldTypes)) {
  98. $type = $this->_fieldTypes[$name];
  99. if ($type === 'blob') {
  100. // (B)LOB is treated as string when we read from the DB
  101. if (is_resource($args[0])) {
  102. $args[0] = stream_get_contents($args[0]);
  103. }
  104. $type = 'string';
  105. }
  106. if ($type === 'datetime') {
  107. if (!$args[0] instanceof \DateTime) {
  108. $args[0] = new \DateTime($args[0]);
  109. }
  110. } elseif ($type === 'json') {
  111. if (!is_array($args[0])) {
  112. $args[0] = json_decode($args[0], true);
  113. }
  114. } else {
  115. settype($args[0], $type);
  116. }
  117. }
  118. $this->$name = $args[0];
  119. } else {
  120. throw new \BadFunctionCallException($name .
  121. ' is not a valid attribute');
  122. }
  123. }
  124. /**
  125. * Generic getter for properties
  126. * @since 7.0.0
  127. */
  128. protected function getter(string $name): mixed {
  129. // getters should only work for existing attributes
  130. if (property_exists($this, $name)) {
  131. return $this->$name;
  132. } else {
  133. throw new \BadFunctionCallException($name .
  134. ' is not a valid attribute');
  135. }
  136. }
  137. /**
  138. * Each time a setter is called, push the part after set
  139. * into an array: for instance setId will save Id in the
  140. * updated fields array so it can be easily used to create the
  141. * getter method
  142. * @since 7.0.0
  143. */
  144. public function __call(string $methodName, array $args) {
  145. if (strpos($methodName, 'set') === 0) {
  146. $this->setter(lcfirst(substr($methodName, 3)), $args);
  147. } elseif (strpos($methodName, 'get') === 0) {
  148. return $this->getter(lcfirst(substr($methodName, 3)));
  149. } elseif ($this->isGetterForBoolProperty($methodName)) {
  150. return $this->getter(lcfirst(substr($methodName, 2)));
  151. } else {
  152. throw new \BadFunctionCallException($methodName .
  153. ' does not exist');
  154. }
  155. }
  156. /**
  157. * @param string $methodName
  158. * @return bool
  159. * @since 18.0.0
  160. */
  161. protected function isGetterForBoolProperty(string $methodName): bool {
  162. if (strpos($methodName, 'is') === 0) {
  163. $fieldName = lcfirst(substr($methodName, 2));
  164. return isset($this->_fieldTypes[$fieldName]) && strpos($this->_fieldTypes[$fieldName], 'bool') === 0;
  165. }
  166. return false;
  167. }
  168. /**
  169. * Mark am attribute as updated
  170. * @param string $attribute the name of the attribute
  171. * @since 7.0.0
  172. */
  173. protected function markFieldUpdated(string $attribute): void {
  174. $this->_updatedFields[$attribute] = true;
  175. }
  176. /**
  177. * Transform a database columnname to a property
  178. * @param string $columnName the name of the column
  179. * @return string the property name
  180. * @since 7.0.0
  181. */
  182. public function columnToProperty($columnName) {
  183. $parts = explode('_', $columnName);
  184. $property = null;
  185. foreach ($parts as $part) {
  186. if ($property === null) {
  187. $property = $part;
  188. } else {
  189. $property .= ucfirst($part);
  190. }
  191. }
  192. return $property;
  193. }
  194. /**
  195. * Transform a property to a database column name
  196. * @param string $property the name of the property
  197. * @return string the column name
  198. * @since 7.0.0
  199. */
  200. public function propertyToColumn($property) {
  201. $parts = preg_split('/(?=[A-Z])/', $property);
  202. $column = null;
  203. foreach ($parts as $part) {
  204. if ($column === null) {
  205. $column = $part;
  206. } else {
  207. $column .= '_' . lcfirst($part);
  208. }
  209. }
  210. return $column;
  211. }
  212. /**
  213. * @return array array of updated fields for update query
  214. * @since 7.0.0
  215. */
  216. public function getUpdatedFields() {
  217. return $this->_updatedFields;
  218. }
  219. /**
  220. * Adds type information for a field so that its automatically casted to
  221. * that value once its being returned from the database
  222. * @param string $fieldName the name of the attribute
  223. * @param string $type the type which will be used to call settype()
  224. * @since 7.0.0
  225. */
  226. protected function addType($fieldName, $type) {
  227. $this->_fieldTypes[$fieldName] = $type;
  228. }
  229. /**
  230. * Slugify the value of a given attribute
  231. * Warning: This doesn't result in a unique value
  232. * @param string $attributeName the name of the attribute, which value should be slugified
  233. * @return string slugified value
  234. * @since 7.0.0
  235. * @deprecated 24.0.0
  236. */
  237. public function slugify($attributeName) {
  238. // toSlug should only work for existing attributes
  239. if (property_exists($this, $attributeName)) {
  240. $value = $this->$attributeName;
  241. // replace everything except alphanumeric with a single '-'
  242. $value = preg_replace('/[^A-Za-z0-9]+/', '-', $value);
  243. $value = strtolower($value);
  244. // trim '-'
  245. return trim($value, '-');
  246. } else {
  247. throw new \BadFunctionCallException($attributeName .
  248. ' is not a valid attribute');
  249. }
  250. }
  251. }