Giter Site home page Giter Site logo

cybercog / laravel-eloquent-flag Goto Github PK

View Code? Open in Web Editor NEW
133.0 7.0 21.0 441 KB

Laravel Eloquent boolean & timestamp flagged attributes behavior.

Home Page: https://komarev.com/sources/laravel-eloquent-flag

License: MIT License

PHP 99.59% Dockerfile 0.41%
laravel scope eloquent trait cog flag boolean timestamp status state

laravel-eloquent-flag's Introduction

Laravel Eloquent Flag

cog-laravel-eloquent-flag

Discord Releases Build StyleCI Code Quality License

Introduction

Eloquent boolean & timestamp flagged attributes behavior. Enhance Eloquent Models with commonly used state flags like Active, Published, Approved and others in a minutes!

Contents

Features

Available flags list

Trait name Logic Database column Flag type Conflict
HasAcceptedAt Classic accepted_at Timestamp HasAcceptedFlag
HasAcceptedFlag Classic is_accepted Boolean HasAcceptedAt
HasActiveFlag Classic is_active Boolean -
HasApprovedAt Classic approved_at Timestamp HasApprovedFlag
HasApprovedFlag Classic is_approved Boolean HasApprovedAt
HasArchivedAt Inverse archived_at Timestamp HasArchivedFlag
HasArchivedFlag Inverse is_archived Boolean HasArchivedAt
HasClosedAt Inverse closed_at Timestamp HasClosedFlag
HasClosedFlag Inverse is_closed Boolean HasClosedAt
HasDraftedAt Inverse drafted_at Timestamp HasDraftedFlag
HasDraftedFlag Inverse is_drafted Boolean HasDraftedAt
HasEndedAt Inverse ended_at Timestamp HasEndedFlag
HasEndedFlag Inverse is_ended Boolean HasEndedAt
HasExpiredAt Inverse expired_at Timestamp HasExpiredFlag
HasExpiredFlag Inverse is_expired Boolean HasExpiredAt
HasInvitedAt Classic invited_at Timestamp HasInvitedFlag
HasInvitedFlag Classic is_invited Boolean HasInvitedAt
HasKeptFlag Classic is_kept Boolean -
HasPublishedAt Classic published_at Timestamp HasPublishedFlag
HasPublishedFlag Classic is_published Boolean HasPublishedAt
HasVerifiedAt Classic verified_at Timestamp HasVerifiedFlag
HasVerifiedFlag Classic is_verified Boolean HasVerifiedAt

Any entity can has more than one flag at the same time. If flags can't work for the same entity simultaneously they are listed in Conflict column.

How it works

Eloquent Flag is an easy way to add flagged attributes to eloquent models. All flags has their own trait which adds global scopes to desired entity.

There are 2 types of flags:

  • Boolean flags are the common ones. Stored in database as BOOLEAN or TINYINT(1) value.
  • Timestamp flags represented in database as nullable TIMESTAMP column. Useful when you need to know when action was performed.

All flags separated on 2 logical groups:

  • Classic flags displays only entities with true or timestamp flag value by default.
  • Inverse flags displays only entities with false or null flag value by default.

Omitted entities could be retrieved by using special global scope methods, unique for each flag.

Example: If your Article model has PublishedAt flag then Article::get() will return you only published records. When you need to get only unpublished records you could call Article::onlyUnpublished()->get() and Article::withUnpublished()->get() will return you published and unpublished articles as well.

Helper traits will automatically cast flag attributes to a DateTime / Carbon instance or bool for you.

Installation

Pull in the package through Composer.

composer require cybercog/laravel-eloquent-flag

Usage

Usage examples described in Wiki

Changelog

Please see CHANGELOG for more information on what has changed recently.

Upgrading

Please see UPGRADING for detailed upgrade instructions.

Contributing

Please see CONTRIBUTING for details.

Testing

Run the tests with:

vendor/bin/phpunit

Security

If you discover any security related issues, please email [email protected] instead of using the issue tracker.

Contributors

@antonkomarev
Anton Komarev
@zagreusinoz
zagreusinoz
@jonagoldman
Jona Goldman
@irazasyed
Irfaq Syed
@gpioto
gpioto
@rayronvictor
Rayron Victor
@jnbn
Ugur Aydogdu

Eloquent Flag contributors list

Alternatives

