Skip to content

Commit

Permalink
Merge branch 'updated-value-object' into 'master'
Browse files Browse the repository at this point in the history
Updated value object

See merge request composer/Laravel-Helpers!18
  • Loading branch information
craigAtCD committed Mar 13, 2024
2 parents 5413ef9 + cf11092 commit f9dcd5f
Show file tree
Hide file tree
Showing 18 changed files with 206 additions and 150 deletions.
2 changes: 1 addition & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ include:
variables:
TEST_PHP_8_3: "true"
TEST_PHP_8_2: "true"
TEST_PHP_8_1: "true"
TEST_PHP_8_1: "false"
TEST_PHP_8_0: "false"
43 changes: 31 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ Collection of helpers for re-use accross a few of our projects
- [Case insensitive statments](#case-insensitive-statments)
- [Enforced Non Nullable Relations (orFail chain)](#enforced-non-nullable-relations-orfail-chain)
- [DB Repositories](#db-repositories)
- [String Macros](#string-macros)
- [Observerable trait](#observerable-trait)
- [Observerable trait (Deprecated)](#observerable-trait-deprecated)
- [Date Manipulation](#date-manipulation)
- [Date(Carbon) Helpers attached to above:](#datecarbon-helpers-attached-to-above)
- [Value Objects](#value-objects)
Expand All @@ -31,6 +30,7 @@ Collection of helpers for re-use accross a few of our projects

## Installation


Install via composer

```bash
Expand Down Expand Up @@ -123,17 +123,27 @@ use of repositories via extending the `CustomD\LaravelHelpers\Repository\BaseRep
example in the [UserRepository.stub.php](https://git.customd.com/composer/Laravel-Helpers/-/blob/master/src/Repository/UserRepository.php.stub) file


## String Macros
`Str::reverse(string)` - to safely reverse a string that is multibyte safe.

## Observerable trait
## Observerable trait (Deprecated)
adding this trait to your models will automatically look for an observer in the app/Observers folder with the convension {model}Observer as the classname,

you can additionally/optionally add
```php
protected static $observers = [ ...arrayOfObservers]
```
to add a additional ones if needed
to add a additional ones if

Replace this with
```
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use App\Observers\UserObserver;
#[ObservedBy(UserObserver::class)]
#[ObservedBy(AnotherUserObserver::class)]
class User extends Model
{
//
}
```

## Date Manipulation

Expand Down Expand Up @@ -179,8 +189,9 @@ methods available:
* `usersStartOfYear(): Static`
* `usersEndOfYear(): Static`
* `parseWithTz(string $time): Static` - parses the time passed using the users timezone unless the timezone is in the timestamp
* `hasISOFormat(string $date): bool` - checks if the date is in iso format.

You can also use the CDCarbonDate to create a few differnt date objects.
You can also use the CDCarbonDate to create a few different date objects.

## Value Objects
Example:
Expand All @@ -192,11 +203,11 @@ namespace CustomD\LaravelHelpers\Tests\ValueObjects;

use CustomD\LaravelHelpers\ValueObjects\ValueObject;

class SimpleValue extends ValueObject
final readonly class SimpleValue extends ValueObject
{
protected function __construct(
readonly public string $value,
readonly public int $count = 0
public string $value,
public int $count = 0
) {
}

Expand Down Expand Up @@ -226,7 +237,7 @@ use CustomD\LaravelHelpers\ValueObjects\Attributes\MakeableObject;
use CustomD\LaravelHelpers\ValueObjects\Attributes\ChildValueObject;
use CustomD\LaravelHelpers\ValueObjects\Attributes\CollectableValue;

class ComplexValue extends ValueObject
final readonly class ComplexValue extends ValueObject
{
public function __construct(
#[ChildValueObject(StringValue::class)]
Expand All @@ -246,6 +257,14 @@ class ComplexValue extends ValueObject

Best practice is to use the make option, which will validate, if you use a public constructor it will not.

These should all be marked as READONLY and FINAL.

The attributes available are:
* `ChildValueObject(valueobectclass)` - which will make a new valueObject
* `CollectableValue(valueobjectclass)` - which will convert an array to a coollection of the value objects
* `MakeableObject(class, [?$spread = false])` - will look for a make method or else construct if passed an non object - if spread is true will expand the array else will pass the array as a single argument


## Larastan Stubs
**these are temporary only till implemented by larastan**

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
}
],
"require": {
"php": "^8.1",
"php": "^8.2",
"illuminate/notifications": "^9.0|^10.0|^11.0",
"illuminate/support": "^9.0|^10|^11.0"
},
Expand Down
5 changes: 5 additions & 0 deletions src/CdCarbonMixin.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,9 @@ public function usersFormat(): Closure
return $date->setTimezone($date->getUserTimezone())->format($format);
};
}

public function hasISOFormat(): callable
{
return static fn ($date): bool => Carbon::hasFormat($date, 'Y-m-d\TH:i:s.u\Z');
}
}
40 changes: 34 additions & 6 deletions src/Database/Query/Mixins/NullOrEmptyMixin.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ class NullOrEmptyMixin

public function whereNullOrEmpty(): Closure
{
return function (string $column) {
return function (string $column): Builder {
/** @var \Illuminate\Database\Query\Builder $this */
return $this->where(fn (Builder $builder) => $builder->where($column, '=', '')->orWhereNull($column));
};
}

public function orWhereNullOrEmpty(): Closure
{
return function (string $column) {
return function (string $column): Builder {
/** @var \Illuminate\Database\Query\Builder $this */
return $this->orWhere(fn (Builder $builder) => $builder->where($column, '=', '')->orWhereNull($column));
};
Expand All @@ -29,15 +29,15 @@ public function orWhereNullOrEmpty(): Closure

public function whereNotNullOrEmpty(): Closure
{
return function (string $column) {
return function (string $column): Builder {
/** @var \Illuminate\Database\Query\Builder $this */
return $this->where(fn(Builder $builder) => $builder->where($column, '!=', '')->whereNotNull($column));
};
}

public function orWhereNotNullOrEmpty(): Closure
{
return function (string $column) {
return function (string $column): Builder {
/** @var \Illuminate\Database\Query\Builder $this */
return $this->orWhere(fn(Builder $builder) => $builder->where($column, '!=', '')->whereNotNull($column));
};
Expand All @@ -46,13 +46,41 @@ public function orWhereNotNullOrEmpty(): Closure
public function whereNullOrValue(): Closure
{
/** @param $value mixed **/
return function (string $column, $operator = null, $value = null, $boolean = 'and') {
return function (string $column, $operator = null, $value = null, $boolean = 'and'): Builder {
/** @var \Illuminate\Database\Query\Builder $this */
[$value, $operator] = $this->prepareValueAndOperator(
$value, $operator, func_num_args() === 2
$value,
$operator,
func_num_args() === 2
);

return $this->where(fn (Builder $builder) => $builder->whereNull($column)->when($value, fn($sbuilder) => $sbuilder->orWhere($column, $operator, $value, $boolean)));
};
}

public function iWhere(): Closure
{
return function (string|array $column, $operator = null, $value = null, $boolean = 'and'): Builder {
/** @var \Illuminate\Database\Query\Builder $this */
if (is_array($column)) {
return $this->addArrayOfWheres($column, $boolean, 'iWhere'); //@phpstan-ignore-line
}

[$value, $operator] = $this->prepareValueAndOperator(
$value,
$operator,
func_num_args() === 2
);

return $this->whereRaw("LOWER({$column}) {$operator} ?", [strtolower($value)], $boolean);
};
}

public function orIWhere(): Closure
{
return function (string|array $column, $operator = null, $value = null): Builder {
/** @var \Illuminate\Database\Query\Builder $this */
return $this->iWhere($column, $operator, $value, 'or');
};
}
}
2 changes: 2 additions & 0 deletions src/Http/Middleware/UserTimeZone.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
namespace CustomD\LaravelHelpers\Http\Middleware;

use Carbon\CarbonImmutable;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
Expand Down Expand Up @@ -42,5 +43,6 @@ public function setTimeZone(Request $request): void
);

Carbon::setUserTimezone($timezone); //@phpstan-ignore-line -- this is a mixin on the library
CarbonImmutable::setUserTimezone($timezone); //@phpstan-ignore-line -- this is a mixin on the library
}
}
119 changes: 50 additions & 69 deletions src/Models/Policies/CrudPermissions.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,106 +2,87 @@

namespace CustomD\LaravelHelpers\Models\Policies;

use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Access\Authorizable;
use Illuminate\Database\Eloquent\InvalidCastException;
use Illuminate\Database\Eloquent\MissingAttributeException;
use Illuminate\Database\LazyLoadingViolationException;
use Illuminate\Support\Facades\Gate;
use LogicException;

trait CrudPermissions
{

public function can(Authenticatable $user, string $action, ?Model $model = null): bool
public function can(Authenticatable&Authorizable $user, string $action, ?Model $model = null): bool
{
$permission = collect([
$this->permission_name ?? self::parsePermissionNameFromPolicy(),
$action
])->filter()->implode(".");

if ($model && method_exists($model, 'userHasPermission'))
{
info('Model::userHasPermission calls have been deprecated - and will be removed in the next version');
if(! $model->userHasPermission($user)) {
return false;
if ($model) {
$can = $this->canOnModel($user, $permission, $model);
if ($can === true) {
return $can;
}
$can = $this->canOnModelField($user, $permission, $model);
if ($can === true) {
return $can;
}
}
return $user->can($permission);
}

public static function parsePermissionNameFromPolicy(): string
{
return Str::of(class_basename(get_called_class()))
->replaceLast('Policy', '')
->snake()
->plural()
->value;
return $user->can($permission);
}

/**
* Determine whether the user can view any models.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return mixed
* @still in development
*/
public function viewAny(Authenticatable $user)
protected function canOnModel(Authenticatable&Authorizable $user, string $permission, Model $model): ?true
{
return $this->can($user, 'viewAny');
}

/**
* Determine whether the user can view the model.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return mixed
*/
public function view(Authenticatable $user, Model $model)
{
return $this->can($user, 'view');
}
if (property_exists($this, 'ownerIdColumn') === false) {
return null;
}

/**
* Determine whether the user can create models.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return mixed
*/
public function create(Authenticatable $user)
{
return $this->can($user, 'create');
$ownerId = $model->getAttribute($this->ownerIdColumn);
if ($ownerId === $user->getAuthIdentifier()) {
return true;
}

return null;
}

/**
* Determine whether the user can update the model.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return mixed
* @still in development
*/
public function update(Authenticatable $user, Model $model)
protected function canOnModelField(Authenticatable&Authorizable $user, string $permission, Model $model): ?bool
{
return $this->can($user, 'update');
if (property_exists($this, 'modelField') === false || $this->modelField === false) {
return null;
}

if ($this->modelField === true || $this->modelField === '*') {
$permission .= ".*";
} else {
$permission .= "." . $model->getAttribute($this->modelField);
}

return $user->can($permission);
}

/**
* Determine whether the user can delete the model.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return mixed
*/
public function delete(Authenticatable $user, Model $model)
public static function parsePermissionNameFromPolicy(): string
{
return $this->can($user, 'delete');
return Str::of(class_basename(get_called_class()))
->replaceLast('Policy', '')
->snake()
->plural()
->value;
}

/**
* Determine whether the user can restore the model.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return mixed
*/
public function restore(Authenticatable $user, Model $model)
public function __call($method, $parameters)
{
return $this->can($user, 'restore');
return $this->can($parameters[0], $method, $parameters[1] ?? null);
}
}
Loading

0 comments on commit f9dcd5f

Please sign in to comment.