spinen /
laravel-clickup
| 1 | <?php |
||
| 2 | |||
| 3 | namespace Spinen\ClickUp\Support; |
||
| 4 | |||
| 5 | use ArrayAccess; |
||
| 6 | use GuzzleHttp\Exception\GuzzleException; |
||
| 7 | use Illuminate\Contracts\Support\Arrayable; |
||
| 8 | use Illuminate\Contracts\Support\Jsonable; |
||
| 9 | use Illuminate\Database\Eloquent\Concerns\HasAttributes; |
||
| 10 | use Illuminate\Database\Eloquent\Concerns\HasTimestamps; |
||
| 11 | use Illuminate\Database\Eloquent\Concerns\HidesAttributes; |
||
| 12 | use Illuminate\Database\Eloquent\JsonEncodingException; |
||
| 13 | use Illuminate\Support\Carbon; |
||
| 14 | use Illuminate\Support\Facades\Date; |
||
| 15 | use Illuminate\Support\Str; |
||
| 16 | use JsonSerializable; |
||
| 17 | use LogicException; |
||
| 18 | use Spinen\ClickUp\Concerns\HasClient; |
||
| 19 | use Spinen\ClickUp\Exceptions\InvalidRelationshipException; |
||
| 20 | use Spinen\ClickUp\Exceptions\ModelNotFoundException; |
||
| 21 | use Spinen\ClickUp\Exceptions\ModelReadonlyException; |
||
| 22 | use Spinen\ClickUp\Exceptions\NoClientException; |
||
| 23 | use Spinen\ClickUp\Exceptions\TokenException; |
||
| 24 | use Spinen\ClickUp\Exceptions\UnableToSaveException; |
||
| 25 | use Spinen\ClickUp\Support\Relations\BelongsTo; |
||
| 26 | use Spinen\ClickUp\Support\Relations\ChildOf; |
||
| 27 | use Spinen\ClickUp\Support\Relations\HasMany; |
||
| 28 | use Spinen\ClickUp\Support\Relations\Relation; |
||
| 29 | |||
| 30 | /** |
||
| 31 | * Class Model |
||
| 32 | * |
||
| 33 | * @package Spinen\ClickUp\Support |
||
| 34 | */ |
||
| 35 | abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializable |
||
| 36 | { |
||
| 37 | use HasAttributes { |
||
| 38 | asDateTime as originalAsDateTime; |
||
| 39 | } |
||
| 40 | use HasClient, HasTimestamps, HidesAttributes; |
||
| 41 | |||
| 42 | /** |
||
| 43 | * Indicates if the model exists. |
||
| 44 | * |
||
| 45 | * @var bool |
||
| 46 | */ |
||
| 47 | public $exists = false; |
||
| 48 | |||
| 49 | /** |
||
| 50 | * Indicates if the IDs are auto-incrementing. |
||
| 51 | * |
||
| 52 | * @var bool |
||
| 53 | */ |
||
| 54 | public $incrementing = false; |
||
| 55 | |||
| 56 | /** |
||
| 57 | * The "type" of the primary key ID. |
||
| 58 | * |
||
| 59 | * @var string |
||
| 60 | */ |
||
| 61 | protected $keyType = 'int'; |
||
| 62 | |||
| 63 | /** |
||
| 64 | * Is resource nested behind parentModel |
||
| 65 | * |
||
| 66 | * Several of the endpoints are nested behind another model for relationship, but then to |
||
| 67 | * interact with the specific model, then are not nested. This property will know when to |
||
| 68 | * keep the specific model nested. |
||
| 69 | * |
||
| 70 | * @var bool |
||
| 71 | */ |
||
| 72 | protected $nested = false; |
||
| 73 | |||
| 74 | /** |
||
| 75 | * Optional parentModel instance |
||
| 76 | * |
||
| 77 | * @var Model $parentModel |
||
| 78 | */ |
||
| 79 | public $parentModel; |
||
| 80 | |||
| 81 | /** |
||
| 82 | * Path to API endpoint. |
||
| 83 | * |
||
| 84 | * @var string |
||
| 85 | */ |
||
| 86 | protected $path = null; |
||
| 87 | |||
| 88 | /** |
||
| 89 | * The primary key for the model. |
||
| 90 | * |
||
| 91 | * @var string |
||
| 92 | */ |
||
| 93 | protected $primaryKey = 'id'; |
||
| 94 | |||
| 95 | /** |
||
| 96 | * Is the model readonly? |
||
| 97 | * |
||
| 98 | * @var bool |
||
| 99 | */ |
||
| 100 | protected $readonlyModel = false; |
||
| 101 | |||
| 102 | /** |
||
| 103 | * The loaded relationships for the model. |
||
| 104 | * |
||
| 105 | * @var array |
||
| 106 | */ |
||
| 107 | protected $relations = []; |
||
| 108 | |||
| 109 | /** |
||
| 110 | * Some of the responses have the collections under a property |
||
| 111 | * |
||
| 112 | * @var string|null |
||
| 113 | */ |
||
| 114 | protected $responseCollectionKey = null; |
||
| 115 | |||
| 116 | /** |
||
| 117 | * Some of the responses have the data under a property |
||
| 118 | * |
||
| 119 | * @var string|null |
||
| 120 | */ |
||
| 121 | protected $responseKey = null; |
||
| 122 | |||
| 123 | /** |
||
| 124 | * Are timestamps in milliseconds? |
||
| 125 | * |
||
| 126 | * @var boolean |
||
| 127 | */ |
||
| 128 | protected $timestampsInMilliseconds = true; |
||
| 129 | |||
| 130 | /** |
||
| 131 | * The name of the "created at" column. |
||
| 132 | * |
||
| 133 | * @var string |
||
| 134 | */ |
||
| 135 | const CREATED_AT = 'created_at'; |
||
| 136 | |||
| 137 | /** |
||
| 138 | * The name of the "updated at" column. |
||
| 139 | * |
||
| 140 | * @var string |
||
| 141 | */ |
||
| 142 | const UPDATED_AT = 'updated_at'; |
||
| 143 | |||
| 144 | /** |
||
| 145 | * Model constructor. |
||
| 146 | * |
||
| 147 | * @param array|null $attributes |
||
| 148 | * @param Model|null $parentModel |
||
| 149 | */ |
||
| 150 | 198 | public function __construct(array $attributes = [], Model $parentModel = null) |
|
| 151 | { |
||
| 152 | // All dates from API comes as epoch with milliseconds |
||
| 153 | 198 | $this->dateFormat = 'Uv'; |
|
| 154 | // None of these models will use timestamps, but need the date casting |
||
| 155 | 198 | $this->timestamps = false; |
|
| 156 | |||
| 157 | 198 | $this->syncOriginal(); |
|
| 158 | |||
| 159 | 198 | $this->fill($attributes); |
|
| 160 | 198 | $this->parentModel = $parentModel; |
|
| 161 | 198 | } |
|
| 162 | |||
| 163 | /** |
||
| 164 | * Dynamically retrieve attributes on the model. |
||
| 165 | * |
||
| 166 | * @param string $key |
||
| 167 | * |
||
| 168 | * @return mixed |
||
| 169 | */ |
||
| 170 | 94 | public function __get($key) |
|
| 171 | { |
||
| 172 | 94 | return $this->getAttribute($key); |
|
| 173 | } |
||
| 174 | |||
| 175 | /** |
||
| 176 | * Determine if an attribute or relation exists on the model. |
||
| 177 | * |
||
| 178 | * @param string $key |
||
| 179 | * |
||
| 180 | * @return bool |
||
| 181 | */ |
||
| 182 | 2 | public function __isset($key) |
|
| 183 | { |
||
| 184 | 2 | return $this->offsetExists($key); |
|
| 185 | } |
||
| 186 | |||
| 187 | /** |
||
| 188 | * Dynamically set attributes on the model. |
||
| 189 | * |
||
| 190 | * @param string $key |
||
| 191 | * @param mixed $value |
||
| 192 | * |
||
| 193 | * @return void |
||
| 194 | * @throws ModelReadonlyException |
||
| 195 | */ |
||
| 196 | 79 | public function __set($key, $value) |
|
| 197 | { |
||
| 198 | 79 | if ($this->readonlyModel) { |
|
| 199 | 1 | throw new ModelReadonlyException(); |
|
| 200 | } |
||
| 201 | |||
| 202 | 78 | $this->setAttribute($key, $value); |
|
| 203 | 78 | } |
|
| 204 | |||
| 205 | /** |
||
| 206 | * Convert the model to its string representation. |
||
| 207 | * |
||
| 208 | * @return string |
||
| 209 | */ |
||
| 210 | 1 | public function __toString() |
|
| 211 | { |
||
| 212 | 1 | return $this->toJson(); |
|
| 213 | } |
||
| 214 | |||
| 215 | /** |
||
| 216 | * Unset an attribute on the model. |
||
| 217 | * |
||
| 218 | * @param string $key |
||
| 219 | * |
||
| 220 | * @return void |
||
| 221 | */ |
||
| 222 | 2 | public function __unset($key) |
|
| 223 | { |
||
| 224 | 2 | $this->offsetUnset($key); |
|
| 225 | 2 | } |
|
| 226 | |||
| 227 | /** |
||
| 228 | * Return a timestamp as DateTime object. |
||
| 229 | * |
||
| 230 | * @param mixed $value |
||
| 231 | * @return Carbon |
||
| 232 | */ |
||
| 233 | 8 | protected function asDateTime($value) |
|
| 234 | { |
||
| 235 | 8 | if (is_numeric($value) && $this->timestampsInMilliseconds) { |
|
| 236 | 1 | return Date::createFromTimestampMs($value); |
|
| 237 | } |
||
| 238 | |||
| 239 | 7 | return $this->originalAsDateTime($value); |
|
| 240 | } |
||
| 241 | |||
| 242 | /** |
||
| 243 | * Assume foreign key |
||
| 244 | * |
||
| 245 | * @param string $related |
||
| 246 | * |
||
| 247 | * @return string |
||
| 248 | */ |
||
| 249 | 30 | protected function assumeForeignKey($related): string |
|
| 250 | { |
||
| 251 | 30 | return Str::snake((new $related())->getResponseKey()) . '_id'; |
|
| 252 | } |
||
| 253 | |||
| 254 | /** |
||
| 255 | * Relationship that makes the model belongs to another model |
||
| 256 | * |
||
| 257 | * @param string $related |
||
| 258 | * @param string|null $foreignKey |
||
| 259 | * |
||
| 260 | * @return BelongsTo |
||
| 261 | * @throws InvalidRelationshipException |
||
| 262 | * @throws ModelNotFoundException |
||
| 263 | * @throws NoClientException |
||
| 264 | */ |
||
| 265 | 8 | public function belongsTo($related, $foreignKey = null): BelongsTo |
|
| 266 | { |
||
| 267 | 8 | $foreignKey = $foreignKey ?? $this->assumeForeignKey($related); |
|
| 268 | |||
| 269 | 8 | $builder = (new Builder())->setClass($related) |
|
| 270 | 8 | ->setClient($this->getClient()); |
|
| 271 | |||
| 272 | 8 | return new BelongsTo($builder, $this, $foreignKey); |
|
| 273 | } |
||
| 274 | |||
| 275 | /** |
||
| 276 | * Relationship that makes the model child to another model |
||
| 277 | * |
||
| 278 | * @param string $related |
||
| 279 | * @param string|null $foreignKey |
||
| 280 | * |
||
| 281 | * @return ChildOf |
||
| 282 | * @throws InvalidRelationshipException |
||
| 283 | * @throws ModelNotFoundException |
||
| 284 | * @throws NoClientException |
||
| 285 | */ |
||
| 286 | 25 | public function childOf($related, $foreignKey = null): ChildOf |
|
| 287 | { |
||
| 288 | 25 | $foreignKey = $foreignKey ?? $this->assumeForeignKey($related); |
|
| 289 | |||
| 290 | 25 | $builder = (new Builder())->setClass($related) |
|
| 291 | 25 | ->setClient($this->getClient()) |
|
| 292 | 25 | ->setParent($this); |
|
| 293 | |||
| 294 | 25 | return new ChildOf($builder, $this, $foreignKey); |
|
| 295 | } |
||
| 296 | |||
| 297 | /** |
||
| 298 | * Delete the model from ClickUp |
||
| 299 | * |
||
| 300 | * @return boolean |
||
| 301 | * @throws NoClientException |
||
| 302 | * @throws TokenException |
||
| 303 | */ |
||
| 304 | 3 | public function delete(): bool |
|
| 305 | { |
||
| 306 | // TODO: Make sure that the model supports being deleted |
||
| 307 | 3 | if ($this->readonlyModel) { |
|
| 308 | 1 | return false; |
|
| 309 | } |
||
| 310 | |||
| 311 | try { |
||
| 312 | 2 | $this->getClient() |
|
| 313 | 2 | ->delete($this->getPath()); |
|
| 314 | |||
| 315 | 1 | return true; |
|
| 316 | 1 | } catch (GuzzleException $e) { |
|
| 317 | // TODO: Do something with the error |
||
| 318 | |||
| 319 | 1 | return false; |
|
| 320 | } |
||
| 321 | } |
||
| 322 | |||
| 323 | /** |
||
| 324 | * Fill the model with the supplied properties |
||
| 325 | * |
||
| 326 | * @param array $attributes |
||
| 327 | * |
||
| 328 | * @return $this |
||
| 329 | */ |
||
| 330 | 198 | public function fill(array $attributes = []): self |
|
| 331 | { |
||
| 332 | 198 | foreach ($attributes as $attribute => $value) { |
|
| 333 | 59 | $this->setAttribute($attribute, $value); |
|
| 334 | } |
||
| 335 | |||
| 336 | 198 | return $this; |
|
| 337 | } |
||
| 338 | |||
| 339 | /** |
||
| 340 | * Get the value indicating whether the IDs are incrementing. |
||
| 341 | * |
||
| 342 | * @return bool |
||
| 343 | */ |
||
| 344 | 130 | public function getIncrementing(): bool |
|
| 345 | { |
||
| 346 | 130 | return $this->incrementing; |
|
| 347 | } |
||
| 348 | |||
| 349 | /** |
||
| 350 | * Get the value of the model's primary key. |
||
| 351 | * |
||
| 352 | * @return mixed |
||
| 353 | */ |
||
| 354 | 23 | public function getKey() |
|
| 355 | { |
||
| 356 | 23 | return $this->getAttribute($this->getKeyName()); |
|
| 357 | } |
||
| 358 | |||
| 359 | /** |
||
| 360 | * Get the primary key for the model. |
||
| 361 | * |
||
| 362 | * @return string |
||
| 363 | */ |
||
| 364 | 57 | public function getKeyName(): string |
|
| 365 | { |
||
| 366 | 57 | return $this->primaryKey; |
|
| 367 | } |
||
| 368 | |||
| 369 | /** |
||
| 370 | * Get the auto-incrementing key type. |
||
| 371 | * |
||
| 372 | * @return string |
||
| 373 | */ |
||
| 374 | 1 | public function getKeyType(): string |
|
| 375 | { |
||
| 376 | 1 | return $this->keyType; |
|
| 377 | } |
||
| 378 | |||
| 379 | /** |
||
| 380 | * Build API path |
||
| 381 | * |
||
| 382 | * Put anything on the end of the URI that is passed in |
||
| 383 | * |
||
| 384 | * @param string|null $extra |
||
| 385 | * @param array|null $query |
||
| 386 | * |
||
| 387 | * @return string |
||
| 388 | */ |
||
| 389 | 22 | public function getPath($extra = null, array $query = []): ?string |
|
| 390 | { |
||
| 391 | // Start with path to resource without "/" on end |
||
| 392 | 22 | $path = rtrim($this->path, '/'); |
|
| 393 | |||
| 394 | // If have an id, then put it on the end |
||
| 395 | 22 | if ($this->getKey()) { |
|
| 396 | 6 | $path .= '/' . $this->getKey(); |
|
| 397 | } |
||
| 398 | |||
| 399 | // Stick any extra things on the end |
||
| 400 | 22 | if (!is_null($extra)) { |
|
| 401 | 4 | $path .= '/' . ltrim($extra, '/'); |
|
| 402 | } |
||
| 403 | |||
| 404 | // Convert query to querystring format and put on the end |
||
| 405 | 22 | if (!empty($query)) { |
|
| 406 | 2 | $path .= '?' . http_build_query($query); |
|
| 407 | } |
||
| 408 | |||
| 409 | // If there is a parentModel & not have an id (unless for nested), then prepend parentModel |
||
| 410 | 22 | if (!is_null($this->parentModel) && (!$this->getKey() || $this->isNested())) { |
|
| 411 | 4 | return $this->parentModel->getPath($path); |
|
| 412 | } |
||
| 413 | |||
| 414 | 22 | return $path; |
|
| 415 | } |
||
| 416 | |||
| 417 | /** |
||
| 418 | * Get a relationship value from a method. |
||
| 419 | * |
||
| 420 | * @param string $method |
||
| 421 | * |
||
| 422 | * @return mixed |
||
| 423 | * |
||
| 424 | * @throws LogicException |
||
| 425 | */ |
||
| 426 | 5 | public function getRelationshipFromMethod($method) |
|
| 427 | { |
||
| 428 | 5 | $relation = $this->{$method}(); |
|
| 429 | |||
| 430 | 5 | if (!$relation instanceof Relation) { |
|
| 431 | 2 | $exception_message = is_null($relation) |
|
| 432 | 1 | ? '%s::%s must return a relationship instance, but "null" was returned. Was the "return" keyword used?' |
|
| 433 | 2 | : '%s::%s must return a relationship instance.'; |
|
| 434 | |||
| 435 | 2 | throw new LogicException( |
|
| 436 | 2 | sprintf($exception_message, static::class, $method) |
|
| 437 | ); |
||
| 438 | } |
||
| 439 | |||
| 440 | 3 | return tap( |
|
| 441 | 3 | $relation->getResults(), |
|
| 442 | function ($results) use ($method) { |
||
| 443 | 3 | $this->setRelation($method, $results); |
|
| 444 | 3 | } |
|
| 445 | ); |
||
| 446 | } |
||
| 447 | |||
| 448 | /** |
||
| 449 | * Name of the wrapping key when response is a collection |
||
| 450 | * |
||
| 451 | * If none provided, assume plural version responseKey |
||
| 452 | * |
||
| 453 | * @return string|null |
||
| 454 | */ |
||
| 455 | 14 | public function getResponseCollectionKey(): ?string |
|
| 456 | { |
||
| 457 | 14 | return $this->responseCollectionKey ?? Str::plural($this->getResponseKey()); |
|
| 458 | } |
||
| 459 | |||
| 460 | /** |
||
| 461 | * Name of the wrapping key of response |
||
| 462 | * |
||
| 463 | * If none provided, assume camelCase of class name |
||
| 464 | * |
||
| 465 | * @return string|null |
||
| 466 | */ |
||
| 467 | 46 | public function getResponseKey(): ?string |
|
| 468 | { |
||
| 469 | 46 | return $this->responseKey ?? Str::camel(class_basename(static::class)); |
|
| 470 | } |
||
| 471 | |||
| 472 | /** |
||
| 473 | * Many of the results include collection of related data, so cast it |
||
| 474 | * |
||
| 475 | * @param string $related |
||
| 476 | * @param array $given |
||
| 477 | * @param bool $reset Some of the values are nested under a property, so peel it off |
||
| 478 | * |
||
| 479 | * @return Collection |
||
| 480 | * @throws NoClientException |
||
| 481 | */ |
||
| 482 | 20 | public function givenMany($related, $given, $reset = false): Collection |
|
| 483 | { |
||
| 484 | /** @var Model $model */ |
||
| 485 | 20 | $model = (new $related([], $this->parentModel))->setClient($this->getClient()); |
|
| 486 | |||
| 487 | 20 | return (new Collection($given))->map( |
|
| 488 | function ($attributes) use ($model, $reset) { |
||
| 489 | 1 | return $model->newFromBuilder($reset ? reset($attributes) : $attributes); |
|
| 490 | 20 | } |
|
| 491 | ); |
||
| 492 | } |
||
| 493 | |||
| 494 | /** |
||
| 495 | * Many of the results include related data, so cast it to object |
||
| 496 | * |
||
| 497 | * @param string $related |
||
| 498 | * @param array $attributes |
||
| 499 | * @param bool $reset Some of the values are nested under a property, so peel it off |
||
| 500 | * |
||
| 501 | * @return Model |
||
| 502 | * @throws NoClientException |
||
| 503 | */ |
||
| 504 | 16 | public function givenOne($related, $attributes, $reset = false): Model |
|
| 505 | { |
||
| 506 | 16 | return (new $related([], $this->parentModel))->setClient($this->getClient()) |
|
| 507 | 16 | ->newFromBuilder($reset ? reset($attributes) : $attributes); |
|
| 508 | } |
||
| 509 | |||
| 510 | /** |
||
| 511 | * Relationship that makes the model have a collection of another model |
||
| 512 | * |
||
| 513 | * @param string $related |
||
| 514 | * |
||
| 515 | * @return HasMany |
||
| 516 | * @throws InvalidRelationshipException |
||
| 517 | * @throws ModelNotFoundException |
||
| 518 | * @throws NoClientException |
||
| 519 | */ |
||
| 520 | 27 | public function hasMany($related): HasMany |
|
| 521 | { |
||
| 522 | 27 | $builder = (new Builder())->setClass($related) |
|
| 523 | 27 | ->setClient($this->getClient()) |
|
| 524 | 27 | ->setParent($this); |
|
| 525 | |||
| 526 | 27 | return new HasMany($builder, $this); |
|
| 527 | } |
||
| 528 | |||
| 529 | /** |
||
| 530 | * Is endpoint nested behind another endpoint |
||
| 531 | * |
||
| 532 | * @return bool |
||
| 533 | */ |
||
| 534 | 2 | public function isNested(): bool |
|
| 535 | { |
||
| 536 | 2 | return $this->nested ?? false; |
|
| 537 | } |
||
| 538 | |||
| 539 | /** |
||
| 540 | * Convert the object into something JSON serializable. |
||
| 541 | * |
||
| 542 | * @return array |
||
| 543 | */ |
||
| 544 | 1 | public function jsonSerialize(): array |
|
| 545 | { |
||
| 546 | 1 | return $this->toArray(); |
|
| 547 | } |
||
| 548 | |||
| 549 | /** |
||
| 550 | * Create a new model instance that is existing. |
||
| 551 | * |
||
| 552 | * @param array $attributes |
||
| 553 | * |
||
| 554 | * @return static |
||
| 555 | */ |
||
| 556 | 24 | public function newFromBuilder($attributes = []): self |
|
| 557 | { |
||
| 558 | 24 | $model = $this->newInstance([], true); |
|
| 559 | |||
| 560 | 24 | $model->setRawAttributes((array)$attributes, true); |
|
| 561 | |||
| 562 | 24 | return $model; |
|
| 563 | } |
||
| 564 | |||
| 565 | /** |
||
| 566 | * Create a new instance of the given model. |
||
| 567 | * |
||
| 568 | * Provides a convenient way for us to generate fresh model instances of this current model. |
||
| 569 | * It is particularly useful during the hydration of new objects via the builder. |
||
| 570 | * |
||
| 571 | * @param array $attributes |
||
| 572 | * @param bool $exists |
||
| 573 | * |
||
| 574 | * @return static |
||
| 575 | */ |
||
| 576 | 29 | public function newInstance(array $attributes = [], $exists = false): self |
|
| 577 | { |
||
| 578 | 29 | $model = (new static($attributes, $this->parentModel))->setClient($this->client); |
|
| 579 | |||
| 580 | 29 | $model->exists = $exists; |
|
| 581 | |||
| 582 | 29 | return $model; |
|
| 583 | } |
||
| 584 | |||
| 585 | /** |
||
| 586 | * Determine if the given attribute exists. |
||
| 587 | * |
||
| 588 | * @param mixed $offset |
||
| 589 | * |
||
| 590 | * @return bool |
||
| 591 | */ |
||
| 592 | 5 | public function offsetExists($offset) |
|
| 593 | { |
||
| 594 | 5 | return !is_null($this->getAttribute($offset)); |
|
| 595 | } |
||
| 596 | |||
| 597 | /** |
||
| 598 | * Get the value for a given offset. |
||
| 599 | * |
||
| 600 | * @param mixed $offset |
||
| 601 | * |
||
| 602 | * @return mixed |
||
| 603 | */ |
||
| 604 | 2 | public function offsetGet($offset) |
|
| 605 | { |
||
| 606 | 2 | return $this->getAttribute($offset); |
|
| 607 | } |
||
| 608 | |||
| 609 | /** |
||
| 610 | * Set the value for a given offset. |
||
| 611 | * |
||
| 612 | * @param mixed $offset |
||
| 613 | * @param mixed $value |
||
| 614 | * |
||
| 615 | * @return void |
||
| 616 | * @throws ModelReadonlyException |
||
| 617 | */ |
||
| 618 | 2 | public function offsetSet($offset, $value): void |
|
| 619 | { |
||
| 620 | 2 | if ($this->readonlyModel) { |
|
| 621 | 1 | throw new ModelReadonlyException(); |
|
| 622 | } |
||
| 623 | |||
| 624 | 1 | $this->setAttribute($offset, $value); |
|
| 625 | 1 | } |
|
| 626 | |||
| 627 | /** |
||
| 628 | * Unset the value for a given offset. |
||
| 629 | * |
||
| 630 | * @param mixed $offset |
||
| 631 | * |
||
| 632 | * @return void |
||
| 633 | */ |
||
| 634 | 3 | public function offsetUnset($offset): void |
|
| 635 | { |
||
| 636 | 3 | unset($this->attributes[$offset], $this->relations[$offset]); |
|
| 637 | 3 | } |
|
| 638 | |||
| 639 | /** |
||
| 640 | * Determine if the given relation is loaded. |
||
| 641 | * |
||
| 642 | * @param string $key |
||
| 643 | * |
||
| 644 | * @return bool |
||
| 645 | */ |
||
| 646 | 60 | public function relationLoaded($key): bool |
|
| 647 | { |
||
| 648 | 60 | return array_key_exists($key, $this->relations); |
|
| 649 | } |
||
| 650 | |||
| 651 | /** |
||
| 652 | * Save the model in ClickUp |
||
| 653 | * |
||
| 654 | * @return bool |
||
| 655 | * @throws NoClientException |
||
| 656 | * @throws TokenException |
||
| 657 | */ |
||
| 658 | 7 | public function save(): bool |
|
| 659 | { |
||
| 660 | // TODO: Make sure that the model supports being saved |
||
| 661 | 7 | if ($this->readonlyModel) { |
|
| 662 | 1 | return false; |
|
| 663 | } |
||
| 664 | |||
| 665 | try { |
||
| 666 | 6 | if (!$this->isDirty()) { |
|
| 667 | 1 | return true; |
|
| 668 | } |
||
| 669 | |||
| 670 | 5 | if ($this->exists) { |
|
| 671 | // TODO: If we get null from the PUT, throw/handle exception |
||
| 672 | 1 | $response = $this->getClient() |
|
| 673 | 1 | ->put($this->getPath(), $this->getDirty()); |
|
| 674 | |||
| 675 | // Record the changes |
||
| 676 | 1 | $this->syncChanges(); |
|
| 677 | |||
| 678 | // Reset the model with the results as we get back the full model |
||
| 679 | 1 | $this->setRawAttributes($response, true); |
|
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 680 | |||
| 681 | 1 | return true; |
|
| 682 | } |
||
| 683 | |||
| 684 | 5 | $response = $this->getClient() |
|
| 685 | 5 | ->post($this->getPath(), $this->toArray()); |
|
| 686 | |||
| 687 | 3 | $this->exists = true; |
|
| 688 | |||
| 689 | // Reset the model with the results as we get back the full model |
||
| 690 | 3 | $this->setRawAttributes($response, true); |
|
| 691 | |||
| 692 | 3 | return true; |
|
| 693 | |||
| 694 | 2 | } catch (GuzzleException $e) { |
|
| 695 | // TODO: Do something with the error |
||
| 696 | |||
| 697 | 2 | return false; |
|
| 698 | } |
||
| 699 | } |
||
| 700 | |||
| 701 | /** |
||
| 702 | * Save the model in ClickUp, but raise error if fail |
||
| 703 | * |
||
| 704 | * @return bool |
||
| 705 | * @throws NoClientException |
||
| 706 | * @throws TokenException |
||
| 707 | * @throws UnableToSaveException |
||
| 708 | */ |
||
| 709 | 2 | public function saveOrFail(): bool |
|
| 710 | { |
||
| 711 | 2 | if (!$this->save()) { |
|
| 712 | 1 | throw new UnableToSaveException(); |
|
| 713 | } |
||
| 714 | |||
| 715 | 1 | return true; |
|
| 716 | } |
||
| 717 | |||
| 718 | |||
| 719 | /** |
||
| 720 | * Set the readonly |
||
| 721 | * |
||
| 722 | * @param bool $readonly |
||
| 723 | * |
||
| 724 | * @return $this |
||
| 725 | */ |
||
| 726 | 4 | public function setReadonly($readonly = true): self |
|
| 727 | { |
||
| 728 | 4 | $this->readonlyModel = $readonly; |
|
| 729 | |||
| 730 | 4 | return $this; |
|
| 731 | } |
||
| 732 | |||
| 733 | /** |
||
| 734 | * Set the given relationship on the model. |
||
| 735 | * |
||
| 736 | * @param string $relation |
||
| 737 | * @param mixed $value |
||
| 738 | * |
||
| 739 | * @return $this |
||
| 740 | */ |
||
| 741 | 3 | public function setRelation($relation, $value): self |
|
| 742 | { |
||
| 743 | 3 | $this->relations[$relation] = $value; |
|
| 744 | |||
| 745 | 3 | return $this; |
|
| 746 | } |
||
| 747 | |||
| 748 | /** |
||
| 749 | * Convert the model instance to an array. |
||
| 750 | * |
||
| 751 | * @return array |
||
| 752 | */ |
||
| 753 | 13 | public function toArray(): array |
|
| 754 | { |
||
| 755 | 13 | return array_merge($this->attributesToArray(), $this->relationsToArray()); |
|
| 756 | } |
||
| 757 | |||
| 758 | /** |
||
| 759 | * Convert the model instance to JSON. |
||
| 760 | * |
||
| 761 | * @param int $options |
||
| 762 | * |
||
| 763 | * @return string |
||
| 764 | * |
||
| 765 | * @throws JsonEncodingException |
||
| 766 | */ |
||
| 767 | 1 | public function toJson($options = 0): string |
|
| 768 | { |
||
| 769 | 1 | $json = json_encode($this->jsonSerialize(), $options); |
|
| 770 | |||
| 771 | // @codeCoverageIgnoreStart |
||
| 772 | if (JSON_ERROR_NONE !== json_last_error()) { |
||
| 773 | throw JsonEncodingException::forModel($this, json_last_error_msg()); |
||
| 774 | } |
||
| 775 | // @codeCoverageIgnoreEnd |
||
| 776 | |||
| 777 | 1 | return $json; |
|
| 778 | } |
||
| 779 | } |
||
| 780 |