Feel free to add more alternatives as Pull Request.

License

About CyberCog

CyberCog is a Social Unity of enthusiasts. Research the best solutions in product & software development is our passion.

CyberCog

laravel-eloquent-flag's People

Contributors

antonkomarev avatar gabrielpioto avatar irazasyed avatar jnbn avatar jonagoldman avatar maherelgamil avatar rayronvictor avatar vesper8 avatar zagreusinoz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel-eloquent-flag's Issues

Methods naming

The most hardest part is choose right names for methods which wouldn't confuse developer what exactly will be done.

Preface

At the start of the package live I've chosen a naming concept where unset flag method should use verbose antonym of set method. This was a good choice at the start point, but with each new flag there were harder to strictly follow this convention.

Current flags methods

Flag name Set method Unset method Scope methods postfix
Classic\Accepted accept unaccept *Unaccepted
Classic\Active activate deactivate *Inactive
Classic\Approved approve unapprove *Unapproved
Classic\Kept keep unkeep *Unkept
Classic\Opened open ??? *Closed
Classic\Published publish unpublish *Unpublished
Classic\Verified verify unverify *Unverified
Inverse\Closed close ??? *Closed
Inverse\Expired expire unexpire *Expired

Issue 1 (not exists antonyms): I haven't found any verbose antonym for expire word (valid isn't a good one because it's opposite of invalid). After that I had to add one more not exists word unexpire. Same case was issued with verify and keep. Things started to be not so clear.

Issue 2 (same words for logically different flags): Then Classoc\Approved and Classic\Accepted flags were introduced. Both of this words has similar antonyms: Refuse, Reject, Decline. When I see decline method in code, I'm not sure is it decline of an Approved flag or decline of an Accepted flag? By example of expired and verified flags I've used one more not exists word unapprove for unset flag method of an Approved flag, but it's started to ruin standard. Now I see that I missed possibility to use disapprove method for approve and reject for accept but there could be other words with same issue.

Issue 3 (methods name intersection): After flags with inverse logic were introduced there one more issue revealed. Both of the Inverse\Closed and Classic\Opened flags has open and close methods which leads to conflict in case of simultaneous use. It's not critical, because there are no use cases was found for it but should be considered and documented.

Finally I've decided to choose one naming concept and follow if for all future releases. There are 3 naming concepts I can see so far. Each has own pros and cons.

1. Flag unset methods names are antonyms of set method

All unset flag methods SHOULD use antonyms of the set flag method name.

Pros:

  • Intuitive. It looks obvious that if you need to remove active flag from model - you should deactivate it.
  • Follow English language grammar.

Cons:

  • Issue 1 (not exists antonyms). Words expire, verify, keep don't have any verbose antonyms which will be clearly understood by developer as opposite of these ones without diving into the code or docs.
  • Issue 2 (same words for logically different flags). Refuse, Reject, Decline are antonyms for both of the words Accept and Approve.
  • Issue 3 (methods name intersection). Closed inverse flag has conflicts with Opened classic flag.

Flags methods

Flag name Set method Unset method Scope methods postfix
Classic\Accepted accept reject *Rejected
Classic\Active activate deactivate *Deactivated
Classic\Approved approve disapprove *Disapproved
Classic\Kept keep unkeep *Unkept
Classic\Opened open close *Closed
Classic\Published publish unpublish *Unpublished
Classic\Verified verify unverify *Unverified
Inverse\Closed close open *Closed
Inverse\Expired expire unexpire *Expired

2. Flag unset methods names starts with un

All unset flag methods SHOULD have un prefix. Not exists words will be used then: unaccept, unactivate, unapprove, unexpire, unclose.

Pros:

  • Solves Issue 1 (not exists antonyms). No worry about antonyms. Just add prefix to unset name.
  • Solves Issue 2 (same words for logically different flags). It wouldn't matter if antonym could be valid for more than one word. decline and reject could be treated as unset methods for accept and approve methods, but unaccept and unapprove not.
  • Partially solves Issue 3 (methods name intersection). Closed flag will have close & unclose methods and Opened flag will have open & unopen methods, what allows us to use them in one model. This isn't a silver bullet anyway because such flags as Unpublished or any other words which has antonym prefixed with un are still be bugged.
  • Consistent naming convention. If you want to unset flag - you should just add un prefix to it's name.
  • Easier to remember. It's easier to remember opposite name of the action because it's always will just add a prefix.

Cons:

  • Bye grammar. And hello a whole bunch of not exists words in your code.

Flags methods

Flag name Set method Unset method Scope methods postfix
Classic\Accepted accept unaccept *Unaccepted
Classic\Active activate unactivate *Unactivated
Classic\Approved approve unapprove *Unapproved
Classic\Kept keep unkeep *Unkept
Classic\Opened open unopen *Unopened
Classic\Published publish unpublish *Unpublished
Classic\Verified verify unverify *Unverified
Inverse\Closed close unclose *Closed
Inverse\Expired expire unexpire *Expired

3. Both of these concepts should work

Both of previous concepts SHOULD be implemented. All unset flag methods will have prefix un in name and it's alias named as antonym of the set flag method.

That will create reject and unaccept methods as opposite of accept.

Pros:

  • Solves Issue 1 (not exists antonyms). No worry about antonyms. Just add prefix to unset name.
  • Solves Issue 2 (same words for logically different flags). It wouldn't matter if antonym could be valid for more than one word. decline and reject could be treated as unset methods for accept and approve methods, but unaccept and unapprove not.
  • Partially solves Issue 3 (methods name intersection). Closed flag will have close & unclose methods and Opened flag will have open & unopen methods, what allows us to use them in one model. This isn't a silver bullet anyway because such flags as Unpublished or any other words which has antonym prefixed with un are still be bugged.
  • Follow English language grammar.
  • Intuitive. It looks obvious that if you need to remove active flag from model - you should deactivate it.
  • Free to choose. Developer can choose what does he likes.

Cons:

  • Naming inconsistency.
  • Namespace narrowing. Aliases spam isn't very good because of increasing possibility of methods name intersection with other packages.

Flags methods

Flag name Set method Unset method Scope methods postfix
Classic\Accepted accept reject & unaccept *Unaccepted & *Rejected
Classic\Active activate deactivate & unactivate *Unactivated & *Deactivated
Classic\Approved approve disapprove & unapprove *Unapproved & *Disapproved
Classic\Kept keep unkeep *Unkept
Classic\Opened open close & unopen *Closed
Classic\Published publish unpublish *Unpublished
Classic\Verified verify unverify *Unverified
Inverse\Closed close open & unclose *Closed
Inverse\Expired expire unexpire *Expired

Conclusion

If concept no. 1 will be accepted these methods will be affected:

  • unaccept -> reject
  • unapprove -> disapprove
  • *Unaccepted -> *Rejected
  • *Inactive -> *Deactivated
  • *Unapproved -> *Disapproved

Problems with antonyms for words like verify and expire wouldn't be resolved. As exception for words which doesn't have verbose antonym un prefix will be used still.

If concept no. 2 will be accepted these methods will be affected:

  • deactivate -> unactivate
  • *Inactive -> *Unactivated
  • *Closed -> *Unopened

[CONCEPT] Nullable flags

It's just a concept. Not yet decided if it will be included in future.

Add nullable flags state which will means that none of the actions were performed.

To proof concept we'll look at Classic\Accept flag there could be 3 states:

  • is_accepted = null - Not accepted and not rejected yet.
  • is_accepted = true - Entity was accepted.
  • is_accepted = false - Entity was rejected.

Scopes

NULL

  • withNotYetAccepted will get true + null entities.
  • withoutNotYetAccepted will omit only null entities.
  • onlyNotYetAccepted will get only null entities.

FALSE

  • withRejected will get true + false entities.
  • withoutRejected will omit only false entities.
  • onlyRejected will get only false entities.

NULL + FALSE

  • Some method to get null + false + true entities. Right now withRejected method is removing global scope.

The main issue here that all FALSE scopes removes global scope, and entities with NULL flag will be displayed then.

// :TODO: Write test case and try to implement nullable flag

Or find another solution, like extra database table to display what entities was not accepted or rejected yet.

Events needed

It will be very useful to fire events on actions like activate(), deactivate(), publish(), unpublish() and so on.

Use cases:

  • Listen for event and notify moderator that somebody published something.
  • Perform logging of all user actions (track user activity).
  • Perform actions on entity relations.

Caveats:

Action to change state of the records:

Post::whereIn('id', [4, 5, 6, 7])->publish();

Calling query under the hood:

$builder->macro('publish', function (Builder $builder) {
    $builder->withUnpublished();
    return $builder->update(['is_published' => 1]);
});

Because this actions are performed on query level:

  1. Can't fire an events on each model state change via global scopes.
  2. Can't determine if record state was changed.

Flags to add

  • is_expired (inverse) #8
  • is_closed (inverse) #9
  • published_at (classic) #17
  • accepted_at (classic) #18
  • verified_at (classic) #19
  • closed_at (inverse)
  • approved_at (classic) #21
  • is_ended (inverse)
  • is_draft | is_drafted (inverse)
  • is_temp (inverse)
  • is_opened (classic)
  • is_archived (inverse)
  • is_disabled (inverse)
  • is_enabled (classic)

Fire events

I see lots of // :TODO: Fire an event here in the source code. Is there a reason events are not fired?

can this be used for "user-to-many" blocked scenario?

Hi there!

I just found your other package laravel-ban which I plan to use

I'm also looking for a package that will help me implement the following.

I am making a dating site and users can "block" other users. In that case the user would only be able to see users that are not on their block list.

Wondering if with this library you can create custom flags? In my case it would be a "banned" and "banned_at" flag. And does this library support flags that are only applicable to certain users?

With the laravel-ban the ban is applied categorically, but with this library the ban (or block) is only applied to the scope of the logged-in user ?

htmlspecialchars() expects parameter 1 to be string, object given

@antonkomarev Hello! I try to use this package in my project.
Took info from this wiki page: https://github.com/cybercog/laravel-eloquent-flag/wiki/Publishable-model

One line from migration:

$table->boolean('is_published')->default(0);

Model:

<?php

namespace App;

use Cog\Flag\Traits\Classic\HasPublishedFlag;
use Illuminate\Database\Eloquent\Model;
use Spatie\Tags\HasTags;

class Post extends Model
{
    use HasTags;
    use HasPublishedFlag;
    //

    protected $fillable = ['tags'];
}

Database (posts table) content:
image

My Controller:

public function index()
{
    $posts = Post::onlyNotPublished();
    //dd($posts);
    return view('manage.posts.index')->withPosts($posts);
}

And finally I got an error:
image

dd result:
image

What I'm doing wrong?

p.s. Method publish() works fine.

Laravel 5.8
laravel-eloquent-flag 5.1.2

[question] what i do here wrong

i got a model that look like this
`<?php

namespace App;

use Cog\Flag\Traits\Classic\HasApprovedFlag;
use Cog\Flag\Traits\Classic\HasPublishedFlag;
use Illuminate\Database\Eloquent\Model;

class Books extends Model
{
use HasApprovedFlag, HasPublishedFlag;
/**
* The attributes that should be hidden for arrays.
*
* @var array
/
protected $hidden = [
'id'
];
/
*
* The attributes that aren't mass assignable.
*
* @var array
*/
protected $guarded = ['external_id'];

/**
 * scope get resource by external id.
 *
 * @param \Illuminate\Database\Eloquent\Builder $query
 * @param string $uuid
 * @return \Illuminate\Database\Eloquent\Builder
 */
public function ScopeByResourceExternalId($query, $uuid)
{
    return $query->where('external_id', $uuid);
}

/**
 * scope by resource
 *
 * @param \Illuminate\Database\Eloquent\Builder $query
 * @param array<string> $tagsNamesArr array of tags
 * @return \Illuminate\Database\Eloquent\Builder
 */
public function ScopeByResourceIdAndTagsName($query, $tagsNamesArr)
{
    return $query->whereIn('id',
        $this->tags()->whereIn('name', $tagsNamesArr)->select('book_id')
    );
}

/**
 * scope by resource and user id
 * @param \Illuminate\Database\Eloquent\Builder $query
 * @param string $resourceId
 * @param integer $user_id
 * @return \Illuminate\Database\Eloquent\Builder
 */
public function ScopeByResourceIdAndUserId($query, $resourceId, $user_id)
{
    return $query->where([
        ['external_id', '=', $resourceId],
        ['user_id', '=', $user_id]
    ]);
}

/**
 * The tags that belong to the book.
 */
public function tags()
{
    return $this->belongsToMany('App\EntityTags', 'books_has_entity_tags');
}

}
`

and controller method index that work like this
public function index(Request $request) { $show_publish = ($request->query('show_publish', '0') != '1') ? false : true; $show_approved = ($request->query('show_approved', '0') != '1') ? false : true; $books = ($show_publish) ? Books::WithNotPublished() : Books::WithoutNotPublished(); //$books = ($show_approved) ? $response = $this->processResponse($books->get()); return $response->success(); }

my issue is this
i get this error
BadMethodCallException
Call to undefined method App\Books::WithoutNotPublished()
http://backend.booksurf.local/api/books
Hide solutions
Bad Method Call
Did you mean App\Books::isNotPublished() ?

what do i do wrong what i did miss readed?

New methods naming

After couple of years passed I've started to revise all my initial decisions from #11. The main concept is to try to use only one word for all the methods & events names within a single flag.

  1. Helper boolean methods names like isExpired and it's opposite antonyms like isUnexpired should be isExpired & isNotExpired.

  2. Helper action methods with unsetting flag should has undo* prefix: expire and it's opposite unexpire could be renamed to expire & undoExpire.

  3. Additionally it will require to change events names introduced in PR #37. Setting of the flags will be untouched because they are just copy of the methods name: expired, invited and so on, but removal of the flag are the opposite, and should has suffix *Undone: expiredUndone, invitedUndone.

  4. Global scope methods should use only flag name in it's methods names:

  • approve & disapprove could be renamed to approve & undoApprove.
  • withDisapproved could be renamed to withNotApproved
  • withoutDisapproved could be renamed to withoutNotApproved
  • onlyDisapproved could be renamed to onlyNotApproved

With this changes we will prevent naming conflicts and all the methods names will be more intuitive without documentation.

Remove force auto-applying global scopes

After start using this package in different projects I've understood that has made wrong decision at the start.

I've started to build classes with a lot of such methods to don't use auto apply and have more hand control:

public function shouldApplyPublishedAtScope()
{
    return false;
}

I'm thinking to invert this behavior in next major version.

Maybe it is a good starting point to make configuration file for this package.

Set future date for timestamp flags

This could be useful for example when you want an article to be automatically shown in future date.

Right now if future published_at will be setted - this record will be published immediately. Global scopes and helpers should check not for the null value, but for the exact date is past.

This use case will be useful for all timestamp flags. For example expired_at flag will be useful for subscriptions and so on.

Flags documentation

Amount of traits is grow and hard and README file starts to be too big. Each flag description should be extracted to wiki.

[CONCEPT] Casts flags

First mockup:

public function getCasts()
{
    return array_merge([
        'published_at' => 'datetime',
    ], parent::getCasts());
}

Need to verify if multiple flags will apply getCasts methods.

Add getQualified***Column method

In SoftDeletes trait getQualifiedDeletedAtColumn() method is used to avoid errors in queries when join is used. Sample:
public function apply(Builder $builder, Model $model) { $builder->whereNull($model->getQualifiedDeletedAtColumn()); }
It is necessary to add similar functionality to this package.

v3.0 release

The new v3.0 release is in progress and will include a lot of changes.

Flags methods naming convention

Methods naming convention was approved. That means couple of current methods will be renamed to follow the convention. That's the only breaking change in this release.

These methods should be renamed to upgrade from v2.0:

  • unaccept() to reject()
  • unapprove() to disapprove()
  • withUnaccepted() to withRejected()
  • withoutUnaccepted() to withoutRejected()
  • onlyUnaccepted() to onlyRejected()
  • withInactive() to withDeactivated()
  • withoutInactive() to withoutDeactivated()
  • onlyInactive() to onlyDeactivated()
  • withUnapproved() to withDisapproved()
  • withoutUnapproved() to withoutDisapproved()
  • onlyUnapproved() to onlyDisapproved()

New structure of flag traits

New structure fully compatible with v2.0. All flag traits were spliced on a multiple files which included in root flag trait.

Each flag has root trait Has{Name}Flag which will use:

  • Has{Name}FlagScope adds global scopes to flag.
  • Has{Name}FlagHelpers for additional flag helper methods.
  • Has{Name}FlagBehavior - used only in Kept flag to implement it's default behavior (model will be created with is_kept=false and will change to is_kept=true on first model update).

This will add possibility to use only global scope methods, or only helpers without adding whole flag functionality. Helper methods will bring common checks for the flags.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.